From ac97442312b0eb87d54ae63a7d654a5ba107361a Mon Sep 17 00:00:00 2001 From: Andreas Dahm Date: Wed, 29 Apr 2026 08:50:02 +0200 Subject: [PATCH] Added dev impersonation and changed api a little bit --- .../security/DevJwtAuthenticationFilter.java | 51 +++++++++++++++++++ .../security/JwtAuthenticationFilter.java | 4 +- .../configuration/security/JwtConfig.java | 2 + .../configuration/security/JwtService.java | 2 + .../security/SecurityConfig.java | 9 +++- .../dto/response/SteamOwnedGamesResponse.java | 14 +++-- .../service/SteamApiService.java | 32 +++++++++--- .../service/SteamApiServiceTest.java | 8 +-- 8 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 src/main/java/core_service/configuration/security/DevJwtAuthenticationFilter.java diff --git a/src/main/java/core_service/configuration/security/DevJwtAuthenticationFilter.java b/src/main/java/core_service/configuration/security/DevJwtAuthenticationFilter.java new file mode 100644 index 0000000..e16d804 --- /dev/null +++ b/src/main/java/core_service/configuration/security/DevJwtAuthenticationFilter.java @@ -0,0 +1,51 @@ +package core_service.configuration.security; + +import core_service.shared.security.UserPrincipal; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; + +/** + * Development-specific authentication filter that bypasses real JWT validation. + * It automatically injects a default user principal for every request when the 'dev' profile is active. + */ +@Component("jwtAuthenticationFilter") +@Profile("dev") +@Slf4j +public class DevJwtAuthenticationFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + + if (SecurityContextHolder.getContext().getAuthentication() == null) { + // Default user principal for dev mode: Using the requested Steam ID 76561198000000001 + UserPrincipal principal = new UserPrincipal("1", "dev@example.com", 76561198000000001L); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + principal, + null, + Collections.emptyList() + ); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + log.trace("Dev mode: Injected default user principal with SteamID: {}", principal.steamId()); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/core_service/configuration/security/JwtAuthenticationFilter.java b/src/main/java/core_service/configuration/security/JwtAuthenticationFilter.java index 11a15b8..1eeefdb 100644 --- a/src/main/java/core_service/configuration/security/JwtAuthenticationFilter.java +++ b/src/main/java/core_service/configuration/security/JwtAuthenticationFilter.java @@ -8,6 +8,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jspecify.annotations.NonNull; +import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -17,7 +18,8 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; -@Component +@Component("jwtAuthenticationFilter") +@Profile("!dev") @RequiredArgsConstructor @Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { diff --git a/src/main/java/core_service/configuration/security/JwtConfig.java b/src/main/java/core_service/configuration/security/JwtConfig.java index 62ccd8c..311f72a 100644 --- a/src/main/java/core_service/configuration/security/JwtConfig.java +++ b/src/main/java/core_service/configuration/security/JwtConfig.java @@ -4,10 +4,12 @@ import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; @Getter @Setter @Configuration +@Profile("!dev") @ConfigurationProperties(prefix = "security.jwt") public class JwtConfig { diff --git a/src/main/java/core_service/configuration/security/JwtService.java b/src/main/java/core_service/configuration/security/JwtService.java index 8bbc969..9046abd 100644 --- a/src/main/java/core_service/configuration/security/JwtService.java +++ b/src/main/java/core_service/configuration/security/JwtService.java @@ -2,12 +2,14 @@ package core_service.configuration.security; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; @Service +@Profile("!dev") public class JwtService { private final JwtConfig jwtConfig; diff --git a/src/main/java/core_service/configuration/security/SecurityConfig.java b/src/main/java/core_service/configuration/security/SecurityConfig.java index fc2ef3d..75ab400 100644 --- a/src/main/java/core_service/configuration/security/SecurityConfig.java +++ b/src/main/java/core_service/configuration/security/SecurityConfig.java @@ -1,6 +1,7 @@ package core_service.configuration.security; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,13 +15,13 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.OncePerRequestFilter; import java.util.Arrays; import java.util.List; @Configuration @EnableWebSecurity -@RequiredArgsConstructor public class SecurityConfig { private static final String[] WHITE_LIST_URLS = { @@ -31,7 +32,11 @@ public class SecurityConfig { "/api/steam/test/**" }; - private final JwtAuthenticationFilter jwtAuthFilter; + private final OncePerRequestFilter jwtAuthFilter; + + public SecurityConfig(@Qualifier("jwtAuthenticationFilter") OncePerRequestFilter jwtAuthFilter) { + this.jwtAuthFilter = jwtAuthFilter; + } @Value("${app.cors.allowed-origins}") private String[] allowedOrigins; diff --git a/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java b/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java index 68669ad..a4baf4a 100644 --- a/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java +++ b/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java @@ -1,18 +1,24 @@ package core_service.modules.steam_adapter.api.dto.response; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; -public record SteamOwnedGamesResponse(OwnedGamesResponse response) { +@JsonIgnoreProperties(ignoreUnknown = true) +public record SteamOwnedGamesResponse( + @JsonProperty("response") OwnedGamesResponse response +) { + @JsonIgnoreProperties(ignoreUnknown = true) public record OwnedGamesResponse( @JsonProperty("game_count") Integer gameCount, - List games + @JsonProperty("games") List games ) {} + @JsonIgnoreProperties(ignoreUnknown = true) public record Game( - Integer appid, - String name, + @JsonProperty("appid") Integer appid, + @JsonProperty("name") String name, @JsonProperty("playtime_forever") Integer playtimeForever, @JsonProperty("img_icon_url") String imgIconUrl, @JsonProperty("has_community_visible_stats") Boolean hasCommunityVisibleStats diff --git a/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java b/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java index 73f70ba..6b8e353 100644 --- a/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java +++ b/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java @@ -14,9 +14,9 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -@Slf4j @Service @RequiredArgsConstructor +@Slf4j public class SteamApiService { private final RestClient steamRestClient; @@ -26,24 +26,42 @@ public class SteamApiService { @Cacheable(value = "steamGameCount", key = "#steamId") public int getOwnedGamesCount(String steamId) { + if (apiKey == null || apiKey.isEmpty() || apiKey.equals("${STEAM_API_KEY}")) { + log.error("Steam API Key is not configured correctly!"); + return 0; + } + try { - log.info("Fetching owned games count for SteamID: {}", steamId); + log.info("Fetching owned games count for SteamID: {} using API Key starting with: {}", + steamId, apiKey.substring(0, Math.min(apiKey.length(), 4))); SteamOwnedGamesResponse response = steamRestClient.get() .uri(uriBuilder -> uriBuilder - .path("/IPlayerService/GetOwnedGames/v0001/") + .path("/IPlayerService/GetOwnedGames/v1/") .queryParam("key", apiKey) .queryParam("steamid", steamId) - .queryParam("include_appinfo", 0) - .queryParam("include_played_free_games", 1) + .queryParam("include_appinfo", "true") + .queryParam("include_played_free_games", "true") + .queryParam("format", "json") .build()) .retrieve() .body(SteamOwnedGamesResponse.class); - log.info("SteamOwnedGamesResponse: {}", response); + log.info("Steam API Response object for {}: {}", steamId, response); if (response != null && response.response() != null) { - return response.response().gameCount() != null ? response.response().gameCount() : 0; + Integer count = response.response().gameCount(); + if (count == null) { + if (response.response().games() != null) { + int size = response.response().games().size(); + log.info("game_count was null, but games list has {} entries for SteamID: {}", size, steamId); + return size; + } + log.warn("Steam API returned an empty response object for SteamID: {}. Check if the ID is correct and 'Game details' are Public.", steamId); + return 0; + } + log.info("Successfully fetched {} games for SteamID: {}", count, steamId); + return count; } } catch (Exception e) { diff --git a/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java b/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java index 2c674fb..589b413 100644 --- a/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java +++ b/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java @@ -43,7 +43,7 @@ class SteamApiServiceTest { } """; - server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/"))) .andExpect(queryParam("steamid", steamId)) .andExpect(queryParam("key", "test-api-key")) .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); @@ -102,7 +102,7 @@ class SteamApiServiceTest { } """; - server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/"))) .andExpect(queryParam("steamid", steamId)) .andExpect(queryParam("key", "test-api-key")) .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); @@ -130,7 +130,7 @@ class SteamApiServiceTest { } """; - server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/"))) .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); List result = steamApiService.getOwnedGames(steamId); @@ -178,7 +178,7 @@ class SteamApiServiceTest { server.expect(requestTo(containsString("/ISteamUser/GetPlayerSummaries/v0002/"))) .andRespond(withSuccess(summaryJson, MediaType.APPLICATION_JSON)); - server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/"))) .andRespond(withSuccess(gamesJson, MediaType.APPLICATION_JSON)); Optional result = steamApiService.getFullPlayerProfile(steamId);