From 3364cf7ac3f700418d8bda081d626434a5229d87 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 3 Jun 2026 00:05:25 +0200 Subject: [PATCH] feat(serve): wire Dex OIDC into serve when configured, else StubAuth serve now uses oidc.DexAuth (single-user allowlist authz, ADR-011) when TAPIR_OIDC_ISSUER is set, falling back to allow-all StubAuth for local dev. Adds the Dex config fields (TAPIR_OIDC_ISSUER/DEX_CLIENT_ID/SECRET/ OIDC_REDIRECT_URL/SESSION_SECRET/ALLOWED_SUBJECT) + Config.DexConfigured(). Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/tapir/main.go | 27 +++++++++++++++++++++++---- internal/config/config.go | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/cmd/tapir/main.go b/cmd/tapir/main.go index 195d76f..2a5af41 100644 --- a/cmd/tapir/main.go +++ b/cmd/tapir/main.go @@ -32,6 +32,7 @@ import ( "gitea.d-ma.be/mathias/tapir/internal/runner" "gitea.d-ma.be/mathias/tapir/internal/usecase" "gitea.d-ma.be/mathias/tapir/internal/web" + "gitea.d-ma.be/mathias/tapir/internal/web/oidc" ) func main() { @@ -162,10 +163,28 @@ func cmdServe(ctx context.Context, log *slog.Logger) error { } defer st.Close() - // Auth seam: StubAuth allows every request as the configured user. The - // Conductor replaces this with oidc.DexAuth (lane B) at merge — nothing else - // in this function or the handlers changes (handlers depend on web.Auth only). - var authn web.Auth = web.StubAuth{U: web.User{Subject: cfg.UserID}} + // Auth seam (handlers depend on web.Auth only). With Dex configured + // (TAPIR_OIDC_ISSUER set) serve uses real OIDC login with single-user + // allowlist authz (ADR-011); otherwise it falls back to the allow-all + // StubAuth for local dev — never expose StubAuth publicly. + var authn web.Auth + if cfg.DexConfigured() { + authn, err = oidc.New(ctx, oidc.Config{ + Issuer: cfg.OIDCIssuer, + ClientID: cfg.DexClientID, + ClientSecret: cfg.DexClientSecret, + RedirectURL: cfg.OIDCRedirectURL, + SessionSecret: cfg.SessionSecret, + AllowedSubject: cfg.AllowedSubject, + }) + if err != nil { + return fmt.Errorf("dex oidc: %w", err) + } + log.Info("web auth: dex oidc", "issuer", cfg.OIDCIssuer) + } else { + authn = web.StubAuth{U: web.User{Subject: cfg.UserID}} + log.Warn("web auth: STUB allow-all (no TAPIR_OIDC_ISSUER) — local dev only, do not expose") + } app := &web.App{Store: st, Auth: authn, UserID: cfg.UserID, Log: log} srv := &http.Server{ diff --git a/internal/config/config.go b/internal/config/config.go index 03a3349..da0ca84 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -56,8 +56,22 @@ type Config struct { // HTTPAddr is the listen address for `tapir serve` (the Stage-0 web UI). HTTPAddr string + + // Dex OIDC (web login, ADR-011). When OIDCIssuer is empty, `serve` falls back + // to the allow-all StubAuth (local dev). When set, serve uses Dex with + // single-user allowlist authz. + OIDCIssuer string + DexClientID string + DexClientSecret string + OIDCRedirectURL string + SessionSecret string + AllowedSubject string } +// DexConfigured reports whether Dex OIDC login is wired (issuer present). When +// false, `serve` uses StubAuth (dev only). +func (c Config) DexConfigured() bool { return strings.TrimSpace(c.OIDCIssuer) != "" } + // Defaults (see docs/homelab-integration.md). All overridable via env. const ( defaultGatewayURL = "http://koala:30401/v1" @@ -85,6 +99,12 @@ func Load() (Config, error) { SecretsFile: envOr("TAPIR_SECRETS_FILE", defaultSecretsFile()), OAuthRedirectAddr: envOr("TAPIR_OAUTH_REDIRECT_ADDR", defaultOAuthRedirectAddr), HTTPAddr: envOr("TAPIR_HTTP_ADDR", defaultHTTPAddr), + OIDCIssuer: os.Getenv("TAPIR_OIDC_ISSUER"), + DexClientID: os.Getenv("TAPIR_DEX_CLIENT_ID"), + DexClientSecret: os.Getenv("TAPIR_DEX_CLIENT_SECRET"), + OIDCRedirectURL: os.Getenv("TAPIR_OIDC_REDIRECT_URL"), + SessionSecret: os.Getenv("TAPIR_SESSION_SECRET"), + AllowedSubject: os.Getenv("TAPIR_ALLOWED_SUBJECT"), } timeout, err := durationOr("TAPIR_SUMMARIZER_TIMEOUT", defaultSummarizerTimeout)