Found on ambrosio after the first real self-registration: GoTrue v2.158.1 persists OIDC-provisioned users with empty-string role and aud. PostgREST then dies with `role "" does not exist` on every REST call because its per-request `SET LOCAL role = $claim` can't resolve ''. Dev never caught this because seed.sql hardcodes both columns. - supabase/migrations/013_auth_users_role_default.sql: backfill existing rows + BEFORE INSERT trigger on auth.users that fills empty/NULL role + aud with 'authenticated'. Idempotent, preserves explicit values. - supabase/tests/008_auth_user_role_default.sql: pgTAP coverage — function + trigger exist, backfill left nothing empty, empty-string and NULL values get defaulted, explicit non-empty role is preserved. - CLAUDE.md: status line updated to 255 tests (was 248), new gotcha #19 documenting the GoTrue quirk + that users in existing sessions must re-login once for a fresh JWT after the fix lands on any given env. Total: 41 pgTAP (was 34) + 140 integration + 15 unit + 58 Playwright + 1 gated rate-limit. Full `just test-all` green.
47 lines
1.9 KiB
PL/PgSQL
47 lines
1.9 KiB
PL/PgSQL
-- Migration 013: default `role` and `aud` to 'authenticated' on auth.users.
|
|
--
|
|
-- Background: GoTrue v2.158.1, when creating a user via the OIDC path
|
|
-- (Keycloak external provider), persists the user with empty-string `role`
|
|
-- and `aud` columns. The JWT it subsequently issues carries the empty role,
|
|
-- and PostgREST's per-request `SET LOCAL role = $role_claim` then fails with
|
|
-- `role "" does not exist` on every REST call — including RPCs.
|
|
--
|
|
-- The seed users in `supabase/seed.sql` hardcode role='authenticated' and
|
|
-- aud='authenticated', which is why dev never surfaced this. Prod (ambrosio)
|
|
-- hit it on the first real self-registration.
|
|
--
|
|
-- Two parts:
|
|
-- 1. Backfill any already-persisted rows missing role/aud.
|
|
-- 2. BEFORE INSERT trigger that fills empty/NULL role + aud with
|
|
-- 'authenticated' so future self-registrations are safe without
|
|
-- requiring a GoTrue upgrade.
|
|
|
|
-- ── 1. Backfill ──────────────────────────────────────────────────────────
|
|
UPDATE auth.users
|
|
SET
|
|
role = COALESCE(NULLIF(role, ''), 'authenticated'),
|
|
aud = COALESCE(NULLIF(aud, ''), 'authenticated')
|
|
WHERE role IS NULL OR role = '' OR aud IS NULL OR aud = '';
|
|
|
|
-- ── 2. Trigger function + trigger ────────────────────────────────────────
|
|
CREATE OR REPLACE FUNCTION auth.ensure_user_role_default()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
IF NEW.role IS NULL OR NEW.role = '' THEN
|
|
NEW.role := 'authenticated';
|
|
END IF;
|
|
IF NEW.aud IS NULL OR NEW.aud = '' THEN
|
|
NEW.aud := 'authenticated';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS ensure_role_default ON auth.users;
|
|
CREATE TRIGGER ensure_role_default
|
|
BEFORE INSERT ON auth.users
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION auth.ensure_user_role_default();
|