feat(chore): added qodana analysis job
Some checks failed
Build, Test, Deploy and Analysis / test-backend (pull_request) Failing after 0s
Build, Test, Deploy and Analysis / build-and-push (pull_request) Has been skipped
Build, Test, Deploy and Analysis / deploy (pull_request) Has been skipped
Build, Test, Deploy and Analysis / qodana (pull_request) Failing after 0s
Build, Test, Deploy and Analysis / qodana (push) Failing after 32s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 1m30s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 42s
Build, Test, Deploy and Analysis / deploy (push) Successful in 8s

This commit is contained in:
2026-03-03 09:19:05 +01:00
parent 9955f23f31
commit c00ca5a32e
6 changed files with 55 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
package com.printcalculator.security;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -14,6 +15,13 @@ public class AdminLoginThrottleService {
private static final long MAX_DELAY_SECONDS = 3600L;
private final ConcurrentHashMap<String, LoginAttemptState> attemptsByClient = new ConcurrentHashMap<>();
private final boolean trustProxyHeaders;
public AdminLoginThrottleService(
@Value("${admin.auth.trust-proxy-headers:false}") boolean trustProxyHeaders
) {
this.trustProxyHeaders = trustProxyHeaders;
}
public OptionalLong getRemainingLockSeconds(String clientKey) {
LoginAttemptState state = attemptsByClient.get(clientKey);
@@ -47,17 +55,19 @@ public class AdminLoginThrottleService {
}
public String resolveClientKey(HttpServletRequest request) {
String forwardedFor = request.getHeader("X-Forwarded-For");
if (forwardedFor != null && !forwardedFor.isBlank()) {
String[] parts = forwardedFor.split(",");
if (parts.length > 0 && !parts[0].trim().isEmpty()) {
return parts[0].trim();
if (trustProxyHeaders) {
String forwardedFor = request.getHeader("X-Forwarded-For");
if (forwardedFor != null && !forwardedFor.isBlank()) {
String[] parts = forwardedFor.split(",");
if (parts.length > 0 && !parts[0].trim().isEmpty()) {
return parts[0].trim();
}
}
}
String realIp = request.getHeader("X-Real-IP");
if (realIp != null && !realIp.isBlank()) {
return realIp.trim();
String realIp = request.getHeader("X-Real-IP");
if (realIp != null && !realIp.isBlank()) {
return realIp.trim();
}
}
String remoteAddress = request.getRemoteAddr();

View File

@@ -24,6 +24,7 @@ import java.util.UUID;
public class AdminSessionService {
public static final String COOKIE_NAME = "admin_session";
private static final String COOKIE_PATH = "/api/admin";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private final ObjectMapper objectMapper;
@@ -127,20 +128,20 @@ public class AdminSessionService {
public ResponseCookie buildLoginCookie(String token) {
return ResponseCookie.from(COOKIE_NAME, token)
.path("/")
.path(COOKIE_PATH)
.httpOnly(true)
.secure(true)
.sameSite("Lax")
.sameSite("Strict")
.maxAge(Duration.ofMinutes(sessionTtlMinutes))
.build();
}
public ResponseCookie buildLogoutCookie() {
return ResponseCookie.from(COOKIE_NAME, "")
.path("/")
.path(COOKIE_PATH)
.httpOnly(true)
.secure(true)
.sameSite("Lax")
.sameSite("Strict")
.maxAge(Duration.ZERO)
.build();
}

View File

@@ -46,3 +46,4 @@ app.frontend.base-url=${APP_FRONTEND_BASE_URL:http://localhost:4200}
admin.password=${ADMIN_PASSWORD}
admin.session.secret=${ADMIN_SESSION_SECRET}
admin.session.ttl-minutes=${ADMIN_SESSION_TTL_MINUTES:480}
admin.auth.trust-proxy-headers=${ADMIN_AUTH_TRUST_PROXY_HEADERS:false}