diff --git a/docker-compose.yml b/docker-compose.yml index 8bc2a7ad..f341aded 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,9 @@ services: - dev - backend server: + build: + context: ./server + dockerfile: ../docker/server/Dockerfile image: ghcr.io/r-sandor/findfirst-server:latest ports: - "9000:9000" diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 8776bdbd..ec6addf8 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -1,8 +1,8 @@ #syntax=docker/dockerfile:1.7-labs -FROM gradle:jdk21-alpine AS builder +FROM gradle:8.9-jdk21-alpine AS builder WORKDIR /app COPY --exclude=build/ . . -RUN ./gradlew assemble +RUN gradle assemble --no-daemon FROM openjdk:26-ea-slim AS runner WORKDIR /app diff --git a/server/src/main/java/dev/findfirst/FindFirstApplication.java b/server/src/main/java/dev/findfirst/FindFirstApplication.java index ba0d3c1e..5f1427c6 100644 --- a/server/src/main/java/dev/findfirst/FindFirstApplication.java +++ b/server/src/main/java/dev/findfirst/FindFirstApplication.java @@ -47,8 +47,9 @@ public FilterRegistrationBean simpleCorsFilter() { config.setAllowCredentials(true); // *** URL below needs to match the Vue client URL and port *** // Local host and 127.0.0.1 are the same - config.setAllowedOrigins(Arrays.asList("https://localhost:3000", "http://localhost:3000", - "https://findfirst.dev", "http://localhost", "http://127.0.0.1")); + config.setAllowedOriginPatterns(Arrays.asList("https://localhost:3000", "http://localhost:3000", + "https://findfirst.dev", "http://localhost", "http://127.0.0.1", + "chrome-extension://*", "moz-extension://*")); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowedHeaders(Collections.singletonList("*")); source.registerCorsConfiguration("/**", config); diff --git a/server/src/main/java/dev/findfirst/security/config/SecSecurityConfig.java b/server/src/main/java/dev/findfirst/security/config/SecSecurityConfig.java index 8e4d5914..832c17b7 100644 --- a/server/src/main/java/dev/findfirst/security/config/SecSecurityConfig.java +++ b/server/src/main/java/dev/findfirst/security/config/SecSecurityConfig.java @@ -2,12 +2,15 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.Collections; import dev.findfirst.security.conditions.OAuthClientsCondition; import dev.findfirst.security.filters.CookieAuthenticationFilter; import dev.findfirst.security.jwt.AuthEntryPointJwt; +import dev.findfirst.security.jwt.UserAuthenticationToken; import dev.findfirst.security.oauth2client.handlers.Oauth2LoginSuccessHandler; import dev.findfirst.security.userauth.service.UserDetailsServiceImpl; +import dev.findfirst.security.userauth.utils.Constants; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; @@ -29,6 +32,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtDecoder; @@ -102,7 +106,15 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http.csrf(csrf -> csrf.disable()) .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .oauth2ResourceServer(rs -> rs.jwt(jwt -> jwt.decoder(jwtDecoder()))); + .oauth2ResourceServer(rs -> rs.jwt(jwt -> jwt.decoder(jwtDecoder()) + .jwtAuthenticationConverter(token -> { + Number userId = token.getClaim(Constants.USER_ID_CLAIM); + Number roleId = token.getClaim(Constants.ROLE_ID_CLAIM); + String roleName = token.getClaim(Constants.ROLE_NAME_CLAIM); + return new UserAuthenticationToken(token.getSubject(), roleId.intValue(), + Collections.singletonList(new SimpleGrantedAuthority(roleName)), + userId.intValue()); + }))); http.httpBasic( httpBasicCustomizer -> httpBasicCustomizer.authenticationEntryPoint(unauthorizedHandler)) diff --git a/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java b/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java index 84ca5c2c..e21cabae 100644 --- a/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java +++ b/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java @@ -2,6 +2,7 @@ import dev.findfirst.security.jwt.UserAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -9,7 +10,10 @@ public class UserContext { public int getUserId() { - return ((UserAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()) - .getUserId(); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth instanceof UserAuthenticationToken uat) { + return uat.getUserId(); + } + throw new IllegalStateException("Unexpected authentication type: " + auth.getClass()); } } diff --git a/server/src/main/java/dev/findfirst/security/userauth/models/TokenRefreshResponse.java b/server/src/main/java/dev/findfirst/security/userauth/models/TokenRefreshResponse.java index 320d4cd4..e4f92397 100644 --- a/server/src/main/java/dev/findfirst/security/userauth/models/TokenRefreshResponse.java +++ b/server/src/main/java/dev/findfirst/security/userauth/models/TokenRefreshResponse.java @@ -1,7 +1,7 @@ package dev.findfirst.security.userauth.models; -public record TokenRefreshResponse(String tokenType, String refreshToken, String error) { - public TokenRefreshResponse(String refreshToken) { - this("Bearer", refreshToken, null); +public record TokenRefreshResponse(String tokenType, String accessToken, String refreshToken, String error) { + public TokenRefreshResponse(String accessToken, String refreshToken) { + this("Bearer", accessToken, refreshToken, null); } } diff --git a/server/src/main/java/dev/findfirst/users/controller/UserController.java b/server/src/main/java/dev/findfirst/users/controller/UserController.java index 7995c7af..82ccc2c5 100644 --- a/server/src/main/java/dev/findfirst/users/controller/UserController.java +++ b/server/src/main/java/dev/findfirst/users/controller/UserController.java @@ -173,14 +173,14 @@ public ResponseEntity token( log.debug("User Signing in"); tkns = userService.signinUser(authorization); } catch (NoUserFoundException e) { - return ResponseEntity.badRequest().body(new TokenRefreshResponse(null, null, e.toString())); + return ResponseEntity.badRequest().body(new TokenRefreshResponse(null, null, null, e.toString())); } ResponseCookie cookie = ResponseCookie.from("findfirst", tkns.jwt()).secure(secure).path("/") .domain(domain).httpOnly(true).build(); return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) - .body(new TokenRefreshResponse(tkns.refreshToken())); + .body(new TokenRefreshResponse(tkns.jwt(), tkns.refreshToken())); } @PostMapping("/refreshToken")