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:
co-authored by
Claude Sonnet 5
parent
f539d43136
commit
1b3ba67468
+45
-4
@@ -24,12 +24,19 @@
|
||||
-- access via RLS is only granted where the matrix explicitly says so
|
||||
-- (insert own, read/write, etc). Everything else is mutated exclusively
|
||||
-- by service-role /api routes, which bypass RLS entirely.
|
||||
--
|
||||
-- All checks below read role, org_id, and department_id from JWT claims
|
||||
-- (auth.jwt() ->> '...') set by db/auth-hook.sql. No policy in this file
|
||||
-- ever queries the profiles table — the sops/sop_versions staff-scoping
|
||||
-- checks use the department_id claim directly instead of joining
|
||||
-- profiles, avoiding any cross-table RLS dependency.
|
||||
|
||||
-- ============================================================
|
||||
-- orgs (not in matrix — read own org only, no client write)
|
||||
-- ============================================================
|
||||
alter table orgs enable row level security;
|
||||
|
||||
drop policy if exists orgs_read on orgs;
|
||||
create policy orgs_read on orgs
|
||||
for select using (
|
||||
id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -41,23 +48,27 @@ create policy orgs_read on orgs
|
||||
-- ============================================================
|
||||
alter table departments enable row level security;
|
||||
|
||||
drop policy if exists departments_read on departments;
|
||||
create policy departments_read on departments
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
);
|
||||
|
||||
drop policy if exists departments_insert_admin on departments;
|
||||
create policy departments_insert_admin on departments
|
||||
for insert with check (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
and (auth.jwt() ->> 'role') = 'admin'
|
||||
);
|
||||
|
||||
drop policy if exists departments_update_admin on departments;
|
||||
create policy departments_update_admin on departments
|
||||
for update using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
and (auth.jwt() ->> 'role') = 'admin'
|
||||
);
|
||||
|
||||
drop policy if exists departments_delete_admin on departments;
|
||||
create policy departments_delete_admin on departments
|
||||
for delete using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -74,11 +85,25 @@ create policy departments_delete_admin on departments
|
||||
-- ============================================================
|
||||
alter table profiles enable row level security;
|
||||
|
||||
-- CRITICAL: the JWT claims hook (db/auth-hook.sql) reads role/org_id/
|
||||
-- department_id from this table on every login/token refresh, running
|
||||
-- as supabase_auth_admin. Enabling RLS above blocks that read unless
|
||||
-- this policy exists — without it, the hook silently returns null
|
||||
-- claims and every other policy in this file fails closed. This grant
|
||||
-- + policy is idempotent (grant is safe to re-run; the policy already
|
||||
-- exists from db/auth-hook.sql, not recreated here to avoid a
|
||||
-- duplicate-policy error).
|
||||
grant select
|
||||
on table public.profiles
|
||||
to supabase_auth_admin;
|
||||
|
||||
drop policy if exists profiles_read on profiles;
|
||||
create policy profiles_read on profiles
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
);
|
||||
|
||||
drop policy if exists profiles_update_admin on profiles;
|
||||
create policy profiles_update_admin on profiles
|
||||
for update using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -92,6 +117,7 @@ create policy profiles_update_admin on profiles
|
||||
-- ============================================================
|
||||
alter table sops enable row level security;
|
||||
|
||||
drop policy if exists sops_read on sops;
|
||||
create policy sops_read on sops
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -103,20 +129,21 @@ create policy sops_read on sops
|
||||
and exists (
|
||||
select 1
|
||||
from sop_assignments sa
|
||||
join profiles p on p.department_id = sa.department_id
|
||||
where sa.sop_id = sops.id
|
||||
and p.id = auth.uid()
|
||||
and sa.department_id = (auth.jwt() ->> 'department_id')::uuid
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
drop policy if exists sops_insert_editor on sops;
|
||||
create policy sops_insert_editor on sops
|
||||
for insert with check (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
and (auth.jwt() ->> 'role') in ('editor','admin')
|
||||
);
|
||||
|
||||
drop policy if exists sops_update_editor on sops;
|
||||
create policy sops_update_editor on sops
|
||||
for update using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -132,6 +159,7 @@ create policy sops_update_editor on sops
|
||||
-- ============================================================
|
||||
alter table sop_versions enable row level security;
|
||||
|
||||
drop policy if exists sop_versions_read on sop_versions;
|
||||
create policy sop_versions_read on sop_versions
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -142,9 +170,8 @@ create policy sop_versions_read on sop_versions
|
||||
and exists (
|
||||
select 1
|
||||
from sop_assignments sa
|
||||
join profiles p on p.department_id = sa.department_id
|
||||
where sa.sop_id = sop_versions.sop_id
|
||||
and p.id = auth.uid()
|
||||
and sa.department_id = (auth.jwt() ->> 'department_id')::uuid
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -155,12 +182,14 @@ create policy sop_versions_read on sop_versions
|
||||
-- ============================================================
|
||||
alter table approvals enable row level security;
|
||||
|
||||
drop policy if exists approvals_read on approvals;
|
||||
create policy approvals_read on approvals
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
and (auth.jwt() ->> 'role') in ('editor','approver','admin')
|
||||
);
|
||||
|
||||
drop policy if exists approvals_insert on approvals;
|
||||
create policy approvals_insert on approvals
|
||||
for insert with check (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -174,6 +203,7 @@ create policy approvals_insert on approvals
|
||||
-- ============================================================
|
||||
alter table sop_assignments enable row level security;
|
||||
|
||||
drop policy if exists sop_assignments_read on sop_assignments;
|
||||
create policy sop_assignments_read on sop_assignments
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -185,12 +215,14 @@ create policy sop_assignments_read on sop_assignments
|
||||
-- ============================================================
|
||||
alter table acknowledgements enable row level security;
|
||||
|
||||
drop policy if exists ack_insert_own on acknowledgements;
|
||||
create policy ack_insert_own on acknowledgements
|
||||
for insert with check (
|
||||
user_id = auth.uid()
|
||||
and org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
);
|
||||
|
||||
drop policy if exists ack_read on acknowledgements;
|
||||
create policy ack_read on acknowledgements
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -203,17 +235,20 @@ create policy ack_read on acknowledgements
|
||||
-- ============================================================
|
||||
alter table sop_translations enable row level security;
|
||||
|
||||
drop policy if exists translations_read on sop_translations;
|
||||
create policy translations_read on sop_translations
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
);
|
||||
|
||||
drop policy if exists translations_insert_editor on sop_translations;
|
||||
create policy translations_insert_editor on sop_translations
|
||||
for insert with check (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
and (auth.jwt() ->> 'role') in ('editor','admin')
|
||||
);
|
||||
|
||||
drop policy if exists translations_update_editor on sop_translations;
|
||||
create policy translations_update_editor on sop_translations
|
||||
for update using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -226,12 +261,14 @@ create policy translations_update_editor on sop_translations
|
||||
-- ============================================================
|
||||
alter table audit_log enable row level security;
|
||||
|
||||
drop policy if exists audit_read on audit_log;
|
||||
create policy audit_read on audit_log
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
and (auth.jwt() ->> 'role') in ('approver','admin')
|
||||
);
|
||||
|
||||
drop policy if exists audit_insert on audit_log;
|
||||
create policy audit_insert on audit_log
|
||||
for insert with check (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -243,6 +280,7 @@ create policy audit_insert on audit_log
|
||||
-- ============================================================
|
||||
alter table ai_log enable row level security;
|
||||
|
||||
drop policy if exists ai_log_read_admin on ai_log;
|
||||
create policy ai_log_read_admin on ai_log
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -255,12 +293,14 @@ create policy ai_log_read_admin on ai_log
|
||||
-- ============================================================
|
||||
alter table incidents enable row level security;
|
||||
|
||||
drop policy if exists incidents_insert_own on incidents;
|
||||
create policy incidents_insert_own on incidents
|
||||
for insert with check (
|
||||
reporter_id = auth.uid()
|
||||
and org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
);
|
||||
|
||||
drop policy if exists incidents_read on incidents;
|
||||
create policy incidents_read on incidents
|
||||
for select using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
@@ -268,6 +308,7 @@ create policy incidents_read on incidents
|
||||
or (auth.jwt() ->> 'role') in ('editor','approver','admin') )
|
||||
);
|
||||
|
||||
drop policy if exists incidents_update on incidents;
|
||||
create policy incidents_update on incidents
|
||||
for update using (
|
||||
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||
|
||||
Reference in New Issue
Block a user