From b50b69adb2435d3710613f07e06d78920d9025e1 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 19 Aug 2024 18:02:56 +0200 Subject: [PATCH] Revert "better workaround for cookie max-age" because it causes issues with favicon fetching --- commafeed-server/pom.xml | 4 - .../FormAuthenticationCookieInterceptor.java | 34 ------- ...okieMaxAgeFormAuthenticationMechanism.java | 90 +++++++++++++++++++ 3 files changed, 90 insertions(+), 38 deletions(-) delete mode 100644 commafeed-server/src/main/java/com/commafeed/security/FormAuthenticationCookieInterceptor.java create mode 100644 commafeed-server/src/main/java/com/commafeed/security/mechanism/CookieMaxAgeFormAuthenticationMechanism.java diff --git a/commafeed-server/pom.xml b/commafeed-server/pom.xml index ad5b67ff..06424992 100644 --- a/commafeed-server/pom.xml +++ b/commafeed-server/pom.xml @@ -248,10 +248,6 @@ io.quarkus quarkus-arc - - io.quarkus - quarkus-reactive-routes - io.quarkus quarkus-security diff --git a/commafeed-server/src/main/java/com/commafeed/security/FormAuthenticationCookieInterceptor.java b/commafeed-server/src/main/java/com/commafeed/security/FormAuthenticationCookieInterceptor.java deleted file mode 100644 index 3692ed2d..00000000 --- a/commafeed-server/src/main/java/com/commafeed/security/FormAuthenticationCookieInterceptor.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.commafeed.security; - -import io.quarkus.vertx.http.runtime.HttpConfiguration; -import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism; -import io.quarkus.vertx.web.RouteFilter; -import io.vertx.core.http.Cookie; -import io.vertx.core.http.impl.ServerCookie; -import io.vertx.ext.web.RoutingContext; -import jakarta.inject.Singleton; -import lombok.RequiredArgsConstructor; - -/** - * Intercepts responses and sets a Max-Age on the cookie created by {@link FormAuthenticationMechanism} because it has no value by default. - * - * This is a workaround for https://github.com/quarkusio/quarkus/issues/42463 - */ -@RequiredArgsConstructor -@Singleton -public class FormAuthenticationCookieInterceptor { - - private final HttpConfiguration config; - - @RouteFilter(Integer.MAX_VALUE) - public void cookieInterceptor(RoutingContext context) { - context.addHeadersEndHandler(v -> { - Cookie cookie = context.request().getCookie(config.auth.form.cookieName); - if (cookie instanceof ServerCookie sc && sc.isChanged()) { - cookie.setMaxAge(config.auth.form.timeout.toSeconds()); - } - }); - - context.next(); - } -} diff --git a/commafeed-server/src/main/java/com/commafeed/security/mechanism/CookieMaxAgeFormAuthenticationMechanism.java b/commafeed-server/src/main/java/com/commafeed/security/mechanism/CookieMaxAgeFormAuthenticationMechanism.java new file mode 100644 index 00000000..c5ffa581 --- /dev/null +++ b/commafeed-server/src/main/java/com/commafeed/security/mechanism/CookieMaxAgeFormAuthenticationMechanism.java @@ -0,0 +1,90 @@ +package com.commafeed.security.mechanism; + +import java.security.SecureRandom; +import java.util.Base64; + +import io.quarkus.vertx.http.runtime.FormAuthConfig; +import io.quarkus.vertx.http.runtime.FormAuthRuntimeConfig; +import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; +import io.quarkus.vertx.http.runtime.HttpConfiguration; +import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism; +import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; +import io.quarkus.vertx.http.runtime.security.PersistentLoginManager; +import io.vertx.core.http.Cookie; +import io.vertx.core.http.impl.ServerCookie; +import io.vertx.ext.web.RoutingContext; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import lombok.experimental.Delegate; +import lombok.extern.slf4j.Slf4j; + +/** + * HttpAuthenticationMechanism that wraps FormAuthenticationMechanism and sets a Max-Age on the cookie because it has no value by default. + * + * This is a workaround for https://github.com/quarkusio/quarkus/issues/42463 + */ +@Priority(1) +@Singleton +@Slf4j +public class CookieMaxAgeFormAuthenticationMechanism implements HttpAuthenticationMechanism { + + // the temp encryption key, persistent across dev mode restarts + static volatile String encryptionKey; + + @Delegate + private final FormAuthenticationMechanism delegate; + + public CookieMaxAgeFormAuthenticationMechanism(HttpConfiguration httpConfiguration, HttpBuildTimeConfig buildTimeConfig) { + String key; + if (httpConfiguration.encryptionKey.isEmpty()) { + if (encryptionKey != null) { + // persist across dev mode restarts + key = encryptionKey; + } else { + byte[] data = new byte[32]; + new SecureRandom().nextBytes(data); + key = encryptionKey = Base64.getEncoder().encodeToString(data); + log.warn("Encryption key was not specified for persistent FORM auth, using temporary key {}", key); + } + } else { + key = httpConfiguration.encryptionKey.get(); + } + + FormAuthConfig form = buildTimeConfig.auth.form; + FormAuthRuntimeConfig runtimeForm = httpConfiguration.auth.form; + String loginPage = startWithSlash(runtimeForm.loginPage.orElse(null)); + String errorPage = startWithSlash(runtimeForm.errorPage.orElse(null)); + String landingPage = startWithSlash(runtimeForm.landingPage.orElse(null)); + String postLocation = startWithSlash(form.postLocation); + String usernameParameter = runtimeForm.usernameParameter; + String passwordParameter = runtimeForm.passwordParameter; + String locationCookie = runtimeForm.locationCookie; + String cookiePath = runtimeForm.cookiePath.orElse(null); + boolean redirectAfterLogin = landingPage != null; + String cookieSameSite = runtimeForm.cookieSameSite.name(); + + PersistentLoginManager loginManager = new PersistentLoginManager(key, runtimeForm.cookieName, runtimeForm.timeout.toMillis(), + runtimeForm.newCookieInterval.toMillis(), runtimeForm.httpOnlyCookie, cookieSameSite, cookiePath) { + @Override + public void save(String value, RoutingContext context, String cookieName, RestoreResult restoreResult, boolean secureCookie) { + super.save(value, context, cookieName, restoreResult, secureCookie); + + // add max age to the cookie + Cookie cookie = context.request().getCookie(cookieName); + if (cookie instanceof ServerCookie sc && sc.isChanged()) { + cookie.setMaxAge(runtimeForm.timeout.toSeconds()); + } + } + }; + + this.delegate = new FormAuthenticationMechanism(loginPage, postLocation, usernameParameter, passwordParameter, errorPage, + landingPage, redirectAfterLogin, locationCookie, cookieSameSite, cookiePath, loginManager); + } + + private static String startWithSlash(String page) { + if (page == null) { + return null; + } + return page.startsWith("/") ? page : "/" + page; + } +}