diff --git a/internal/store/migrations/0019_oidc.sql b/internal/store/migrations/0019_oidc.sql new file mode 100644 index 0000000..1f6cea6 --- /dev/null +++ b/internal/store/migrations/0019_oidc.sql @@ -0,0 +1,35 @@ +-- 0019_oidc.sql +-- +-- OIDC bookkeeping. Three independent additions land in one +-- migration to keep the related changes together: +-- +-- 1. users.auth_source — 'local' | 'oidc'. Local users get +-- the default; first OIDC sign-in JITs +-- a row with auth_source='oidc'. +-- 2. users.oidc_subject — IdP's stable 'sub' claim. Indexed +-- uniquely (partial; NULLs allowed). +-- 3. sessions.id_token — last id_token for OIDC sessions, used +-- as id_token_hint on RP-initiated +-- logout. NULL for local sessions. +-- 4. oidc_state — short-lived state for the OAuth round- +-- trip (state + PKCE code_verifier). +-- Swept on the alert engine tick. +-- +-- All column-level ALTERs (CLAUDE.md preference; safe under +-- foreign_keys=ON). + +ALTER TABLE users ADD COLUMN auth_source TEXT NOT NULL DEFAULT 'local' + CHECK (auth_source IN ('local', 'oidc')); +ALTER TABLE users ADD COLUMN oidc_subject TEXT; + +CREATE UNIQUE INDEX users_oidc_subject ON users(oidc_subject) + WHERE oidc_subject IS NOT NULL; + +ALTER TABLE sessions ADD COLUMN id_token TEXT; + +CREATE TABLE oidc_state ( + state_hash TEXT PRIMARY KEY, -- sha256(state) hex; raw never persisted + code_verifier TEXT NOT NULL, + created_at TEXT NOT NULL +); +CREATE INDEX oidc_state_created ON oidc_state(created_at);