diff --git a/supabase/migrations/014_auth_users_role_update_trigger.sql b/supabase/migrations/014_auth_users_role_update_trigger.sql new file mode 100644 index 0000000..f21f367 --- /dev/null +++ b/supabase/migrations/014_auth_users_role_update_trigger.sql @@ -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 ~150–250ms 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();