diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f99697f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# --- Stage 1: Build the application using Maven --- +FROM maven:3.9.6-eclipse-temurin-17 AS build + +WORKDIR /app + +COPY . . + +# Build the application while caching Maven dependencies to speed up future builds +RUN --mount=type=cache,target=/root/.m2 \ + mvn clean package -DENV_VAR=docker -DskipTests -Dgit.skip=true + +# --- Stage 2: Run the application with a minimal JRE image --- +FROM eclipse-temurin:17-jre + +WORKDIR /app + +# Copy the built WAR file from the build stage +COPY --from=build /app/target/*.war app.war + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.war"] diff --git a/pom.xml b/pom.xml index ac63bb8..050c2aa 100644 --- a/pom.xml +++ b/pom.xml @@ -392,6 +392,18 @@ + + org.springframework.boot + spring-boot-maven-plugin + 3.2.2 + + + + repackage + + + + diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index f0ed942..24558d3 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -15,7 +15,7 @@ callcentre-server-ip=@env.CALLCENTRE_SERVER_IP@ common-url=@env.COMMON_API@ ### Redis IP -spring.redis.host=localhost +spring.redis.host=@env.REDIS_HOST@ jwt.secret=@env.JWT_SECRET_KEY@ #ELK logging file name @@ -25,4 +25,5 @@ logging.file.name=@env.SCHEDULER_API_LOGGING_FILE_NAME@ springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@ +cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties new file mode 100644 index 0000000..2a14eb1 --- /dev/null +++ b/src/main/environment/common_docker.properties @@ -0,0 +1,24 @@ +# local env +# DB Connections +spring.datasource.url=${DATABASE_URL} +spring.datasource.username=${DATABASE_USERNAME} +spring.datasource.password=${DATABASE_PASSWORD} +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.show-sql=true + +spring.profiles.active=test + +callcentre-server-ip=${CALLCENTRE_SERVER_IP} + +common-url=${COMMON_API} + +### Redis IP +spring.redis.host=${REDIS_HOST} +jwt.secret=${JWT_SECRET_KEY} + +#ELK logging file name +logging.file.name=${SCHEDULER_API_LOGGING_FILE_NAME} + +springdoc.api-docs.enabled=${SWAGGER_DOC_ENABLED} +springdoc.swagger-ui.enabled=${SWAGGER_DOC_ENABLED} diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index d6b4463..d074aa7 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -22,3 +22,5 @@ jwt.secret=my-32-character-ultra-secure-and-ultra-long-secret logging.path=logs/ logging.file.name=logs/scheduler-api.log +cors.allowed-origins=http://localhost:* + diff --git a/src/main/java/com/iemr/tm/controller/schedule/SchedulingController.java b/src/main/java/com/iemr/tm/controller/schedule/SchedulingController.java index 3cb6812..1af4dc1 100644 --- a/src/main/java/com/iemr/tm/controller/schedule/SchedulingController.java +++ b/src/main/java/com/iemr/tm/controller/schedule/SchedulingController.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; + import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -43,7 +43,6 @@ import io.swagger.v3.oas.annotations.Operation; - @RestController @RequestMapping(value = "/schedule", headers = "Authorization") public class SchedulingController { @@ -52,7 +51,6 @@ public class SchedulingController { @Autowired private SchedulingService schedulingService; - @CrossOrigin() @Operation(summary = "Mark availability of specialist") @RequestMapping(value = "markavailability", method = RequestMethod.POST) public String markavailability(@RequestBody String specialistInput1) { @@ -72,7 +70,6 @@ public String markavailability(@RequestBody String specialistInput1) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Mark unavailability of specialist") @RequestMapping(value = "unmarkavailability", method = RequestMethod.POST) public String unmarkavailability(@RequestBody String specialistInput1) { @@ -91,7 +88,6 @@ public String unmarkavailability(@RequestBody String specialistInput1) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Get available slots of specialist for a particular day") @RequestMapping(value = "getavailableSlot", method = RequestMethod.POST) public String getavailableSlot(@RequestBody String specialistInput1) { @@ -109,7 +105,6 @@ public String getavailableSlot(@RequestBody String specialistInput1) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Get available slots of specialist for a particular month") @RequestMapping(value = { "/monthview/{year}", "/monthview/{year}/{month}", "/monthview/{year}/{month}/{day}" }, method = RequestMethod.POST) @@ -131,7 +126,6 @@ public String view(@RequestBody String specialistInput1, @PathVariable("year") I return response.toString(); } - @CrossOrigin() @Operation(summary = "Book available slots of specialist of a particular day") @RequestMapping(value = "bookSlot", method = RequestMethod.POST) public String bookSlot(@RequestBody String specialistInput1) { @@ -149,7 +143,6 @@ public String bookSlot(@RequestBody String specialistInput1) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Cancel booked slots of specialist of a particular day") @RequestMapping(value = "cancelBookedSlot", method = RequestMethod.POST) public String cancelBookedSlot(@RequestBody String specialistInput1) { @@ -167,7 +160,6 @@ public String cancelBookedSlot(@RequestBody String specialistInput1) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Get day view of particular specialization") @RequestMapping(value = "getdayview", method = RequestMethod.POST) public String getdayview(@RequestBody String specialistInput1) { diff --git a/src/main/java/com/iemr/tm/controller/specialist/SpecialistController.java b/src/main/java/com/iemr/tm/controller/specialist/SpecialistController.java index c42e124..f890e1b 100644 --- a/src/main/java/com/iemr/tm/controller/specialist/SpecialistController.java +++ b/src/main/java/com/iemr/tm/controller/specialist/SpecialistController.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; + import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -41,7 +41,6 @@ import io.swagger.v3.oas.annotations.Operation; - @RestController @RequestMapping(value = "/specialist", headers = "Authorization") public class SpecialistController { @@ -50,7 +49,6 @@ public class SpecialistController { @Autowired private SpecialistService specialistService; - @CrossOrigin() @Operation(summary = "Fetch master specialization") @RequestMapping(value = "masterspecialization", method = RequestMethod.POST) public String markavailability() { @@ -66,7 +64,6 @@ public String markavailability() { return response.toString(); } - @CrossOrigin() @Operation(summary = "Fetch list of specialists") @RequestMapping(value = "getSpecialist", method = RequestMethod.POST) public String getSpecialist(@RequestBody Specialist specialist) { @@ -83,7 +80,6 @@ public String getSpecialist(@RequestBody Specialist specialist) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Fetch user specialist") @RequestMapping(value = "info/{userID}", method = RequestMethod.GET) public String info(@PathVariable("userID") Long userID) { @@ -101,7 +97,6 @@ public String info(@PathVariable("userID") Long userID) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Fetch all specialists") @RequestMapping(value = "getSpecialistAll", method = RequestMethod.POST) public String getSpecialistAll(@RequestBody Specialist specialist) { diff --git a/src/main/java/com/iemr/tm/controller/van/VanController.java b/src/main/java/com/iemr/tm/controller/van/VanController.java index 876f593..26fa53b 100644 --- a/src/main/java/com/iemr/tm/controller/van/VanController.java +++ b/src/main/java/com/iemr/tm/controller/van/VanController.java @@ -24,7 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; + import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -36,8 +36,6 @@ import io.swagger.v3.oas.annotations.Operation; - - @RestController @RequestMapping(value = "/van", headers = "Authorization") public class VanController { @@ -46,7 +44,6 @@ public class VanController { @Autowired private VanService vanService; - @CrossOrigin() @Operation(summary = "Fetch specialization by van id") @RequestMapping(value = "getvan/{vanid}", method = RequestMethod.GET) public String markavailability(@PathVariable("vanid") Integer vanid) { diff --git a/src/main/java/com/iemr/tm/utils/FilterConfig.java b/src/main/java/com/iemr/tm/utils/FilterConfig.java index 48aee27..b11a7ee 100644 --- a/src/main/java/com/iemr/tm/utils/FilterConfig.java +++ b/src/main/java/com/iemr/tm/utils/FilterConfig.java @@ -3,17 +3,26 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.beans.factory.annotation.Value; @Configuration public class FilterConfig { + @Value("${cors.allowed-origins}") + private String allowedOrigins; + @Bean public FilterRegistrationBean jwtUserIdValidationFilter( JwtAuthenticationUtil jwtAuthenticationUtil) { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - registrationBean.setFilter(new JwtUserIdValidationFilter(jwtAuthenticationUtil)); + + // Pass allowedOrigins explicitly to the filter constructor + JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins); + + registrationBean.setFilter(filter); + registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints return registrationBean; } - } diff --git a/src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java index 174ed73..3a2ef5e 100644 --- a/src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java @@ -1,6 +1,7 @@ package com.iemr.tm.utils; import java.io.IOException; +import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,14 +18,16 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -@Component public class JwtUserIdValidationFilter implements Filter { private final JwtAuthenticationUtil jwtAuthenticationUtil; private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + private final String allowedOrigins; - public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil) { + public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil, + String allowedOrigins) { this.jwtAuthenticationUtil = jwtAuthenticationUtil; + this.allowedOrigins = allowedOrigins; } @Override @@ -33,6 +36,27 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; + String origin = request.getHeader("Origin"); + + logger.debug("Incoming Origin: {}", origin); + logger.debug("Allowed Origins Configured: {}", allowedOrigins); + + 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("Vary", "Origin"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else { + logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); + } + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + logger.info("OPTIONS request - skipping JWT validation"); + response.setStatus(HttpServletResponse.SC_OK); + return; + } + String path = request.getRequestURI(); String contextPath = request.getContextPath(); logger.info("JwtUserIdValidationFilter invoked for path: " + path); @@ -110,12 +134,33 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization error: " + e.getMessage()); } } + + private boolean isOriginAllowed(String origin) { + if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { + logger.warn("No allowed origins configured or origin is null"); + return false; + } + + return Arrays.stream(allowedOrigins.split(",")) + .map(String::trim) + .anyMatch(pattern -> { + String regex = pattern + .replace(".", "\\.") + .replace("*", ".*") + .replace("http://localhost:.*", "http://localhost:\\d+"); // special case for wildcard port + + boolean matched = origin.matches(regex); + return matched; + }); + } + private boolean isMobileClient(String userAgent) { if (userAgent == null) return false; userAgent = userAgent.toLowerCase(); return userAgent.contains("okhttp"); } + private String getJwtTokenFromCookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) {