Skip to content

Commit 52dafde

Browse files
authored
infra(bixarena): develop BixArena CDK app for the dev environments (SMR-589) (Sage-Bionetworks#3650)
1 parent 2c5a5c7 commit 52dafde

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2557
-536
lines changed

apps/bixarena/api-gateway/project.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
"command": "./gradlew :{projectName}:generateRouteConfig"
5252
},
5353
"dependsOn": ["^build"]
54+
},
55+
"export-image-tarball": {
56+
"executor": "nx:run-commands",
57+
"options": {
58+
"command": "docker save ghcr.io/sage-bionetworks/{projectName}:local -o /tmp/{projectName}.tar"
59+
},
60+
"dependsOn": ["build-image"]
5461
}
5562
},
5663
"tags": ["type:service", "scope:backend", "language:java", "package-manager:gradle"],
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package org.sagebionetworks.bixarena.api.gateway.filter;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
6+
import org.springframework.cloud.gateway.filter.GlobalFilter;
7+
import org.springframework.core.Ordered;
8+
import org.springframework.http.HttpHeaders;
9+
import org.springframework.stereotype.Component;
10+
import org.springframework.web.server.ServerWebExchange;
11+
import reactor.core.publisher.Mono;
12+
13+
/**
14+
* Global filter to log response headers, particularly Set-Cookie headers.
15+
*
16+
* <p>This filter runs AFTER the response is received from downstream services but BEFORE it's sent
17+
* to the client. It helps debug issues with cookie handling and header propagation.
18+
*
19+
* <p>Order: LOWEST_PRECEDENCE - 1 (runs after most filters but before final response)
20+
*/
21+
@Component
22+
public class ResponseHeaderLoggingFilter implements GlobalFilter, Ordered {
23+
24+
private static final Logger log = LoggerFactory.getLogger(ResponseHeaderLoggingFilter.class);
25+
26+
@Override
27+
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
28+
String path = exchange.getRequest().getPath().value();
29+
String method = exchange.getRequest().getMethod().name();
30+
31+
// Process the request and log response headers
32+
return chain
33+
.filter(exchange)
34+
.then(
35+
Mono.fromRunnable(
36+
() -> {
37+
HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
38+
int statusCode = exchange.getResponse().getStatusCode().value();
39+
40+
// Log Set-Cookie headers for debugging
41+
if (responseHeaders.containsKey(HttpHeaders.SET_COOKIE)) {
42+
var setCookies = responseHeaders.get(HttpHeaders.SET_COOKIE);
43+
log.debug(
44+
"Response headers for {} {}: status={} Set-Cookie count={}",
45+
method,
46+
path,
47+
statusCode,
48+
setCookies != null ? setCookies.size() : 0);
49+
if (setCookies != null) {
50+
for (int i = 0; i < setCookies.size(); i++) {
51+
String cookieValue = setCookies.get(i);
52+
// Log first 100 chars of cookie for debugging (don't log full value for
53+
// security)
54+
String preview =
55+
cookieValue.length() > 100
56+
? cookieValue.substring(0, 100) + "..."
57+
: cookieValue;
58+
log.debug(" Set-Cookie[{}]: {}", i, preview);
59+
}
60+
}
61+
} else {
62+
log.debug(
63+
"Response headers for {} {}: status={} (no Set-Cookie headers)",
64+
method,
65+
path,
66+
statusCode);
67+
}
68+
}));
69+
}
70+
71+
@Override
72+
public int getOrder() {
73+
// Run after most filters but before final response
74+
// LOWEST_PRECEDENCE - 1 ensures this runs late in the chain
75+
return Ordered.LOWEST_PRECEDENCE - 1;
76+
}
77+
}

apps/bixarena/api-gateway/src/main/java/org/sagebionetworks/bixarena/api/gateway/filter/SessionToJwtFilter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
8989
return validateSession(sessionId)
9090
.flatMap(userInfo -> {
9191
var userAuth = createAuthentication(userInfo);
92+
log.debug("Session validated for auth service endpoint, passing through to: {} {}", method, path);
9293
return chain.filter(exchange)
93-
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(userAuth));
94+
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(userAuth))
95+
.doOnSuccess(v -> log.debug("Auth service response completed for: {} {}", method, path));
9496
})
9597
.onErrorResume(e -> handleAuthError(exchange, e, method, path));
9698
}

apps/bixarena/api/project.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@
5353
"parallel": false
5454
},
5555
"dependsOn": ["^build"]
56+
},
57+
"export-image-tarball": {
58+
"executor": "nx:run-commands",
59+
"options": {
60+
"command": "docker save ghcr.io/sage-bionetworks/{projectName}:local -o /tmp/{projectName}.tar"
61+
},
62+
"dependsOn": ["build-image"]
5663
}
5764
},
5865
"tags": ["type:service", "scope:backend", "language:java", "package-manager:gradle"],

apps/bixarena/api/src/main/java/org/sagebionetworks/bixarena/api/configuration/AppProperties.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import jakarta.validation.Valid;
44
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotEmpty;
56
import jakarta.validation.constraints.NotNull;
7+
import java.util.List;
68
import org.springframework.boot.context.properties.ConfigurationProperties;
79
import org.springframework.validation.annotation.Validated;
810

@@ -15,7 +17,8 @@
1517
public record AppProperties(
1618
@NotBlank(message = "Welcome message must not be blank") String welcomeMessage,
1719
@Valid @NotNull AuthService authService,
18-
@Valid @NotNull Jwt jwt
20+
@Valid @NotNull Jwt jwt,
21+
@Valid @NotNull Cors cors
1922
) {
2023
@Validated
2124
public record AuthService(
@@ -27,4 +30,12 @@ public record Jwt(
2730
@NotBlank(message = "Expected issuer must not be blank") String expectedIssuer,
2831
@NotBlank(message = "Expected audience must not be blank") String expectedAudience
2932
) {}
33+
34+
@Validated
35+
public record Cors(
36+
@NotEmpty(message = "Allowed origins must not be empty") List<String> allowedOrigins,
37+
@NotEmpty(message = "Allowed methods must not be empty") List<String> allowedMethods,
38+
@NotEmpty(message = "Allowed headers must not be empty") List<String> allowedHeaders,
39+
@NotNull(message = "Allow credentials must not be null") Boolean allowCredentials
40+
) {}
3041
}

apps/bixarena/api/src/main/java/org/sagebionetworks/bixarena/api/configuration/SecurityConfiguration.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.sagebionetworks.bixarena.api.configuration;
22

3-
import java.util.List;
43
import org.sagebionetworks.bixarena.api.security.JwtAuthenticationConverter;
54
import org.springframework.beans.factory.annotation.Autowired;
65
import org.springframework.context.annotation.Bean;
@@ -109,17 +108,10 @@ public JwtDecoder jwtDecoder() {
109108
@Bean
110109
public CorsConfigurationSource corsConfigurationSource() {
111110
CorsConfiguration config = new CorsConfiguration();
112-
config.setAllowCredentials(true);
113-
config.setAllowedOrigins(
114-
List.of(
115-
"http://localhost:8100",
116-
"http://127.0.0.1:8100",
117-
"http://localhost:7860",
118-
"http://127.0.0.1:7860"
119-
)
120-
);
121-
config.setAllowedMethods(List.of("GET", "POST", "PATCH", "OPTIONS"));
122-
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
111+
config.setAllowCredentials(appProperties.cors().allowCredentials());
112+
config.setAllowedOrigins(appProperties.cors().allowedOrigins());
113+
config.setAllowedMethods(appProperties.cors().allowedMethods());
114+
config.setAllowedHeaders(appProperties.cors().allowedHeaders());
123115
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
124116
source.registerCorsConfiguration("/**", config);
125117
return source;

apps/bixarena/api/src/main/resources/application.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,20 @@ app:
9090
expected-issuer: urn:bixarena:auth
9191
# Expected audience claim value
9292
expected-audience: urn:bixarena:api
93+
cors:
94+
# CORS configuration for browser requests (localhost defaults)
95+
# Can be overridden via env vars (e.g., APP_CORS_ALLOWED_ORIGINS=http://example.com,https://example.com)
96+
allowed-origins:
97+
- http://localhost:8100
98+
- http://127.0.0.1:8100
99+
- http://localhost:7860
100+
- http://127.0.0.1:7860
101+
allowed-methods:
102+
- GET
103+
- POST
104+
- PATCH
105+
- OPTIONS
106+
allowed-headers:
107+
- Authorization
108+
- Content-Type
109+
allow-credentials: true

apps/bixarena/app/project.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@
4848
"bundleLocalDependencies": true
4949
},
5050
"cache": true
51+
},
52+
"export-image-tarball": {
53+
"executor": "nx:run-commands",
54+
"options": {
55+
"command": "docker save ghcr.io/sage-bionetworks/{projectName}:local -o /tmp/{projectName}.tar"
56+
},
57+
"dependsOn": ["build-image"]
5158
}
5259
},
5360
"tags": ["type:app", "scope:backend", "language:python", "package-manager:uv"]

apps/bixarena/auth-service/project.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@
5353
"parallel": false
5454
},
5555
"dependsOn": ["^build"]
56+
},
57+
"export-image-tarball": {
58+
"executor": "nx:run-commands",
59+
"options": {
60+
"command": "docker save ghcr.io/sage-bionetworks/{projectName}:local -o /tmp/{projectName}.tar"
61+
},
62+
"dependsOn": ["build-image"]
5663
}
5764
},
5865
"tags": ["type:service", "scope:backend", "language:java", "package-manager:gradle"],

apps/bixarena/auth-service/src/main/java/org/sagebionetworks/bixarena/auth/service/configuration/AppProperties.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import jakarta.validation.Valid;
44
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotEmpty;
56
import jakarta.validation.constraints.NotNull;
67
import java.net.URI;
8+
import java.util.List;
79
import org.springframework.boot.context.properties.ConfigurationProperties;
810
import org.springframework.validation.annotation.Validated;
911

@@ -18,7 +20,8 @@ public record AppProperties(
1820
/** Base URL of UI (Gradio) used for post-login redirect */
1921
@NotBlank(message = "UI base URL must not be blank") String uiBaseUrl,
2022
@Valid @NotNull Auth auth,
21-
@Valid @NotNull SessionCookie sessionCookie
23+
@Valid @NotNull SessionCookie sessionCookie,
24+
@Valid @NotNull Cors cors
2225
) {
2326
@Validated
2427
public record Auth(
@@ -43,4 +46,12 @@ public record SessionCookie(
4346
@NotNull(message = "Secure flag must not be null") Boolean secure,
4447
@NotNull(message = "HttpOnly flag must not be null") Boolean httpOnly
4548
) {}
49+
50+
@Validated
51+
public record Cors(
52+
@NotEmpty(message = "Allowed origins must not be empty") List<String> allowedOrigins,
53+
@NotEmpty(message = "Allowed methods must not be empty") List<String> allowedMethods,
54+
@NotEmpty(message = "Allowed headers must not be empty") List<String> allowedHeaders,
55+
@NotNull(message = "Allow credentials must not be null") Boolean allowCredentials
56+
) {}
4657
}

0 commit comments

Comments
 (0)