From 608773a2a89d573e2e42305127a19a2d83abd3d3 Mon Sep 17 00:00:00 2001 From: 5Amogh Date: Mon, 17 Nov 2025 17:10:20 +0530 Subject: [PATCH 1/4] fix: amm-1927 send headers only if the request is from the allowed origin --- .../com/iemr/admin/config/CorsConfig.java | 5 +- .../utils/JwtUserIdValidationFilter.java | 59 +++++++++++++++---- .../utils/http/HTTPRequestInterceptor.java | 28 ++++++++- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/iemr/admin/config/CorsConfig.java b/src/main/java/com/iemr/admin/config/CorsConfig.java index f43d8ef..c98af1c 100644 --- a/src/main/java/com/iemr/admin/config/CorsConfig.java +++ b/src/main/java/com/iemr/admin/config/CorsConfig.java @@ -19,8 +19,9 @@ public void addCorsMappings(CorsRegistry registry) { Arrays.stream(allowedOrigins.split(",")) .map(String::trim) .toArray(String[]::new)) - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") + .allowedHeaders("Authorization", "Content-Type", "Accept", "Jwttoken", + "serverAuthorization", "ServerAuthorization", "serverauthorization", "Serverauthorization") .exposedHeaders("Authorization", "Jwttoken") .allowCredentials(true) .maxAge(3600); diff --git a/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java index 64c89a9..e825036 100644 --- a/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java @@ -37,23 +37,61 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletResponse response = (HttpServletResponse) servletResponse; String origin = request.getHeader("Origin"); + String method = request.getMethod(); + String uri = request.getRequestURI(); + + logger.debug("Incoming Origin: {}", origin); + logger.debug("Request Method: {}", method); + logger.debug("Request URI: {}", uri); + logger.debug("Allowed Origins Configured: {}", allowedOrigins); + + if ("OPTIONS".equalsIgnoreCase(method)) { + if (origin == null) { + logger.warn("BLOCKED - OPTIONS request without Origin header | Method: {} | URI: {}", method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "OPTIONS request requires Origin header"); + return; + } + if (!isOriginAllowed(origin)) { + logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed"); + return; + } + } else { + // For non-OPTIONS requests, validate origin if present + if (origin != null && !isOriginAllowed(origin)) { + logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed"); + return; + } + } + + // Determine request path/context for later checks + String path = request.getRequestURI(); + String contextPath = request.getContextPath(); + if (origin != null && isOriginAllowed(origin)) { - response.setHeader("Access-Control-Allow-Origin", origin); - response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken"); + response.setHeader("Access-Control-Allow-Origin", origin); // Never use wildcard + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Max-Age", "3600"); + logger.info("Origin Validated | Origin: {} | Method: {} | URI: {}", origin, method, uri); } else { logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); } - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { - logger.info("OPTIONS request - skipping JWT validation"); + if ("OPTIONS".equalsIgnoreCase(method)) { + // OPTIONS (preflight) - respond with full allowed methods + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); + response.setHeader("Access-Control-Allow-Credentials", "true"); response.setStatus(HttpServletResponse.SC_OK); return; } - String path = request.getRequestURI(); - String contextPath = request.getContextPath(); logger.info("JwtUserIdValidationFilter invoked for path: " + path); // Log cookies for debugging @@ -70,7 +108,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } // Log headers for debugging - logger.info("JWT token from header: "); + logger.debug("JWT token from header: {}", request.getHeader("Jwttoken") != null ? "present" : "not present"); // Skip login and public endpoints if (path.equals(contextPath + "/user/userAuthenticate") @@ -142,9 +180,8 @@ private boolean isOriginAllowed(String origin) { .anyMatch(pattern -> { String regex = pattern .replace(".", "\\.") - .replace("*", ".*") - .replace("http://localhost:.*", "http://localhost:\\d+"); // special case for wildcard port - + .replace("*", ".*"); + boolean matched = origin.matches(regex); return matched; }); diff --git a/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java b/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java index 665f156..d76dc48 100644 --- a/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java +++ b/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java @@ -22,11 +22,14 @@ package com.iemr.admin.utils.http; +import java.util.Arrays; + import javax.ws.rs.core.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; @@ -41,6 +44,8 @@ @Component public class HTTPRequestInterceptor implements HandlerInterceptor { Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + @Value("${cors.allowed-origins}") + private String allowedOrigins; @Autowired private RedisStorage redisStorage; @Autowired @@ -104,7 +109,13 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons response.getOutputStream().print(output.toString()); response.setContentType(MediaType.APPLICATION_JSON); response.setContentLength(output.toString().length()); - response.setHeader("Access-Control-Allow-Origin", "*"); + String origin = request.getHeader("Origin"); + if (origin != null && isOriginAllowed(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else if (origin != null) { + logger.warn("CORS headers NOT added for error response | Unauthorized origin: {}", origin); + } status = false; } } @@ -138,4 +149,19 @@ public void afterCompletion(HttpServletRequest request, HttpServletResponse resp logger.info("http interceptor - after completion"); } + + private boolean isOriginAllowed(String origin) { + if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { + return false; + } + + return Arrays.stream(allowedOrigins.split(",")) + .map(String::trim) + .anyMatch(pattern -> { + String regex = pattern + .replace(".", "\\.") + .replace("*", ".*"); + return origin.matches(regex); + }); + } } From 5a7c6bf9c39c770651169b117ad0e30afd7e9db0 Mon Sep 17 00:00:00 2001 From: 5Amogh Date: Mon, 17 Nov 2025 17:24:07 +0530 Subject: [PATCH 2/4] fix: amm-1927 coderabbit fixes --- .../utils/JwtUserIdValidationFilter.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java index e825036..1c7fd8f 100644 --- a/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java @@ -69,27 +69,23 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo String path = request.getRequestURI(); String contextPath = request.getContextPath(); + // Set CORS headers and handle OPTIONS request only if origin is valid and allowed if (origin != null && isOriginAllowed(origin)) { - response.setHeader("Access-Control-Allow-Origin", origin); // Never use wildcard - response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", - "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); - response.setHeader("Access-Control-Allow-Credentials", "true"); - response.setHeader("Access-Control-Max-Age", "3600"); + addCorsHeaders(response, origin); logger.info("Origin Validated | Origin: {} | Method: {} | URI: {}", origin, method, uri); + + if ("OPTIONS".equalsIgnoreCase(method)) { + // OPTIONS (preflight) - respond with full allowed methods + response.setStatus(HttpServletResponse.SC_OK); + return; + } } else { logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); - } - - if ("OPTIONS".equalsIgnoreCase(method)) { - // OPTIONS (preflight) - respond with full allowed methods - response.setHeader("Access-Control-Allow-Origin", origin); - response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", - "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); - response.setHeader("Access-Control-Allow-Credentials", "true"); - response.setStatus(HttpServletResponse.SC_OK); - return; + + if ("OPTIONS".equalsIgnoreCase(method)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed for OPTIONS request"); + return; + } } logger.info("JwtUserIdValidationFilter invoked for path: " + path); @@ -169,6 +165,15 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } } + private void addCorsHeaders(HttpServletResponse response, String origin) { + response.setHeader("Access-Control-Allow-Origin", origin); // Never use wildcard + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Max-Age", "3600"); + } + private boolean isOriginAllowed(String origin) { if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { logger.warn("No allowed origins configured or origin is null"); @@ -185,9 +190,7 @@ private boolean isOriginAllowed(String origin) { boolean matched = origin.matches(regex); return matched; }); - } - - private boolean isMobileClient(String userAgent) { + } private boolean isMobileClient(String userAgent) { if (userAgent == null) return false; userAgent = userAgent.toLowerCase(); From ad6ff5eb3dd84de8a296752df7438580307419a8 Mon Sep 17 00:00:00 2001 From: Amoghavarsh <93114621+5Amogh@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:07:46 +0530 Subject: [PATCH 3/4] Update regex handling for localhost URLs --- .../java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java b/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java index d76dc48..8c0fa2e 100644 --- a/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java +++ b/src/main/java/com/iemr/admin/utils/http/HTTPRequestInterceptor.java @@ -160,7 +160,8 @@ private boolean isOriginAllowed(String origin) { .anyMatch(pattern -> { String regex = pattern .replace(".", "\\.") - .replace("*", ".*"); + .replace("*", ".*") + .replace("http://localhost:.*", "http://localhost:\\d+"); return origin.matches(regex); }); } From eb98a5a25e3f2d4075c26f272339e5973bc09943 Mon Sep 17 00:00:00 2001 From: Amoghavarsh <93114621+5Amogh@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:08:27 +0530 Subject: [PATCH 4/4] Enhance regex pattern for URL matching --- .../java/com/iemr/admin/utils/JwtUserIdValidationFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java index 1c7fd8f..1b75a6d 100644 --- a/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java @@ -185,7 +185,8 @@ private boolean isOriginAllowed(String origin) { .anyMatch(pattern -> { String regex = pattern .replace(".", "\\.") - .replace("*", ".*"); + .replace("*", ".*") + .replace("http://localhost:.*", "http://localhost:\\d+"); boolean matched = origin.matches(regex); return matched;