@sunboyoo/supabase-rbac 1.0.3 → 1.0.4
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,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Fix: ensure GUC keys used in rbac.has_perm always start with a letter.
|
|
3
|
+
Postgres requires each dot-separated GUC segment to start with [A-Za-z_].
|
|
4
|
+
Prefixing the md5 with 'h' avoids invalid configuration parameter name errors.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
create or replace function rbac.has_perm(target_app_id text, required_perm_name text)
|
|
8
|
+
returns boolean
|
|
9
|
+
language plpgsql
|
|
10
|
+
stable
|
|
11
|
+
security definer
|
|
12
|
+
set search_path = ''
|
|
13
|
+
as $$
|
|
14
|
+
declare
|
|
15
|
+
_res_key text := 'rbac.res.h' || md5(coalesce(target_app_id,'') || '|' || coalesce(required_perm_name,''));
|
|
16
|
+
_t_roles_key text := 'rbac.rids.h' || md5('t|' || coalesce(target_app_id,''));
|
|
17
|
+
_g_roles_key text := 'rbac.rids.h' || md5('g|global');
|
|
18
|
+
_t_pid_key text := 'rbac.pid.h' || md5('t|' || coalesce(target_app_id,'') || '|' || coalesce(required_perm_name,''));
|
|
19
|
+
_g_pid_key text := 'rbac.pid.h' || md5('g|global|' || coalesce(required_perm_name,''));
|
|
20
|
+
|
|
21
|
+
_target_role_ids uuid[];
|
|
22
|
+
_global_role_ids uuid[];
|
|
23
|
+
|
|
24
|
+
_target_pid uuid;
|
|
25
|
+
_global_pid uuid;
|
|
26
|
+
|
|
27
|
+
_sentinel constant uuid := '00000000-0000-0000-0000-000000000000'::uuid;
|
|
28
|
+
_granted boolean := false;
|
|
29
|
+
|
|
30
|
+
_jwt jsonb;
|
|
31
|
+
begin
|
|
32
|
+
-- Level 1: final decision cache
|
|
33
|
+
if current_setting(_res_key, true) is not null then
|
|
34
|
+
return current_setting(_res_key) = 'true';
|
|
35
|
+
end if;
|
|
36
|
+
|
|
37
|
+
_jwt := auth.jwt();
|
|
38
|
+
|
|
39
|
+
-- Level 2: cache role_ids(uuid[]) for target app (never 500; fail-closed)
|
|
40
|
+
if current_setting(_t_roles_key, true) is null then
|
|
41
|
+
begin
|
|
42
|
+
select coalesce(array_agg((x.value)::uuid), '{}'::uuid[])
|
|
43
|
+
into _target_role_ids
|
|
44
|
+
from jsonb_array_elements_text(
|
|
45
|
+
coalesce(_jwt #> array['app_metadata','role_ids', target_app_id], '[]'::jsonb)
|
|
46
|
+
) as x(value);
|
|
47
|
+
exception when others then
|
|
48
|
+
_target_role_ids := '{}'::uuid[];
|
|
49
|
+
end;
|
|
50
|
+
perform set_config(_t_roles_key, _target_role_ids::text, true);
|
|
51
|
+
else
|
|
52
|
+
begin
|
|
53
|
+
_target_role_ids := current_setting(_t_roles_key)::uuid[];
|
|
54
|
+
exception when others then
|
|
55
|
+
_target_role_ids := '{}'::uuid[];
|
|
56
|
+
end;
|
|
57
|
+
end if;
|
|
58
|
+
|
|
59
|
+
-- Level 2b: cache role_ids(uuid[]) for global
|
|
60
|
+
if current_setting(_g_roles_key, true) is null then
|
|
61
|
+
begin
|
|
62
|
+
select coalesce(array_agg((x.value)::uuid), '{}'::uuid[])
|
|
63
|
+
into _global_role_ids
|
|
64
|
+
from jsonb_array_elements_text(
|
|
65
|
+
coalesce(_jwt #> array['app_metadata','role_ids','global'], '[]'::jsonb)
|
|
66
|
+
) as x(value);
|
|
67
|
+
exception when others then
|
|
68
|
+
_global_role_ids := '{}'::uuid[];
|
|
69
|
+
end;
|
|
70
|
+
perform set_config(_g_roles_key, _global_role_ids::text, true);
|
|
71
|
+
else
|
|
72
|
+
begin
|
|
73
|
+
_global_role_ids := current_setting(_g_roles_key)::uuid[];
|
|
74
|
+
exception when others then
|
|
75
|
+
_global_role_ids := '{}'::uuid[];
|
|
76
|
+
end;
|
|
77
|
+
end if;
|
|
78
|
+
|
|
79
|
+
-- Level 3: permission_id cache for target app (sentinel if not found)
|
|
80
|
+
if current_setting(_t_pid_key, true) is null then
|
|
81
|
+
select p.id
|
|
82
|
+
into _target_pid
|
|
83
|
+
from rbac.permissions p
|
|
84
|
+
where p.app_id = target_app_id
|
|
85
|
+
and p.name = required_perm_name;
|
|
86
|
+
|
|
87
|
+
_target_pid := coalesce(_target_pid, _sentinel);
|
|
88
|
+
perform set_config(_t_pid_key, _target_pid::text, true);
|
|
89
|
+
else
|
|
90
|
+
_target_pid := current_setting(_t_pid_key)::uuid;
|
|
91
|
+
end if;
|
|
92
|
+
|
|
93
|
+
-- Level 3b: permission_id cache for global mirror (sentinel if not found)
|
|
94
|
+
if current_setting(_g_pid_key, true) is null then
|
|
95
|
+
select p.id
|
|
96
|
+
into _global_pid
|
|
97
|
+
from rbac.permissions p
|
|
98
|
+
where p.app_id = 'global'
|
|
99
|
+
and p.name = required_perm_name;
|
|
100
|
+
|
|
101
|
+
_global_pid := coalesce(_global_pid, _sentinel);
|
|
102
|
+
perform set_config(_g_pid_key, _global_pid::text, true);
|
|
103
|
+
else
|
|
104
|
+
_global_pid := current_setting(_g_pid_key)::uuid;
|
|
105
|
+
end if;
|
|
106
|
+
|
|
107
|
+
-- Path A: target roles ↔ target permission
|
|
108
|
+
if _target_pid <> _sentinel and cardinality(_target_role_ids) > 0 then
|
|
109
|
+
select exists (
|
|
110
|
+
select 1
|
|
111
|
+
from rbac.role_permissions rp
|
|
112
|
+
where rp.app_id = target_app_id
|
|
113
|
+
and rp.permission_id = _target_pid
|
|
114
|
+
and rp.role_id = any(_target_role_ids)
|
|
115
|
+
) into _granted;
|
|
116
|
+
end if;
|
|
117
|
+
|
|
118
|
+
-- Path B: global roles ↔ global mirrored permission
|
|
119
|
+
if not _granted and _global_pid <> _sentinel and cardinality(_global_role_ids) > 0 then
|
|
120
|
+
select exists (
|
|
121
|
+
select 1
|
|
122
|
+
from rbac.role_permissions rp
|
|
123
|
+
where rp.app_id = 'global'
|
|
124
|
+
and rp.permission_id = _global_pid
|
|
125
|
+
and rp.role_id = any(_global_role_ids)
|
|
126
|
+
) into _granted;
|
|
127
|
+
end if;
|
|
128
|
+
|
|
129
|
+
perform set_config(_res_key, _granted::text, true);
|
|
130
|
+
return _granted;
|
|
131
|
+
end;
|
|
132
|
+
$$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunboyoo/supabase-rbac",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Industrial-grade RBAC helper for Supabase multi-app projects (JWT role_ids + optional permission RPC + React hooks + pg manager + CLI).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -106,4 +106,4 @@
|
|
|
106
106
|
"access": "public",
|
|
107
107
|
"registry": "https://registry.npmjs.org/"
|
|
108
108
|
}
|
|
109
|
-
}
|
|
109
|
+
}
|