fix(auth): also default role/aud on auth.users UPDATE, not just INSERT

GoTrue v2.158.1 emits an UPDATE right after inserting an OIDC-provisioned
user (pop ORM resends the full struct incl. the zero-value role=''),
which bypassed migration 013's BEFORE INSERT trigger and left prod users
with role='' — breaking every PostgREST call with `role "" does not exist`.
Add a matching BEFORE UPDATE trigger reusing the same guard function, and
re-backfill the rows already clobbered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 15:51:21 +02:00
parent 768b3cda58
commit 9067ab8f0c

View File

@@ -0,0 +1,33 @@
-- Migration 014: extend the role/aud default to UPDATEs on auth.users.
--
-- Migration 013 installed a BEFORE INSERT trigger so that OIDC-provisioned
-- rows get role='authenticated'. That fixes the INSERT step, but GoTrue
-- v2.158.1 emits an UPDATE statement immediately after the insert (setting
-- last_sign_in_at etc.) whose pop-ORM payload re-sends every field of the
-- user struct — including role, which is still the Go zero value ("") at
-- that point. The UPDATE silently resets role back to empty, and since our
-- previous trigger was INSERT-only it did not intervene.
--
-- Evidence on prod (ambrosio) as of 2026-04-20:
-- * `ensure_role_default` BEFORE INSERT trigger present and enabled.
-- * Direct `INSERT ... role=''` → trigger fills 'authenticated' (good).
-- * Direct `UPDATE ... SET role=''` → bypasses trigger (bad).
-- * Two recent self-registrations left with role='' and
-- created_at/updated_at ~150250ms apart, confirming the UPDATE step.
--
-- Fix: same logic as 013, but BEFORE UPDATE. Idempotent; safe to re-run.
-- Also re-backfill any rows that were already clobbered.
-- ── 1. Re-backfill rows affected after 013 ──────────────────────────────
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. BEFORE UPDATE trigger reusing the same function ─────────────────
DROP TRIGGER IF EXISTS ensure_role_default_update ON auth.users;
CREATE TRIGGER ensure_role_default_update
BEFORE UPDATE ON auth.users
FOR EACH ROW
EXECUTE FUNCTION auth.ensure_user_role_default();