From 017b737b72c3bcb35832707309bbd6d2c883fd3c Mon Sep 17 00:00:00 2001 From: DarioGii Date: Fri, 25 Jul 2025 19:28:14 +0100 Subject: [PATCH] wip - /view-pdf not loading properly --- .claude/settings.local.json | 4 +- .../src/main/resources/settings.yml.template | 20 ++++----- .../configuration/SecurityConfiguration.java | 6 +-- .../filter/JwtAuthenticationFilter.java | 6 ++- .../filter/UserAuthenticationFilter.java | 41 +++++++++++++++++++ .../security/service/JwtKeystoreService.java | 6 +-- .../security/service/JwtService.java | 10 +++-- 7 files changed, 71 insertions(+), 22 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 13d8d8350..bc5358b85 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,9 @@ "Bash(find:*)", "Bash(grep:*)", "Bash(rg:*)", - "Bash(strings:*)" + "Bash(strings:*)", + "Bash(pkill:*)", + "Bash(true)" ], "deny": [] } diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 282508066..790c03216 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -31,7 +31,7 @@ security: google: clientId: '' # client ID for Google OAuth2 clientSecret: '' # client secret for Google OAuth2 - scopes: email, profile # scopes for Google OAuth2 + scopes: https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile # scopes for Google OAuth2 useAsUsername: email # field to use as the username for Google OAuth2. Available options are: [email | name | given_name | family_name] github: clientId: '' # client ID for GitHub OAuth2 @@ -51,20 +51,20 @@ security: provider: '' # The name of your Provider autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin - registrationId: stirling # The name of your Service Provider (SP) app name. Should match the name in the path for your SSO & SLO URLs - idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata # The uri for your Provider's metadata - idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml # The URL for initiating SSO. Provided by your Provider - idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml # The URL for initiating SLO. Provided by your Provider - idpIssuer: '' # The ID of your Provider - idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider - privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair - spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair + registrationId: stirlingpdf-dario-saml # The name of your Service Provider (SP) app name. Should match the name in the path for your SSO & SLO URLs + idpMetadataUri: https://authentik.dev.stirlingpdf.com/api/v3/providers/saml/5/metadata/ # The uri for your Provider's metadata + idpSingleLoginUrl: https://authentik.dev.stirlingpdf.com/application/saml/stirlingpdf-dario-saml/sso/binding/post/ # The URL for initiating SSO. Provided by your Provider + idpSingleLogoutUrl: https://authentik.dev.stirlingpdf.com/application/saml/stirlingpdf-dario-saml/slo/binding/post/ # The URL for initiating SLO. Provided by your Provider + idpIssuer: authentik # The ID of your Provider + idpCert: classpath:authentik-Self-signed_Certificate_certificate.pem # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider + privateKey: classpath:private_key.key # Your private key. Generated from your keypair + spCert: classpath:certificate.crt # Your signing certificate. Generated from your keypair jwt: enableKeyStore: true # Set to 'true' to enable JWT key store enableKeyRotation: true # Set to 'true' to enable JWT key rotation premium: - key: 00000000-0000-0000-0000-000000000000 + key: 3R3T-WFPY-UNRW-LJFA-MMXM-YVJK-WCKY-PCRT # fixme: remove enabled: false # Enable license key checks for pro/enterprise features proFeatures: SSOAutoLogin: false diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index eeec274bf..a24b109c9 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -135,7 +135,7 @@ public class SecurityConfiguration { boolean v2Enabled = appConfig.v2Enabled(); if (v2Enabled) { - http.addFilterAt( + http.addFilterBefore( jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .exceptionHandling( @@ -145,8 +145,8 @@ public class SecurityConfiguration { } http.addFilterBefore( userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .addFilterAfter(rateLimitingFilter(), userAuthenticationFilter.getClass()) - .addFilterAfter(firstLoginFilter, rateLimitingFilter().getClass()); + .addFilterAfter(rateLimitingFilter(), UserAuthenticationFilter.class) + .addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); if (!securityProperties.getCsrfDisabled()) { CookieCsrfTokenRepository cookieRepo = diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java index d3511b08b..6aea4739a 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java @@ -88,6 +88,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { try { jwtService.validateToken(jwtToken); } catch (AuthenticationFailureException e) { + // Clear invalid tokens from response + jwtService.clearTokenFromResponse(response); handleAuthenticationFailure(request, response, e); return; } @@ -129,7 +131,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); - log.debug("JWT authentication successful for user: {}", username); + log.info( + "JWT authentication successful for user: {} - Authentication set in SecurityContext", + username); } else { throw new UsernameNotFoundException("User not found: " + username); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index bb22f597a..70c9e233e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -63,7 +63,15 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { return; } String requestURI = request.getRequestURI(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + log.info( + "UserAuthenticationFilter - Authentication from SecurityContext: {}", + authentication != null + ? authentication.getClass().getSimpleName() + + " for " + + authentication.getName() + : "null"); // Check for session expiration (unsure if needed) // if (authentication != null && authentication.isAuthenticated()) { @@ -218,4 +226,37 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { return method; } } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String uri = request.getRequestURI(); + String contextPath = request.getContextPath(); + String[] permitAllPatterns = { + contextPath + "/login", + contextPath + "/signup", + contextPath + "/register", + contextPath + "/error", + contextPath + "/images/", + contextPath + "/public/", + contextPath + "/css/", + contextPath + "/fonts/", + contextPath + "/js/", + contextPath + "/pdfjs/", + contextPath + "/pdfjs-legacy/", + contextPath + "/api/v1/info/status", + contextPath + "/site.webmanifest" + }; + + for (String pattern : permitAllPatterns) { + if (uri.startsWith(pattern) + || uri.endsWith(".svg") + || uri.endsWith(".mjs") + || uri.endsWith(".png") + || uri.endsWith(".ico")) { + return true; + } + } + + return false; + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeystoreService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeystoreService.java index 887f26f94..72fbaba92 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeystoreService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeystoreService.java @@ -37,7 +37,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface { public static final String KEY_SUFFIX = ".key"; private final JwtSigningKeyRepository repository; - private final ApplicationProperties.Security.Jwt jwtConfig; + private final ApplicationProperties.Security.Jwt jwtProperties; private final Path privateKeyDirectory; private volatile KeyPair currentKeyPair; @@ -47,7 +47,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface { public JwtKeystoreService( JwtSigningKeyRepository repository, ApplicationProperties applicationProperties) { this.repository = repository; - this.jwtConfig = applicationProperties.getSecurity().getJwt(); + this.jwtProperties = applicationProperties.getSecurity().getJwt(); this.privateKeyDirectory = Paths.get(InstallationPathConfig.getConfigPath(), "jwt-keys"); } @@ -128,7 +128,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface { @Override public boolean isKeystoreEnabled() { - return jwtConfig.isEnableKeystore(); + return jwtProperties.isEnableKeystore(); } private void loadOrGenerateKeypair() { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java index a9b1921bd..b903767ff 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java @@ -82,7 +82,6 @@ public class JwtService implements JwtServiceInterface { .expiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(keyPair.getPrivate(), Jwts.SIG.RS256); - // Add key ID to header if keystore is enabled String keyId = keystoreService.getActiveKeyId(); if (keyId != null) { builder.header().keyId(keyId); @@ -136,8 +135,11 @@ public class JwtService implements JwtServiceInterface { if (specificKeyPair.isPresent()) { keyPair = specificKeyPair.get(); } else { - log.warn("Key ID {} not found in keystore, using active keypair", keyId); - keyPair = keystoreService.getActiveKeypair(); + log.warn( + "Key ID {} not found in keystore, token may have been signed with a rotated key", + keyId); + throw new AuthenticationFailureException( + "JWT token signed with unknown key ID: " + keyId); } } else { keyPair = keystoreService.getActiveKeypair(); @@ -233,7 +235,7 @@ public class JwtService implements JwtServiceInterface { .getHeader() .get("kid"); } catch (Exception e) { - // Token might not have a key ID or be malformed + log.debug("Failed to extract key ID from token header: {}", e.getMessage()); return null; } }