From 9067ab8f0c17bc633f16263e0fe3e2568c3a5084 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Mon, 20 Apr 2026 15:51:21 +0200 Subject: [PATCH] fix(auth): also default role/aud on auth.users UPDATE, not just INSERT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../014_auth_users_role_update_trigger.sql | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 supabase/migrations/014_auth_users_role_update_trigger.sql 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();