SSO per OpenID Connect (Authorization Code + PKCE) zusätzlich zum lokalen Login.
- Regeln: kein Auto-Provisioning (E-Mail muss als User existieren), Rolle aus DB (nie aus Token), lokaler Login+TOTP unangetastet.
- Migration 0040: oidc_settings (Singleton, client_secret_enc via secrets.Box) + users.oidc_subject.
- internal/services/oidc: Settings-Repo (write-only Secret) + lazy go-oidc Client (testbarer Authenticator-Seam).
- internal/handlers/oidc.go: GET/PUT /oidc/settings (admin), GET /auth/oidc/{settings,login,callback}. Flow-State (state/PKCE/nonce) stateless im 5-min signierten HttpOnly-Cookie (SameSite=Lax). email_verified erzwungen, opportunistisches sub-Linking, Session via setSessionCookie+Signer.
- session.SignBlob/VerifyBlob; users.Get/SetOIDCSubject; main.go-Wiring.
- Frontend: App.tsx /auth/me-Bootstrap (für Cookie-Session nach Callback), Login-SSO-Button + sso_error, Settings OIDC-Card, i18n de/en.
- Tests (guarded EG_FWTEST_DSN): Secret-Roundtrip + Callback (Rolle-aus-DB, no_account, disabled, unverified, nonce, state).
Deps: go-oidc/v3, x/oauth2. Scope v1: nur Login (kein SLO/Refresh).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
36 lines
1.4 KiB
SQL
36 lines
1.4 KiB
SQL
-- +goose Up
|
|
-- +goose StatementBegin
|
|
|
|
-- OIDC / Keycloak SSO — Singleton-Settings (analog forward_proxy_settings).
|
|
-- client_secret_enc: secrets.Box.Seal-Output (AES-256-GCM), NULL = nicht gesetzt.
|
|
-- Rolle kommt bewusst NICHT aus dem Token, daher keine group/role-claim-Spalten.
|
|
CREATE TABLE IF NOT EXISTS oidc_settings (
|
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
enabled BOOLEAN NOT NULL DEFAULT false,
|
|
issuer_url TEXT NOT NULL DEFAULT '',
|
|
client_id TEXT NOT NULL DEFAULT '',
|
|
client_secret_enc BYTEA,
|
|
scopes TEXT NOT NULL DEFAULT 'openid email profile',
|
|
email_claim TEXT NOT NULL DEFAULT 'email',
|
|
button_label TEXT NOT NULL DEFAULT 'Sign in with SSO',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
CONSTRAINT oidc_settings_singleton CHECK (id = 1)
|
|
);
|
|
INSERT INTO oidc_settings (id) VALUES (1) ON CONFLICT DO NOTHING;
|
|
|
|
-- Opportunistisches Linking: beim ersten SSO-Login wird der OIDC-'sub'
|
|
-- gespeichert; weicht er später ab, wird der Login abgelehnt. Nullable,
|
|
-- kein Backfill (Match-Schlüssel bleibt die verifizierte E-Mail).
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS oidc_subject TEXT;
|
|
|
|
-- +goose StatementEnd
|
|
|
|
-- +goose Down
|
|
-- +goose StatementBegin
|
|
|
|
ALTER TABLE users DROP COLUMN IF EXISTS oidc_subject;
|
|
DROP TABLE IF EXISTS oidc_settings;
|
|
|
|
-- +goose StatementEnd
|