Added dev impersonation and changed api a little bit

This commit is contained in:
Andreas Dahm committed 2026-04-29 08:50:02 +02:00
1 parent 1b08e7902c
commit ac97442312
8 files changed
+104 -18

No files matched your search

@@ -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);
}
}
@@ -8,6 +8,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -17,7 +18,8 @@ import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@Component @Component("jwtAuthenticationFilter")
@Profile("!dev")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter { public class JwtAuthenticationFilter extends OncePerRequestFilter {
@@ -4,10 +4,12 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Getter @Getter
@Setter @Setter
@Configuration @Configuration
@Profile("!dev")
@ConfigurationProperties(prefix = "security.jwt") @ConfigurationProperties(prefix = "security.jwt")
public class JwtConfig { public class JwtConfig {
@@ -2,12 +2,14 @@ package core_service.configuration.security;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@Service @Service
@Profile("!dev")
public class JwtService { public class JwtService {
private final JwtConfig jwtConfig; private final JwtConfig jwtConfig;
@@ -1,6 +1,7 @@
package core_service.configuration.security; package core_service.configuration.security;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.OncePerRequestFilter;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig { public class SecurityConfig {
private static final String[] WHITE_LIST_URLS = { private static final String[] WHITE_LIST_URLS = {
@@ -31,7 +32,11 @@ public class SecurityConfig {
"/api/steam/test/**" "/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}") @Value("${app.cors.allowed-origins}")
private String[] allowedOrigins; private String[] allowedOrigins;
@@ -1,18 +1,24 @@
package core_service.modules.steam_adapter.api.dto.response; package core_service.modules.steam_adapter.api.dto.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List; 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( public record OwnedGamesResponse(
@JsonProperty("game_count") Integer gameCount, @JsonProperty("game_count") Integer gameCount,
List<Game> games @JsonProperty("games") List<Game> games
) {} ) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record Game( public record Game(
Integer appid, @JsonProperty("appid") Integer appid,
String name, @JsonProperty("name") String name,
@JsonProperty("playtime_forever") Integer playtimeForever, @JsonProperty("playtime_forever") Integer playtimeForever,
@JsonProperty("img_icon_url") String imgIconUrl, @JsonProperty("img_icon_url") String imgIconUrl,
@JsonProperty("has_community_visible_stats") Boolean hasCommunityVisibleStats @JsonProperty("has_community_visible_stats") Boolean hasCommunityVisibleStats
@@ -14,9 +14,9 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class SteamApiService { public class SteamApiService {
private final RestClient steamRestClient; private final RestClient steamRestClient;
@@ -26,24 +26,42 @@ public class SteamApiService {
@Cacheable(value = "steamGameCount", key = "#steamId") @Cacheable(value = "steamGameCount", key = "#steamId")
public int getOwnedGamesCount(String 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 { 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() SteamOwnedGamesResponse response = steamRestClient.get()
.uri(uriBuilder -> uriBuilder .uri(uriBuilder -> uriBuilder
.path("/IPlayerService/GetOwnedGames/v0001/") .path("/IPlayerService/GetOwnedGames/v1/")
.queryParam("key", apiKey) .queryParam("key", apiKey)
.queryParam("steamid", steamId) .queryParam("steamid", steamId)
.queryParam("include_appinfo", 0) .queryParam("include_appinfo", "true")
.queryParam("include_played_free_games", 1) .queryParam("include_played_free_games", "true")
.queryParam("format", "json")
.build()) .build())
.retrieve() .retrieve()
.body(SteamOwnedGamesResponse.class); .body(SteamOwnedGamesResponse.class);
log.info("SteamOwnedGamesResponse: {}", response); log.info("Steam API Response object for {}: {}", steamId, response);
if (response != null && response.response() != null) { 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) { } catch (Exception e) {
@@ -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("steamid", steamId))
.andExpect(queryParam("key", "test-api-key")) .andExpect(queryParam("key", "test-api-key"))
.andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); .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("steamid", steamId))
.andExpect(queryParam("key", "test-api-key")) .andExpect(queryParam("key", "test-api-key"))
.andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); .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)); .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON));
List<SteamOwnedGamesResponse.Game> result = steamApiService.getOwnedGames(steamId); List<SteamOwnedGamesResponse.Game> result = steamApiService.getOwnedGames(steamId);
@@ -178,7 +178,7 @@ class SteamApiServiceTest {
server.expect(requestTo(containsString("/ISteamUser/GetPlayerSummaries/v0002/"))) server.expect(requestTo(containsString("/ISteamUser/GetPlayerSummaries/v0002/")))
.andRespond(withSuccess(summaryJson, MediaType.APPLICATION_JSON)); .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)); .andRespond(withSuccess(gamesJson, MediaType.APPLICATION_JSON));
Optional<SteamProfileAndGamesResponse> result = steamApiService.getFullPlayerProfile(steamId); Optional<SteamProfileAndGamesResponse> result = steamApiService.getFullPlayerProfile(steamId);