Add the Dex-subject -> tapir-user_id bridge for multi-user Stage 1. Migration 004 creates user_identities (dex_subject PK, user_id UNIQUE FK ON DELETE CASCADE). It is intentionally NOT RLS-enabled: it holds no user data and must be readable BEFORE a user_id is known (the lookup is what yields the id used to set tapir.current_user_id). RLS here would be a chicken-and-egg deadlock; data isolation stays on the user-owned tables. UserBySubject resolves subject -> user_id as a plain pool query (pre-scope, no withUser). RegisterUser generates the UUID app-side (stdlib crypto/rand, no new dep) so the forced-RLS WITH CHECK (id = GUC) passes, then inserts the users row via withUser(newID) and the identity row in the same transaction. Re-registration of a subject errors with ErrSubjectRegistered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
23 lines
1.3 KiB
SQL
23 lines
1.3 KiB
SQL
-- Migration 004: the Dex-subject → tapir-user map (ADR-012 Stage 1, multi-user).
|
|
-- A Dex-authenticated subject is the login identity; the tapir user_id (UUID) is
|
|
-- what every user-owned, force-RLS table keys off. This table is the bridge:
|
|
-- resolve subject → user_id here (auth plumbing, pre-scope), THEN scope all data
|
|
-- access by that id via the store's withUser helper.
|
|
--
|
|
-- INTENTIONALLY NOT RLS-ENABLED. The forced-RLS isolation (migration 003) guards
|
|
-- the user-OWNED data tables. user_identities holds no user data — only an opaque
|
|
-- (dex_subject ↔ user_id) pair — and must be readable BEFORE a user_id is known
|
|
-- (that lookup is what yields the id used to set tapir.current_user_id). Putting
|
|
-- RLS here would be a chicken-and-egg deadlock (you'd need the GUC to read the row
|
|
-- that tells you the GUC). Data isolation lives on the user-owned tables, not here.
|
|
CREATE TABLE user_identities (
|
|
dex_subject TEXT PRIMARY KEY,
|
|
user_id UUID NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE user_identities IS
|
|
'Dex subject -> tapir user_id map. Auth plumbing, deliberately NOT RLS-enabled '
|
|
'(no user data; must be read pre-scope to resolve the id used for RLS). '
|
|
'ON DELETE CASCADE so deleting a user cleans up its identity mapping.';
|