From f93aa331d92376c8bc2aa546bacc2989f32d8184 Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Sat, 25 Jul 2026 22:50:46 +0200 Subject: [PATCH] feat(config): add GENIUSRUN_FRONTEND_URL, defaulting to PublicBaseURL Lets the OIDC callback redirect to the frontend's real origin instead of a relative path resolved against the backend's own origin -- needed for this project's own supported split-origin local dev setup (frontend on Vite, backend on geniusrund, bridged by CORS). Co-Authored-By: Claude Sonnet 5 --- backend/internal/config/config.go | 14 +++++++++++++- backend/internal/config/config_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index e778da3..5527f3b 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -43,7 +43,18 @@ type Config struct { // OIDCRedirectURL and whether session cookies can be marked Secure, // instead of requiring both to be configured separately and risking them // drifting out of sync. - PublicBaseURL string + PublicBaseURL string + // FrontendURL is the origin the browser should land on after the OIDC + // callback (both success and failure) -- e.g. "http://localhost:5173" in + // local dev, where the frontend and backend are different origins + // bridged by CORS (see internal/api's corsMiddleware and + // frontend/src/api/client.ts's BASE_URL). Defaults to PublicBaseURL when + // unset, which is correct for the common production topology where a + // reverse proxy unifies frontend and backend under one origin. + // PublicBaseURL itself must stay pointed at the backend's own origin + // regardless -- it derives OIDCRedirectURL and post_logout_redirect_uri, + // which must match wherever those routes are actually served. + FrontendURL string OIDCIssuerURL string OIDCClientID string OIDCClientSecret string @@ -80,6 +91,7 @@ func Load() (Config, error) { if cfg.PublicBaseURL == "" { return cfg, fmt.Errorf("GENIUSRUN_PUBLIC_BASE_URL is required (e.g. https://geniusrun.example.com)") } + cfg.FrontendURL = strings.TrimRight(getEnvDefault("GENIUSRUN_FRONTEND_URL", cfg.PublicBaseURL), "/") if cfg.OIDCIssuerURL == "" { return cfg, fmt.Errorf("GENIUSRUN_OIDC_ISSUER_URL is required") } diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index c48c946..35e52bd 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -134,6 +134,32 @@ func TestLoad_GarminPythonPathExplicitOverridesDefault(t *testing.T) { } } +func TestLoad_FrontendURLDefaultsToPublicBaseURL(t *testing.T) { + setRequiredEnv(t) + t.Setenv("GENIUSRUN_FRONTEND_URL", "") + + cfg, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if cfg.FrontendURL != cfg.PublicBaseURL { + t.Errorf("FrontendURL = %q, want it to default to PublicBaseURL %q", cfg.FrontendURL, cfg.PublicBaseURL) + } +} + +func TestLoad_FrontendURLExplicitOverridesDefault(t *testing.T) { + setRequiredEnv(t) + t.Setenv("GENIUSRUN_FRONTEND_URL", "http://localhost:5173/") + + cfg, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if cfg.FrontendURL != "http://localhost:5173" { + t.Errorf("FrontendURL = %q, want http://localhost:5173 (trailing slash trimmed)", cfg.FrontendURL) + } +} + func TestLoad_CustomRoleAndDuration(t *testing.T) { setRequiredEnv(t) t.Setenv("GENIUSRUN_OIDC_REQUIRED_ROLE", "admin")