From bd4c3b1c5f65dd617b1b3470a54b06925d9cf7ce Mon Sep 17 00:00:00 2001 From: Noooste <83548733+Noooste@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:17:51 +0200 Subject: [PATCH] feat: handle zero SessionMaxAge in GenerateSessionToken to prevent immediate expiration Signed-off-by: Noooste <83548733+Noooste@users.noreply.github.com> --- backend/internal/auth/auth.go | 13 +++++++++++-- backend/internal/auth/auth_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/backend/internal/auth/auth.go b/backend/internal/auth/auth.go index 87ca1f8..de6f02b 100644 --- a/backend/internal/auth/auth.go +++ b/backend/internal/auth/auth.go @@ -356,9 +356,18 @@ func (a *Service) ValidateAndConsumeState(token string) bool { return a.jwtService.ValidateAndConsumeState(token) } -// GenerateSessionToken generates a JWT session token for the user +// GenerateSessionToken generates a JWT session token for the user. +// +// SessionMaxAge lives under the OIDC config block, but the JWT is shared with +// admin-only deployments that never set it. Treat a non-positive value as +// "not configured" and fall back to 24h so the token isn't issued already +// expired (which would make every subsequent request fail with 401). func (a *Service) GenerateSessionToken(userInfo *UserInfo) (string, error) { - return a.jwtService.GenerateToken(userInfo, a.authConfig.OIDC.SessionMaxAge) + maxAge := a.authConfig.OIDC.SessionMaxAge + if maxAge <= 0 { + maxAge = 86400 + } + return a.jwtService.GenerateToken(userInfo, maxAge) } // ValidateSessionToken validates a JWT session token and returns user info diff --git a/backend/internal/auth/auth_test.go b/backend/internal/auth/auth_test.go index 272da95..542ab68 100644 --- a/backend/internal/auth/auth_test.go +++ b/backend/internal/auth/auth_test.go @@ -68,3 +68,31 @@ func TestExtractRolesFromAccessToken_Malformed(t *testing.T) { t.Fatalf("expected nil for empty token, got %v", roles) } } + +// Regression for issue #16: admin-only deployments never set +// OIDC.SessionMaxAge, leaving it at 0. Before the fix, that produced a JWT +// whose exp == iat, so every request after /auth/login was rejected as +// expired and the client saw UNAUTHORIZED. +func TestGenerateSessionToken_ZeroSessionMaxAge_IsNotImmediatelyExpired(t *testing.T) { + jwtSvc, err := NewJWTService() + if err != nil { + t.Fatalf("NewJWTService: %v", err) + } + + svc := &Service{ + authConfig: &config.AuthConfig{ + Admin: config.AdminAuthConfig{Enabled: true, Username: "admin", Password: "pw"}, + // OIDC disabled; SessionMaxAge left at zero value. + }, + jwtService: jwtSvc, + } + + token, err := svc.GenerateSessionToken(&UserInfo{Username: "admin"}) + if err != nil { + t.Fatalf("GenerateSessionToken: %v", err) + } + + if _, err := svc.ValidateSessionToken(token); err != nil { + t.Fatalf("freshly issued admin session token failed validation: %v", err) + } +}