refactor(config): rename GENIUSRUN_PUBLIC_BASE_URL to GENIUSRUN_BACKEND_URL

Now that GENIUSRUN_FRONTEND_URL exists as a separate config value, keeping
the backend's own origin named "PublicBaseURL" invited exactly the kind of
mixup that caused the OIDC callback 404 in the first place. Renamed
consistently: env var, Config.BackendURL, api.SessionConfig.BackendURL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 23:00:35 +02:00
parent db2e8e487d
commit 099e746119
6 changed files with 32 additions and 32 deletions

View File

@@ -38,20 +38,20 @@ type Config struct {
MinConfidence float64
IncrementalSyncEvery time.Duration
// OIDC login gate (Keycloak). PublicBaseURL is this app's own externally
// OIDC login gate (Keycloak). BackendURL is this app's own externally
// reachable origin (e.g. "https://geniusrun.example.com") -- it derives
// 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
BackendURL 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
// frontend/src/api/client.ts's BASE_URL). Defaults to BackendURL 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
// BackendURL 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
@@ -75,7 +75,7 @@ func Load() (Config, error) {
GarminTokenStoreRoot: os.Getenv("GARMIN_TOKENSTORE"),
MinConfidence: getEnvFloat("GENIUSRUN_MIN_CONFIDENCE", 0.6),
IncrementalSyncEvery: getEnvDuration("GENIUSRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour),
PublicBaseURL: strings.TrimRight(os.Getenv("GENIUSRUN_PUBLIC_BASE_URL"), "/"),
BackendURL: strings.TrimRight(os.Getenv("GENIUSRUN_BACKEND_URL"), "/"),
OIDCIssuerURL: os.Getenv("GENIUSRUN_OIDC_ISSUER_URL"),
OIDCClientID: os.Getenv("GENIUSRUN_OIDC_CLIENT_ID"),
OIDCClientSecret: os.Getenv("GENIUSRUN_OIDC_CLIENT_SECRET"),
@@ -88,10 +88,10 @@ func Load() (Config, error) {
log.Printf("GARMIN_TOKENSTORE not set, defaulting to %q", cfg.GarminTokenStoreRoot)
}
if cfg.PublicBaseURL == "" {
return cfg, fmt.Errorf("GENIUSRUN_PUBLIC_BASE_URL is required (e.g. https://geniusrun.example.com)")
if cfg.BackendURL == "" {
return cfg, fmt.Errorf("GENIUSRUN_BACKEND_URL is required (e.g. https://geniusrun.example.com)")
}
cfg.FrontendURL = strings.TrimRight(getEnvDefault("GENIUSRUN_FRONTEND_URL", cfg.PublicBaseURL), "/")
cfg.FrontendURL = strings.TrimRight(getEnvDefault("GENIUSRUN_FRONTEND_URL", cfg.BackendURL), "/")
if cfg.OIDCIssuerURL == "" {
return cfg, fmt.Errorf("GENIUSRUN_OIDC_ISSUER_URL is required")
}
@@ -106,8 +106,8 @@ func Load() (Config, error) {
return cfg, fmt.Errorf("GENIUSRUN_SESSION_SECRET is required and must be at least 32 characters")
}
cfg.SessionSecret = []byte(sessionSecret)
cfg.OIDCRedirectURL = cfg.PublicBaseURL + "/api/session/callback"
cfg.SessionSecure = strings.HasPrefix(cfg.PublicBaseURL, "https://")
cfg.OIDCRedirectURL = cfg.BackendURL + "/api/session/callback"
cfg.SessionSecure = strings.HasPrefix(cfg.BackendURL, "https://")
return cfg, nil
}

View File

@@ -8,14 +8,14 @@ import (
func setRequiredEnv(t *testing.T) {
t.Helper()
t.Setenv("GENIUSRUN_PUBLIC_BASE_URL", "https://geniusrun.example.com")
t.Setenv("GENIUSRUN_BACKEND_URL", "https://geniusrun.example.com")
t.Setenv("GENIUSRUN_OIDC_ISSUER_URL", "https://keycloak.example.com/realms/myrealm")
t.Setenv("GENIUSRUN_OIDC_CLIENT_ID", "geniusrun")
t.Setenv("GENIUSRUN_OIDC_CLIENT_SECRET", "client-secret")
t.Setenv("GENIUSRUN_SESSION_SECRET", "a-session-secret-that-is-at-least-32-bytes-long")
}
func TestLoad_DerivesOIDCSettingsFromPublicBaseURL(t *testing.T) {
func TestLoad_DerivesOIDCSettingsFromBackendURL(t *testing.T) {
setRequiredEnv(t)
cfg, err := Load()
@@ -38,7 +38,7 @@ func TestLoad_DerivesOIDCSettingsFromPublicBaseURL(t *testing.T) {
func TestLoad_HTTPBaseURLYieldsInsecureCookies(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GENIUSRUN_PUBLIC_BASE_URL", "http://localhost:8080")
t.Setenv("GENIUSRUN_BACKEND_URL", "http://localhost:8080")
cfg, err := Load()
if err != nil {
@@ -54,7 +54,7 @@ func TestLoad_HTTPBaseURLYieldsInsecureCookies(t *testing.T) {
func TestLoad_MissingRequiredOIDCVars(t *testing.T) {
cases := []string{
"GENIUSRUN_PUBLIC_BASE_URL",
"GENIUSRUN_BACKEND_URL",
"GENIUSRUN_OIDC_ISSUER_URL",
"GENIUSRUN_OIDC_CLIENT_ID",
"GENIUSRUN_OIDC_CLIENT_SECRET",
@@ -134,7 +134,7 @@ func TestLoad_GarminPythonPathExplicitOverridesDefault(t *testing.T) {
}
}
func TestLoad_FrontendURLDefaultsToPublicBaseURL(t *testing.T) {
func TestLoad_FrontendURLDefaultsToBackendURL(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GENIUSRUN_FRONTEND_URL", "")
@@ -142,8 +142,8 @@ func TestLoad_FrontendURLDefaultsToPublicBaseURL(t *testing.T) {
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)
if cfg.FrontendURL != cfg.BackendURL {
t.Errorf("FrontendURL = %q, want it to default to BackendURL %q", cfg.FrontendURL, cfg.BackendURL)
}
}