-
Notifications
You must be signed in to change notification settings - Fork 0
feat(quiz-service): pipeline complet de gamification (RabbitMQ) #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d971032
2df2e80
88fa089
021c7a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import 'package:dio/dio.dart'; | ||
| import 'package:flutter_secure_storage/flutter_secure_storage.dart'; | ||
|
|
||
| /// Attaches the stored access token to every outgoing request. Without this, | ||
| /// every call is rejected with 401 by api-gateway's JwtAuthenticationFilter | ||
| /// (except the public /api/auth/** routes). | ||
| class AuthInterceptor extends Interceptor { | ||
| final FlutterSecureStorage _storage; | ||
|
|
||
| AuthInterceptor(this._storage); | ||
|
|
||
| @override | ||
| void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { | ||
| final token = await _storage.read(key: 'access_token'); | ||
| if (token != null) { | ||
| options.headers['Authorization'] = 'Bearer $token'; | ||
| } | ||
| handler.next(options); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ import '../../../core/network/api_client.dart'; | |
| import '../data/auth_repository.dart'; | ||
| import '../domain/auth_models.dart'; | ||
|
|
||
| final apiClientProvider = Provider((ref) => ApiClient()); | ||
| final apiClientProvider = Provider((ref) => ApiClient(ref.watch(secureStorageProvider))); | ||
|
|
||
| final authRepositoryProvider = Provider((ref) { | ||
| return AuthRepository(ref.watch(apiClientProvider)); | ||
|
|
@@ -21,20 +21,46 @@ class AuthController extends StateNotifier<AuthResponse?> { | |
|
|
||
| Future<AuthResponse> login(String email, String password) async { | ||
| final result = await _repository.login(email: email, password: password); | ||
| await _storage.write(key: 'access_token', value: result.accessToken); | ||
| await _storage.write(key: 'refresh_token', value: result.refreshToken); | ||
| await _storage.write(key: 'account_id', value: result.id); | ||
| await _persistSession(result); | ||
| state = result; | ||
| return result; | ||
| } | ||
|
|
||
| Future<AuthResponse> register(String email, String password) async { | ||
| final result = await _repository.register(email: email, password: password); | ||
| await _persistSession(result); | ||
| state = result; | ||
| return result; | ||
| } | ||
|
|
||
| Future<void> _persistSession(AuthResponse result) async { | ||
| await _storage.write(key: 'access_token', value: result.accessToken); | ||
| await _storage.write(key: 'refresh_token', value: result.refreshToken); | ||
| await _storage.write(key: 'account_id', value: result.id); | ||
| state = result; | ||
| return result; | ||
| await _storage.write(key: 'email', value: result.email); | ||
| await _storage.write(key: 'role', value: result.role); | ||
| } | ||
|
|
||
| /// Reconstructs the session from secure storage on app startup (page | ||
| /// reload, direct URL navigation, hot restart). Without this, the | ||
| /// in-memory-only state means every reload silently logs the user out | ||
| /// even though valid tokens are still on disk. | ||
| Future<void> restoreSession() async { | ||
| final accessToken = await _storage.read(key: 'access_token'); | ||
| final refreshToken = await _storage.read(key: 'refresh_token'); | ||
| final accountId = await _storage.read(key: 'account_id'); | ||
| final email = await _storage.read(key: 'email'); | ||
| final role = await _storage.read(key: 'role'); | ||
|
|
||
| if (accessToken != null && refreshToken != null && accountId != null && email != null && role != null) { | ||
| state = AuthResponse( | ||
| id: accountId, | ||
| accessToken: accessToken, | ||
| refreshToken: refreshToken, | ||
| email: email, | ||
| role: role, | ||
| ); | ||
| } | ||
|
Comment on lines
+44
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
git ls-files 'mobile/lib/features/auth/presentation/auth_providers.dart' 'mobile/lib/main.dart'
printf '\n== outline auth_providers ==\n'
ast-grep outline mobile/lib/features/auth/presentation/auth_providers.dart --view expanded || true
printf '\n== outline main ==\n'
ast-grep outline mobile/lib/main.dart --view expanded || true
printf '\n== relevant lines auth_providers ==\n'
sed -n '1,220p' mobile/lib/features/auth/presentation/auth_providers.dart | cat -n
printf '\n== relevant lines main ==\n'
sed -n '1,160p' mobile/lib/main.dart | cat -nRepository: ASSONDJI/afrilingua Length of output: 5898 Guard 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| Future<void> logout() async { | ||
|
|
@@ -50,4 +76,10 @@ final authControllerProvider = StateNotifierProvider<AuthController, AuthRespons | |
| /// Convenience accessor: the current session's accountId, or null if logged out. | ||
| final accountIdProvider = Provider<String?>((ref) { | ||
| return ref.watch(authControllerProvider)?.id; | ||
| }); | ||
| }); | ||
| /// Gate used at app startup: the app waits for this to complete before | ||
| /// building any real screen, so no route ever runs with a stale/null | ||
| /// session while restoreSession() is still in flight. | ||
| final sessionRestorationProvider = FutureProvider<void>((ref) async { | ||
| await ref.read(authControllerProvider.notifier).restoreSession(); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| <properties> | ||
| <java.version>21</java.version> | ||
| <spring-cloud.version>2023.0.3</spring-cloud.version> | ||
| <jjwt.version>0.12.6</jjwt.version> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check Maven Central for the latest JJWT 0.12.x release
curl -s "https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt-api/maven-metadata.xml" | grep -oP '<version>0\.12\.\d+</version>' | sort -V | tail -5
# Check GitHub advisories for JJWT
gh api graphql -f query='
{
securityVulnerabilities(first: 5, ecosystem: MAVEN, package: "io.jsonwebtoken:jjwt-api") {
nodes {
advisory { summary severity }
vulnerableVersionRange
firstPatchedVersion { identifier }
}
}
}'Repository: ASSONDJI/afrilingua Length of output: 336 🏁 Script executed: #!/bin/bash
set -euo pipefail
for artifact in jjwt-api jjwt-impl jjwt-jackson; do
echo "== $artifact =="
curl -fsS "https://repo1.maven.org/maven2/io/jsonwebtoken/${artifact}/maven-metadata.xml" \
| grep -oP '<version>0\.12\.\d+</version>' \
| sort -V \
| tail -5
doneRepository: ASSONDJI/afrilingua Length of output: 597 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,120p' services/api-gateway/pom.xml | cat -nRepository: ASSONDJI/afrilingua Length of output: 3634 Bump JJWT to 0.12.7 — 🤖 Prompt for AI Agents |
||
| </properties> | ||
|
|
||
| <dependencies> | ||
|
|
@@ -35,6 +36,23 @@ | |
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-actuator</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.jsonwebtoken</groupId> | ||
| <artifactId>jjwt-api</artifactId> | ||
| <version>${jjwt.version}</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.jsonwebtoken</groupId> | ||
| <artifactId>jjwt-impl</artifactId> | ||
| <version>${jjwt.version}</version> | ||
| <scope>runtime</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.jsonwebtoken</groupId> | ||
| <artifactId>jjwt-jackson</artifactId> | ||
| <version>${jjwt.version}</version> | ||
| <scope>runtime</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-test</artifactId> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package cm.afrilingua.gateway.config; | ||
|
|
||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
|
||
| /** Mirrors auth-service's JwtProperties -- must share the same secret-key so | ||
| * tokens signed by auth-service can be verified here. */ | ||
| @ConfigurationProperties(prefix = "application.security.jwt") | ||
| public record JwtProperties(String secretKey) {} |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,100 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package cm.afrilingua.gateway.security; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import cm.afrilingua.gateway.config.JwtProperties; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.jsonwebtoken.Claims; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.jsonwebtoken.JwtException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.jsonwebtoken.Jwts; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.jsonwebtoken.security.Keys; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.cloud.gateway.filter.GatewayFilterChain; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.cloud.gateway.filter.GlobalFilter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.core.Ordered; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.server.reactive.ServerHttpRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.server.reactive.ServerHttpResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.server.ServerWebExchange; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import reactor.core.publisher.Mono; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.crypto.SecretKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Validates the JWT on every request except the public auth routes, then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * strips the client-supplied Authorization header and replaces it with a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * trusted X-User-Id header before forwarding downstream. Internal services | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * never need to know about JWT at all -- they just trust X-User-Id, because | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * this filter is the only entry point into the system (services are not | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * exposed directly, only through this gateway). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * This is the single point of authentication for the whole platform: no | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * other service validates tokens. Keeping it here (rather than duplicating | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * JWT validation in every microservice) is what makes X-User-Id trustworthy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * downstream -- as long as nothing bypasses the gateway in production. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class JwtAuthenticationFilter implements GlobalFilter, Ordered { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final List<String> PUBLIC_PATH_PREFIXES = List.of("/api/auth/"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final SecretKey signingKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public JwtAuthenticationFilter(JwtProperties jwtProperties) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.signingKey = Keys.hmacShaKeyFor(jwtProperties.secretKey().getBytes(StandardCharsets.UTF_8)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String path = exchange.getRequest().getURI().getPath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isPublicPath(path)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chain.filter(exchange); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (authHeader == null || !authHeader.startsWith("Bearer ")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unauthorized(exchange, "Missing or malformed Authorization header"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String token = authHeader.substring("Bearer ".length()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Claims claims = Jwts.parser() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .verifyWith(signingKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .parseSignedClaims(token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .getPayload(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String userId = claims.getSubject(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ServerHttpRequest mutatedRequest = exchange.getRequest().mutate() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .headers(headers -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers.remove("Authorization"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers.remove("X-User-Id"); // never trust a client-supplied value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers.set("X-User-Id", userId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chain.filter(exchange.mutate().request(mutatedRequest).build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (JwtException | IllegalArgumentException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unauthorized(exchange, "Invalid or expired token"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Unvalidated null subject propagates into
🛡️ Suggested defensive check String userId = claims.getSubject();
+ if (userId == null || userId.isBlank()) {
+ return unauthorized(exchange, "Invalid or expired token");
+ }JWT parsing itself ( Note: the static analysis hint about a "randomly-generated IV" on line 91 is a false positive — that line only builds a JSON error string, no cryptographic/CBC operation is involved. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private boolean isPublicPath(String path) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return PUBLIC_PATH_PREFIXES.stream().anyMatch(path::startsWith); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Mono<Void> unauthorized(ServerWebExchange exchange, String message) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ServerHttpResponse response = exchange.getResponse(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response.setStatusCode(HttpStatus.UNAUTHORIZED); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response.getHeaders().add("Content-Type", "application/json"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] body = ("{\"message\":\"" + message + "\"}").getBytes(StandardCharsets.UTF_8); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.writeWith(Mono.just(response.bufferFactory().wrap(body))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public int getOrder() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ordered.HIGHEST_PRECEDENCE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -70,4 +70,8 @@ management: | |||||||||||||||||||||
| endpoints: | ||||||||||||||||||||||
| web: | ||||||||||||||||||||||
| exposure: | ||||||||||||||||||||||
| include: health,gateway | ||||||||||||||||||||||
| include: health,gateway | ||||||||||||||||||||||
| application: | ||||||||||||||||||||||
| security: | ||||||||||||||||||||||
| jwt: | ||||||||||||||||||||||
| secret-key: ${JWT_SECRET_KEY:404E635266556A586E3272357538782F413F4428472B4B6250645367566B59} | ||||||||||||||||||||||
|
Comment on lines
+73
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Hardcoded JWT secret default checked into source control.
Fail fast instead of silently falling back to a checked-in secret: 🔒 Suggested fix application:
security:
jwt:
- secret-key: ${JWT_SECRET_KEY:404E635266556A586E3272357538782F413F4428472B4B6250645367566B59}
+ secret-key: ${JWT_SECRET_KEY}Note: this must be coordinated with 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Unhandled storage read failure can hang requests indefinitely.
If
_storage.readthrows (platform exception, corrupted storage, permissions change),handler.next(options)is never called and the request stalls until the Dio timeout fires. Wrap the read in a try-catch so the request proceeds without a token — the gateway will return 401, which is the correct degraded behavior.🛡️ Proposed fix: add error handling around storage read
`@override` void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { - final token = await _storage.read(key: 'access_token'); - if (token != null) { - options.headers['Authorization'] = 'Bearer $token'; + try { + final token = await _storage.read(key: 'access_token'); + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + } catch (_) { + // If storage is unavailable, proceed without token; gateway will reject with 401. } handler.next(options); }📝 Committable suggestion
🤖 Prompt for AI Agents