Skip to content

Commit

Permalink
feat: allow configuring logout path and redirect
Browse files Browse the repository at this point in the history
This change allows to configure logout process for Hilla security policy.
by setting the logout path, an optional redirect URI after logout and
a flag to invalidate the HTTP session.
  • Loading branch information
mcollovati committed Feb 5, 2025
1 parent bdf5cb5 commit 5d4302b
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ public boolean shouldRun(Path sourceDir, Config config) {

@Override
public boolean trigger(CodeGenContext context) {
if (context.test()) {
// Generation not required for test code
return false;
}
String prefix = computeConnectClientPrefix(context.config());
boolean defaultPrefix = "connect".equals(prefix);
Path customClient = context.inputDir().resolve("connect-client.ts");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,18 @@
import io.vertx.ext.web.RoutingContext;

public class HillaFormAuthenticationMechanism implements HttpAuthenticationMechanism {
private final String landingPage;
private final String logoutPath;
private final String cookieName;
private final Config config;

FormAuthenticationMechanism delegate;

public HillaFormAuthenticationMechanism(
FormAuthenticationMechanism delegate, String cookieName, String landingPage, String logoutPath) {
public HillaFormAuthenticationMechanism(FormAuthenticationMechanism delegate, Config config) {
this.delegate = delegate;
this.landingPage = landingPage;
this.logoutPath = logoutPath;
this.cookieName = cookieName;
this.config = config;
}

@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
if (context.normalizedPath().equals(logoutPath)) {
if (context.normalizedPath().equals(config.logoutPath())) {
logout(context);
return Uni.createFrom().optional(Optional.empty());
}
Expand All @@ -63,7 +58,7 @@ public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProvid
if (loginFailure) {
response.setStatusCode(401);
} else {
response.putHeader("Default-url", landingPage);
response.putHeader("Default-url", config.landingPage());
if (redirectUrl != null) {
response.putHeader("Saved-url", redirectUrl);
}
Expand Down Expand Up @@ -93,10 +88,23 @@ private void logout(RoutingContext ctx) {
// Vert.x sends back a set-cookie with max-age and expiry but no path,
// so we have to set it first,
// otherwise web clients don't clear it
Cookie cookie = ctx.request().getCookie(cookieName);
Cookie cookie = ctx.request().getCookie(config.cookieName());
if (cookie != null) {
cookie.setPath("/");
}
ctx.response().removeCookie(cookieName);
ctx.response().removeCookie(config.cookieName());
if (config.invalidateSessions() && ctx.session() != null) {
ctx.session().destroy();
}
if (config.postLogoutRedirect() != null) {
ctx.redirect(config.postLogoutRedirect());
}
}

public record Config(
String cookieName,
String landingPage,
String logoutPath,
String postLogoutRedirect,
boolean invalidateSessions) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism;
import io.smallrye.config.SmallRyeConfig;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

Expand All @@ -31,14 +32,20 @@
public class HillaSecurityRecorder {

public Supplier<HillaFormAuthenticationMechanism> setupFormAuthenticationMechanism() {
String cookieName = ConfigProvider.getConfig().getValue("quarkus.http.auth.form.cookie-name", String.class);
String landingPage = ConfigProvider.getConfig()
.getOptionalValue("quarkus.http.auth.form.landing-page", String.class)
.orElse("/");
Config config = ConfigProvider.getConfig();
VaadinSecurityConfig securityConfig =
config.unwrap(SmallRyeConfig.class).getConfigMapping(VaadinSecurityConfig.class);
var authConfig = new HillaFormAuthenticationMechanism.Config(
config.getValue("quarkus.http.auth.form.cookie-name", String.class),
config.getOptionalValue("quarkus.http.auth.form.landing-page", String.class)
.orElse("/"),
securityConfig.logoutPath(),
securityConfig.postLogoutRedirectUri().orElse(null),
securityConfig.logoutInvalidateSession());
return () -> {
FormAuthenticationMechanism delegate =
Arc.container().instance(FormAuthenticationMechanism.class).get();
return new HillaFormAuthenticationMechanism(delegate, cookieName, landingPage, "/logout");
return new HillaFormAuthenticationMechanism(delegate, authConfig);
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2025 Marco Collovati, Dario Götze
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.mcollovati.quarkus.hilla.security;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

/**
* Configuration properties for Vaadin security.
*/
@ConfigMapping(prefix = "vaadin.security")
@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
interface VaadinSecurityConfig {

/**
* The path of the logout HTTP POST endpoint handling logout requests.
* <p></p>
* Defaults to {@literal /logout}.
*
* @return the path of the logout endpoint.
*/
@WithDefault("/logout")
String logoutPath();

/**
* The post logout redirect uri.
* <p></p>
* @return post logout redirect uri.
*/
Optional<String> postLogoutRedirectUri();

/**
* Whether HTTP session should be invalidated on logout.
* <p></p>
* Defaults to {@literal true}.
*
* @return whether HTTP session should be invalidated on logout.
*/
@WithDefault("true")
boolean logoutInvalidateSession();
}

0 comments on commit 5d4302b

Please sign in to comment.