@kontrolia/db 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
-- A "platform admin" needs to see across every organization (support/ops
|
|
2
|
+
-- consoles, cross-tenant monitoring) — something the rest of the model
|
|
3
|
+
-- deliberately can't do, since roles/permissions are always scoped to one
|
|
4
|
+
-- active organization at a time (see custom_access_token_hook). Rather than
|
|
5
|
+
-- have every app invent its own "<app>.admin.ver_todo" escape hatch, this
|
|
6
|
+
-- is one reserved claim outside the app-permission-key namespace, backed by
|
|
7
|
+
-- its own table so it's a single source of truth across the whole install.
|
|
8
|
+
--
|
|
9
|
+
-- No self-service UI/API for granting this on purpose — same "manual step
|
|
10
|
+
-- for now" pattern as application_organizations enablement. Grant with:
|
|
11
|
+
-- insert into kontrolia.platform_admins (user_id) values ('<user-uuid>');
|
|
12
|
+
|
|
13
|
+
create table kontrolia.platform_admins (
|
|
14
|
+
user_id uuid primary key references auth.users (id) on delete cascade,
|
|
15
|
+
granted_by uuid references auth.users (id) on delete set null,
|
|
16
|
+
granted_at timestamptz not null default now()
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
comment on table kontrolia.platform_admins is 'Users who can see across every organization — a single reserved claim (is_platform_admin), not an app-defined permission. Granted by inserting directly, no UI yet.';
|
|
20
|
+
|
|
21
|
+
alter table kontrolia.platform_admins enable row level security;
|
|
22
|
+
|
|
23
|
+
-- Only platform admins can even see who else is one — nobody self-grants
|
|
24
|
+
-- via RLS (writes require direct database access, same as this table's own
|
|
25
|
+
-- comment says).
|
|
26
|
+
create policy "platform admins can view the platform admin list" on kontrolia.platform_admins
|
|
27
|
+
for select using (exists (select 1 from kontrolia.platform_admins pa where pa.user_id = auth.uid()));
|
|
28
|
+
|
|
29
|
+
grant select on kontrolia.platform_admins to authenticated;
|
|
30
|
+
|
|
31
|
+
create or replace function kontrolia.custom_access_token_hook(event jsonb)
|
|
32
|
+
returns jsonb
|
|
33
|
+
language plpgsql
|
|
34
|
+
stable
|
|
35
|
+
security definer
|
|
36
|
+
set search_path = ''
|
|
37
|
+
as $$
|
|
38
|
+
declare
|
|
39
|
+
claims jsonb;
|
|
40
|
+
target_user_id uuid;
|
|
41
|
+
active_org_id uuid;
|
|
42
|
+
active_membership_id uuid;
|
|
43
|
+
role_names text[];
|
|
44
|
+
permission_keys text[];
|
|
45
|
+
platform_admin boolean;
|
|
46
|
+
begin
|
|
47
|
+
claims := coalesce(event->'claims', '{}'::jsonb);
|
|
48
|
+
target_user_id := (event->>'user_id')::uuid;
|
|
49
|
+
|
|
50
|
+
select exists(select 1 from kontrolia.platform_admins where user_id = target_user_id) into platform_admin;
|
|
51
|
+
|
|
52
|
+
select active_organization_id into active_org_id
|
|
53
|
+
from kontrolia.sessions_context
|
|
54
|
+
where user_id = target_user_id;
|
|
55
|
+
|
|
56
|
+
if active_org_id is null then
|
|
57
|
+
select organization_id into active_org_id
|
|
58
|
+
from kontrolia.memberships
|
|
59
|
+
where user_id = target_user_id and status = 'active'
|
|
60
|
+
order by created_at asc
|
|
61
|
+
limit 1;
|
|
62
|
+
end if;
|
|
63
|
+
|
|
64
|
+
if active_org_id is not null then
|
|
65
|
+
select id into active_membership_id
|
|
66
|
+
from kontrolia.memberships
|
|
67
|
+
where user_id = target_user_id
|
|
68
|
+
and organization_id = active_org_id
|
|
69
|
+
and status = 'active';
|
|
70
|
+
end if;
|
|
71
|
+
|
|
72
|
+
if active_membership_id is not null then
|
|
73
|
+
select coalesce(array_agg(distinct r.slug), '{}')
|
|
74
|
+
into role_names
|
|
75
|
+
from kontrolia.membership_roles mr
|
|
76
|
+
join kontrolia.roles r on r.id = mr.role_id
|
|
77
|
+
where mr.membership_id = active_membership_id;
|
|
78
|
+
|
|
79
|
+
select coalesce(array_agg(distinct p.key), '{}')
|
|
80
|
+
into permission_keys
|
|
81
|
+
from (
|
|
82
|
+
select p.id, p.key
|
|
83
|
+
from kontrolia.membership_roles mr
|
|
84
|
+
join kontrolia.role_permissions rp on rp.role_id = mr.role_id
|
|
85
|
+
join kontrolia.permissions p on p.id = rp.permission_id
|
|
86
|
+
where mr.membership_id = active_membership_id
|
|
87
|
+
union
|
|
88
|
+
select p.id, p.key
|
|
89
|
+
from kontrolia.user_permissions up
|
|
90
|
+
join kontrolia.permissions p on p.id = up.permission_id
|
|
91
|
+
where up.membership_id = active_membership_id and up.effect = 'allow'
|
|
92
|
+
) p
|
|
93
|
+
where not exists (
|
|
94
|
+
select 1 from kontrolia.user_permissions up_deny
|
|
95
|
+
where up_deny.membership_id = active_membership_id
|
|
96
|
+
and up_deny.permission_id = p.id
|
|
97
|
+
and up_deny.effect = 'deny'
|
|
98
|
+
);
|
|
99
|
+
else
|
|
100
|
+
role_names := '{}';
|
|
101
|
+
permission_keys := '{}';
|
|
102
|
+
end if;
|
|
103
|
+
|
|
104
|
+
-- to_jsonb(NULL::uuid) is SQL NULL, not a jsonb null — and jsonb_set is
|
|
105
|
+
-- strict, so passing it straight through would collapse the whole
|
|
106
|
+
-- function's result to NULL for any user without an organization yet
|
|
107
|
+
-- (i.e. every brand-new signup). coalesce() converts that to a real
|
|
108
|
+
-- jsonb null instead.
|
|
109
|
+
claims := jsonb_set(claims, '{organization_id}', coalesce(to_jsonb(active_org_id), 'null'::jsonb));
|
|
110
|
+
claims := jsonb_set(claims, '{roles}', to_jsonb(coalesce(role_names, '{}')));
|
|
111
|
+
claims := jsonb_set(claims, '{permissions}', to_jsonb(coalesce(permission_keys, '{}')));
|
|
112
|
+
claims := jsonb_set(claims, '{is_platform_admin}', to_jsonb(coalesce(platform_admin, false)));
|
|
113
|
+
|
|
114
|
+
event := jsonb_set(event, '{claims}', claims);
|
|
115
|
+
return event;
|
|
116
|
+
end;
|
|
117
|
+
$$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontrolia/db",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "KontrolIA Auth database layer: SQL migrations for the `kontrolia` schema (organizations, RBAC, Custom Access Token Hook) and a connection-string-agnostic migration runner.",
|
|
6
6
|
"keywords": [
|