M0: add department_id JWT claim, rewrite RLS to avoid profiles queries

auth-hook.sql now copies department_id into JWT claims alongside
role/org_id, so RLS can scope Staff reads without joining profiles
from another table's policy. policies.sql: sops/sop_versions Staff
read policies now use the department_id claim directly; every create
policy paired with a matching drop policy if exists so the file is
safe to re-run. Folds in the supabase_auth_admin profiles-select grant
so policies.sql is self-contained.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcktbLXSSXzx23GCue813e
This commit is contained in:
Weei Han
2026-07-30 20:38:38 +08:00
co-authored by Claude Sonnet 5
parent f539d43136
commit 1b3ba67468
2 changed files with 56 additions and 7 deletions
+11 -3
View File
@@ -3,6 +3,11 @@
-- "A Postgres function copies role/org_id into JWT claims (custom
-- access token hook) so RLS can check them cheaply."
--
-- Also copies department_id — needed so RLS policies can scope Staff
-- reads to "published + assigned to their department" (04-database-
-- schema.md section 3) using JWT claims only, without any policy ever
-- querying the profiles table from another table's policy.
--
-- Run this AFTER schema.sql (needs the profiles table to exist).
-- After running, enable the hook in the Supabase dashboard (see bottom
-- of this file for the exact steps) — running the SQL alone does not
@@ -17,10 +22,11 @@ as $$
claims jsonb;
user_role public.user_role;
user_org_id uuid;
user_department_id uuid;
begin
-- Look up this user's role and org_id from profiles.
select role, org_id
into user_role, user_org_id
-- Look up this user's role, org_id, and department_id from profiles.
select role, org_id, department_id
into user_role, user_org_id, user_department_id
from public.profiles
where id = (event->>'user_id')::uuid;
@@ -29,9 +35,11 @@ as $$
if user_role is not null then
claims := jsonb_set(claims, '{role}', to_jsonb(user_role));
claims := jsonb_set(claims, '{org_id}', to_jsonb(user_org_id));
claims := jsonb_set(claims, '{department_id}', to_jsonb(user_department_id));
else
claims := jsonb_set(claims, '{role}', 'null');
claims := jsonb_set(claims, '{org_id}', 'null');
claims := jsonb_set(claims, '{department_id}', 'null');
end if;
event := jsonb_set(event, '{claims}', claims);