From d0a1adef8f1d7d8aabe3fc572a8f83c85c0cd905 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 16 Nov 2025 20:27:09 +0000 Subject: [PATCH] csrf --- .../configuration/SecurityConfiguration.java | 90 +++++++++---------- .../src/proprietary/auth/springAuthClient.ts | 6 +- .../proprietary/services/apiClientSetup.ts | 21 +++++ 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index 76c8dec306..bd6acc1b96 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -129,61 +129,53 @@ public class SecurityConfiguration { @Bean public CorsConfigurationSource corsConfigurationSource() { - // Read CORS allowed origins from settings - if (applicationProperties.getSystem() != null - && applicationProperties.getSystem().getCorsAllowedOrigins() != null - && !applicationProperties.getSystem().getCorsAllowedOrigins().isEmpty()) { + List configuredOrigins = null; + if (applicationProperties.getSystem() != null) { + configuredOrigins = applicationProperties.getSystem().getCorsAllowedOrigins(); + } - List allowedOrigins = applicationProperties.getSystem().getCorsAllowedOrigins(); - - CorsConfiguration cfg = new CorsConfiguration(); - - // Use setAllowedOriginPatterns for better wildcard and port support - cfg.setAllowedOriginPatterns(allowedOrigins); + CorsConfiguration cfg = new CorsConfiguration(); + if (configuredOrigins != null && !configuredOrigins.isEmpty()) { + cfg.setAllowedOriginPatterns(configuredOrigins); log.debug( "CORS configured with allowed origin patterns from settings.yml: {}", - allowedOrigins); - - // Set allowed methods explicitly (including OPTIONS for preflight) - cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); - - // Set allowed headers explicitly - cfg.setAllowedHeaders( - List.of( - "Authorization", - "Content-Type", - "X-Requested-With", - "Accept", - "Origin", - "X-API-KEY", - "X-CSRF-TOKEN")); - - // Set exposed headers (headers that the browser can access) - cfg.setExposedHeaders( - List.of( - "WWW-Authenticate", - "X-Total-Count", - "X-Page-Number", - "X-Page-Size", - "Content-Disposition", - "Content-Type")); - - // Allow credentials (cookies, authorization headers) - cfg.setAllowCredentials(true); - - // Set max age for preflight cache - cfg.setMaxAge(3600L); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", cfg); - return source; + configuredOrigins); } else { - // No CORS origins configured - return null to disable CORS processing entirely - // This avoids empty CORS policy that unexpectedly rejects preflights + // Default to allowing all origins when nothing is configured + cfg.setAllowedOriginPatterns(List.of("*")); log.info( - "CORS is disabled - no allowed origins configured in settings.yml (system.corsAllowedOrigins)"); - return null; + "No CORS allowed origins configured in settings.yml (system.corsAllowedOrigins); allowing all origins."); } + + // Explicitly configure supported HTTP methods (include OPTIONS for preflight) + cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); + + cfg.setAllowedHeaders( + List.of( + "Authorization", + "Content-Type", + "X-Requested-With", + "Accept", + "Origin", + "X-API-KEY", + "X-CSRF-TOKEN", + "X-XSRF-TOKEN")); + + cfg.setExposedHeaders( + List.of( + "WWW-Authenticate", + "X-Total-Count", + "X-Page-Number", + "X-Page-Size", + "Content-Disposition", + "Content-Type")); + + cfg.setAllowCredentials(true); + cfg.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", cfg); + return source; } @Bean diff --git a/frontend/src/proprietary/auth/springAuthClient.ts b/frontend/src/proprietary/auth/springAuthClient.ts index fee23c5798..ac997e296a 100644 --- a/frontend/src/proprietary/auth/springAuthClient.ts +++ b/frontend/src/proprietary/auth/springAuthClient.ts @@ -107,7 +107,7 @@ class SpringAuthClient { for (const cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'XSRF-TOKEN') { - return value; + return decodeURIComponent(value); } } return null; @@ -278,7 +278,7 @@ class SpringAuthClient { try { const response = await apiClient.post('/api/v1/auth/logout', null, { headers: { - 'X-CSRF-TOKEN': this.getCsrfToken() || '', + 'X-XSRF-TOKEN': this.getCsrfToken() || '', }, withCredentials: true, }); @@ -311,7 +311,7 @@ class SpringAuthClient { try { const response = await apiClient.post('/api/v1/auth/refresh', null, { headers: { - 'X-CSRF-TOKEN': this.getCsrfToken() || '', + 'X-XSRF-TOKEN': this.getCsrfToken() || '', }, withCredentials: true, }); diff --git a/frontend/src/proprietary/services/apiClientSetup.ts b/frontend/src/proprietary/services/apiClientSetup.ts index be51b165c8..9b1ed75bd6 100644 --- a/frontend/src/proprietary/services/apiClientSetup.ts +++ b/frontend/src/proprietary/services/apiClientSetup.ts @@ -9,17 +9,38 @@ function getJwtTokenFromStorage(): string | null { } } +function getXsrfToken(): string | null { + try { + const cookies = document.cookie.split(';'); + for (const cookie of cookies) { + const [name, value] = cookie.trim().split('='); + if (name === 'XSRF-TOKEN') { + return decodeURIComponent(value); + } + } + return null; + } catch (error) { + console.error('[API Client] Failed to read XSRF token from cookies:', error); + return null; + } +} + export function setupApiInterceptors(client: AxiosInstance): void { // Install request interceptor to add JWT token client.interceptors.request.use( (config) => { const jwtToken = getJwtTokenFromStorage(); + const xsrfToken = getXsrfToken(); if (jwtToken && !config.headers.Authorization) { config.headers.Authorization = `Bearer ${jwtToken}`; console.debug('[API Client] Added JWT token from localStorage to Authorization header'); } + if (xsrfToken && !config.headers['X-XSRF-TOKEN']) { + config.headers['X-XSRF-TOKEN'] = xsrfToken; + } + return config; }, (error) => {