From 5208d695137ceb76d8bff4581f5606adfc5b049a Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Wed, 29 Oct 2025 17:51:24 +0000 Subject: [PATCH] whitelisted another endpoint --- .../configuration/SecurityConfiguration.java | 69 +++++++++++++++++++ .../filter/JwtAuthenticationFilter.java | 2 + frontend/src/core/hooks/useEndpointConfig.ts | 6 +- 3 files changed, 74 insertions(+), 3 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 4feb5effd..6b4256c83 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 @@ -1,5 +1,6 @@ package stirling.software.proprietary.security.configuration; +import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +29,9 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import lombok.extern.slf4j.Slf4j; @@ -119,8 +123,71 @@ public class SecurityConfiguration { return new BCryptPasswordEncoder(); } + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration cfg = new CorsConfiguration(); + + // Set allowed origin patterns + if (appConfig.v2Enabled()) { + // Development mode - allow common development ports + cfg.setAllowedOriginPatterns( + List.of( + "http://localhost:3000", // Common React dev server + "http://localhost:5173", // Vite default port + "http://localhost:5174", // Vite alternate port + "http://localhost:8080", // Backend port + "http://localhost:*", // Any localhost port + "https://localhost:*" // HTTPS localhost + )); + log.info("CORS configured for development mode (v2 enabled)"); + } else { + // Production mode - be more restrictive + // You should configure production domains here + cfg.setAllowedOriginPatterns( + List.of( + "http://localhost:*", // Still allow localhost for local deployments + "https://localhost:*" + // Add your production domains here when deploying, e.g.: + // "https://yourdomain.com", + // "https://*.yourdomain.com" + )); + log.info("CORS configured for production mode"); + } + + // Set allowed methods explicitly + 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")); + + // 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; + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // Enable CORS with our custom configuration + http.cors(cors -> cors.configurationSource(corsConfigurationSource())); + if (securityProperties.getCsrfDisabled() || !loginEnabledValue) { http.csrf(CsrfConfigurer::disable); } @@ -274,6 +341,8 @@ public class SecurityConfiguration { "/api/v1/auth/login") || trimmedUri.startsWith( "/api/v1/auth/refresh") + || trimmedUri.startsWith( + "/api/v1/proprietary/ui-data/account") || trimmedUri.startsWith("/v1/api-docs") || uri.contains("/v1/api-docs"); }) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java index 5ec762fd8..e21b87182 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java @@ -144,6 +144,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { || requestURI.startsWith(contextPath + "/api/v1/auth/login") || requestURI.startsWith(contextPath + "/api/v1/auth/register") || requestURI.startsWith(contextPath + "/api/v1/auth/refresh") + || requestURI.startsWith( + contextPath + "/api/v1/proprietary/ui-data/account") || requestURI.startsWith(contextPath + "/api/v1/config"); return isPublicAuthEndpoint; } diff --git a/frontend/src/core/hooks/useEndpointConfig.ts b/frontend/src/core/hooks/useEndpointConfig.ts index 42c20600c..2edf45f32 100644 --- a/frontend/src/core/hooks/useEndpointConfig.ts +++ b/frontend/src/core/hooks/useEndpointConfig.ts @@ -20,7 +20,7 @@ export function useEndpointEnabled(endpoint: string): { const [enabled, setEnabled] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const headers = useRequestHeaders(); + const _headers = useRequestHeaders(); const fetchEndpointStatus = async () => { if (!endpoint) { @@ -76,8 +76,8 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): { const [endpointStatus, setEndpointStatus] = useState>({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [lastFetchedEndpoints, setLastFetchedEndpoints] = useState(''); - const headers = useRequestHeaders(); + const [_lastFetchedEndpoints, setLastFetchedEndpoints] = useState(''); + const _headers = useRequestHeaders(); const fetchAllEndpointStatuses = async () => { const endpointsKey = endpoints.join(',');