@sunboyoo/supabase-rbac 1.0.2
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.
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/bin/rbac-init.js +122 -0
- package/dist/chunk-23MRNBGM.js +116 -0
- package/dist/chunk-ZFY3OHWO.js +54 -0
- package/dist/client.cjs +307 -0
- package/dist/client.d.cts +7 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +237 -0
- package/dist/index.cjs +209 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +18 -0
- package/dist/server.cjs +150 -0
- package/dist/server.d.cts +57 -0
- package/dist/server.d.ts +57 -0
- package/dist/server.js +6 -0
- package/dist/shared.cjs +82 -0
- package/dist/shared.d.cts +28 -0
- package/dist/shared.d.ts +28 -0
- package/dist/shared.js +14 -0
- package/dist/types-BayIl-Ha.d.cts +116 -0
- package/dist/types-BayIl-Ha.d.ts +116 -0
- package/migrations/20251229000000_create_rbac_schemas_and_extensions.sql +70 -0
- package/migrations/20251229000001_create_rbac_tables.sql +134 -0
- package/migrations/20251229000002_create_rbac_views.sql +70 -0
- package/migrations/20251229000003_create_rbac_functions_and_hook.sql +246 -0
- package/migrations/20251229000004_setup_rbac_grants_and_hardening.sql +97 -0
- package/migrations/20251229000005_setup_example_app1_orders_rls.sql +102 -0
- package/package.json +105 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
RBACManager: () => RBACManager,
|
|
34
|
+
decodeClaims: () => decodeClaims,
|
|
35
|
+
getMergedRoleIds: () => getMergedRoleIds,
|
|
36
|
+
hasRole: () => hasRole,
|
|
37
|
+
isUuid: () => isUuid,
|
|
38
|
+
uniqueStable: () => uniqueStable
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/shared.ts
|
|
43
|
+
var import_jose = require("jose");
|
|
44
|
+
var IS_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
45
|
+
function isUuid(v) {
|
|
46
|
+
return typeof v === "string" && IS_UUID.test(v);
|
|
47
|
+
}
|
|
48
|
+
function uniqueStable(arr) {
|
|
49
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const x of arr) {
|
|
52
|
+
if (seen.has(x)) continue;
|
|
53
|
+
seen.add(x);
|
|
54
|
+
out.push(x);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function decodeClaims(token) {
|
|
59
|
+
if (!token) return null;
|
|
60
|
+
try {
|
|
61
|
+
return (0, import_jose.decodeJwt)(token);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getMergedRoleIds(params) {
|
|
67
|
+
const { token, appId, includeGlobal = true, globalAppId = "global" } = params;
|
|
68
|
+
const claims = decodeClaims(token);
|
|
69
|
+
const roleMap = claims?.app_metadata?.role_ids ?? {};
|
|
70
|
+
const appRoles = Array.isArray(roleMap[appId]) ? roleMap[appId] : [];
|
|
71
|
+
const gRoles = includeGlobal && Array.isArray(roleMap[globalAppId]) ? roleMap[globalAppId] : [];
|
|
72
|
+
const merged = uniqueStable([...appRoles, ...gRoles]).filter(isUuid);
|
|
73
|
+
const userId = isUuid(claims?.sub) ? claims.sub : null;
|
|
74
|
+
const exp = typeof claims?.exp === "number" ? claims.exp : null;
|
|
75
|
+
return { roleIds: merged, claims, userId, exp };
|
|
76
|
+
}
|
|
77
|
+
function hasRole(userRoleIds, required, mode = "any") {
|
|
78
|
+
if (!required || required.length === 0) return true;
|
|
79
|
+
if (!userRoleIds || userRoleIds.length === 0) return false;
|
|
80
|
+
const set = new Set(userRoleIds);
|
|
81
|
+
if (mode === "all") {
|
|
82
|
+
for (const r of required) if (!set.has(r)) return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
for (const r of required) if (set.has(r)) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/server.ts
|
|
90
|
+
var import_pg = __toESM(require("pg"), 1);
|
|
91
|
+
var { Pool } = import_pg.default;
|
|
92
|
+
function qIdent(ident) {
|
|
93
|
+
return `"${String(ident).replace(/"/g, '""')}"`;
|
|
94
|
+
}
|
|
95
|
+
var RBACManager = class {
|
|
96
|
+
pool;
|
|
97
|
+
schema;
|
|
98
|
+
constructor(opts) {
|
|
99
|
+
this.pool = new Pool({ connectionString: opts.connectionString });
|
|
100
|
+
this.schema = opts.rbacSchema ?? "rbac";
|
|
101
|
+
}
|
|
102
|
+
async close() {
|
|
103
|
+
await this.pool.end();
|
|
104
|
+
}
|
|
105
|
+
/** EN: Run a transaction.
|
|
106
|
+
* 中文:事务封装
|
|
107
|
+
*/
|
|
108
|
+
async tx(fn) {
|
|
109
|
+
const client = await this.pool.connect();
|
|
110
|
+
try {
|
|
111
|
+
await client.query("begin");
|
|
112
|
+
const res = await fn(client);
|
|
113
|
+
await client.query("commit");
|
|
114
|
+
return res;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
await client.query("rollback");
|
|
117
|
+
throw e;
|
|
118
|
+
} finally {
|
|
119
|
+
client.release();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async createApp(params) {
|
|
123
|
+
const s = qIdent(this.schema);
|
|
124
|
+
await this.pool.query(
|
|
125
|
+
`insert into ${s}.apps (id, name) values ($1, $2)
|
|
126
|
+
on conflict (id) do update set name = excluded.name`,
|
|
127
|
+
[params.id, params.name]
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
async createPermission(params) {
|
|
131
|
+
const s = qIdent(this.schema);
|
|
132
|
+
const res = await this.pool.query(
|
|
133
|
+
`insert into ${s}.permissions (app_id, name, description)
|
|
134
|
+
values ($1, $2, $3)
|
|
135
|
+
on conflict (app_id, name) do update set description = excluded.description
|
|
136
|
+
returning id`,
|
|
137
|
+
[params.appId, params.name, params.description ?? null]
|
|
138
|
+
);
|
|
139
|
+
return res.rows[0].id;
|
|
140
|
+
}
|
|
141
|
+
async createRole(params) {
|
|
142
|
+
const s = qIdent(this.schema);
|
|
143
|
+
const res = await this.pool.query(
|
|
144
|
+
`insert into ${s}.roles (app_id, name, description)
|
|
145
|
+
values ($1, $2, $3)
|
|
146
|
+
on conflict (app_id, name) do update set description = excluded.description
|
|
147
|
+
returning id`,
|
|
148
|
+
[params.appId, params.name, params.description ?? null]
|
|
149
|
+
);
|
|
150
|
+
return res.rows[0].id;
|
|
151
|
+
}
|
|
152
|
+
async grantPermissionToRole(params) {
|
|
153
|
+
const s = qIdent(this.schema);
|
|
154
|
+
await this.pool.query(
|
|
155
|
+
`insert into ${s}.role_permissions (app_id, role_id, permission_id)
|
|
156
|
+
values ($1, $2, $3)
|
|
157
|
+
on conflict do nothing`,
|
|
158
|
+
[params.appId, params.roleId, params.permissionId]
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
async revokePermissionFromRole(params) {
|
|
162
|
+
const s = qIdent(this.schema);
|
|
163
|
+
await this.pool.query(
|
|
164
|
+
`delete from ${s}.role_permissions
|
|
165
|
+
where app_id = $1 and role_id = $2 and permission_id = $3`,
|
|
166
|
+
[params.appId, params.roleId, params.permissionId]
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
async assignRole(params) {
|
|
170
|
+
const s = qIdent(this.schema);
|
|
171
|
+
await this.pool.query(
|
|
172
|
+
`insert into ${s}.user_roles (user_id, role_id)
|
|
173
|
+
values ($1, $2)
|
|
174
|
+
on conflict do nothing`,
|
|
175
|
+
[params.userId, params.roleId]
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
async removeRole(params) {
|
|
179
|
+
const s = qIdent(this.schema);
|
|
180
|
+
await this.pool.query(
|
|
181
|
+
`delete from ${s}.user_roles where user_id = $1 and role_id = $2`,
|
|
182
|
+
[params.userId, params.roleId]
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
async getUserRoleIds(params) {
|
|
186
|
+
const s = qIdent(this.schema);
|
|
187
|
+
const includeGlobal = params.includeGlobal ?? true;
|
|
188
|
+
const globalAppId = params.globalAppId ?? "global";
|
|
189
|
+
const appIds = includeGlobal ? [params.appId, globalAppId] : [params.appId];
|
|
190
|
+
const res = await this.pool.query(
|
|
191
|
+
`select r.id as role_id
|
|
192
|
+
from ${s}.user_roles ur
|
|
193
|
+
join ${s}.roles r on r.id = ur.role_id
|
|
194
|
+
where ur.user_id = $1 and r.app_id = any($2::text[])
|
|
195
|
+
order by r.app_id, r.id`,
|
|
196
|
+
[params.userId, appIds]
|
|
197
|
+
);
|
|
198
|
+
return res.rows.map((x) => x.role_id);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
202
|
+
0 && (module.exports = {
|
|
203
|
+
RBACManager,
|
|
204
|
+
decodeClaims,
|
|
205
|
+
getMergedRoleIds,
|
|
206
|
+
hasRole,
|
|
207
|
+
isUuid,
|
|
208
|
+
uniqueStable
|
|
209
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { L as Logger, P as PermissionNameOptions, b as RBACClaims, c as RBACProviderProps, d as RBACState, R as RoleCheckMode, a as RoleIdsByApp, U as UUID } from './types-BayIl-Ha.cjs';
|
|
2
|
+
export { decodeClaims, getMergedRoleIds, hasRole, isUuid, uniqueStable } from './shared.cjs';
|
|
3
|
+
export { RBACManager, RBACManagerOptions } from './server.cjs';
|
|
4
|
+
import 'pg';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { L as Logger, P as PermissionNameOptions, b as RBACClaims, c as RBACProviderProps, d as RBACState, R as RoleCheckMode, a as RoleIdsByApp, U as UUID } from './types-BayIl-Ha.js';
|
|
2
|
+
export { decodeClaims, getMergedRoleIds, hasRole, isUuid, uniqueStable } from './shared.js';
|
|
3
|
+
export { RBACManager, RBACManagerOptions } from './server.js';
|
|
4
|
+
import 'pg';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RBACManager
|
|
3
|
+
} from "./chunk-23MRNBGM.js";
|
|
4
|
+
import {
|
|
5
|
+
decodeClaims,
|
|
6
|
+
getMergedRoleIds,
|
|
7
|
+
hasRole,
|
|
8
|
+
isUuid,
|
|
9
|
+
uniqueStable
|
|
10
|
+
} from "./chunk-ZFY3OHWO.js";
|
|
11
|
+
export {
|
|
12
|
+
RBACManager,
|
|
13
|
+
decodeClaims,
|
|
14
|
+
getMergedRoleIds,
|
|
15
|
+
hasRole,
|
|
16
|
+
isUuid,
|
|
17
|
+
uniqueStable
|
|
18
|
+
};
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/server.ts
|
|
31
|
+
var server_exports = {};
|
|
32
|
+
__export(server_exports, {
|
|
33
|
+
RBACManager: () => RBACManager
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(server_exports);
|
|
36
|
+
var import_pg = __toESM(require("pg"), 1);
|
|
37
|
+
var { Pool } = import_pg.default;
|
|
38
|
+
function qIdent(ident) {
|
|
39
|
+
return `"${String(ident).replace(/"/g, '""')}"`;
|
|
40
|
+
}
|
|
41
|
+
var RBACManager = class {
|
|
42
|
+
pool;
|
|
43
|
+
schema;
|
|
44
|
+
constructor(opts) {
|
|
45
|
+
this.pool = new Pool({ connectionString: opts.connectionString });
|
|
46
|
+
this.schema = opts.rbacSchema ?? "rbac";
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
await this.pool.end();
|
|
50
|
+
}
|
|
51
|
+
/** EN: Run a transaction.
|
|
52
|
+
* 中文:事务封装
|
|
53
|
+
*/
|
|
54
|
+
async tx(fn) {
|
|
55
|
+
const client = await this.pool.connect();
|
|
56
|
+
try {
|
|
57
|
+
await client.query("begin");
|
|
58
|
+
const res = await fn(client);
|
|
59
|
+
await client.query("commit");
|
|
60
|
+
return res;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
await client.query("rollback");
|
|
63
|
+
throw e;
|
|
64
|
+
} finally {
|
|
65
|
+
client.release();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async createApp(params) {
|
|
69
|
+
const s = qIdent(this.schema);
|
|
70
|
+
await this.pool.query(
|
|
71
|
+
`insert into ${s}.apps (id, name) values ($1, $2)
|
|
72
|
+
on conflict (id) do update set name = excluded.name`,
|
|
73
|
+
[params.id, params.name]
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
async createPermission(params) {
|
|
77
|
+
const s = qIdent(this.schema);
|
|
78
|
+
const res = await this.pool.query(
|
|
79
|
+
`insert into ${s}.permissions (app_id, name, description)
|
|
80
|
+
values ($1, $2, $3)
|
|
81
|
+
on conflict (app_id, name) do update set description = excluded.description
|
|
82
|
+
returning id`,
|
|
83
|
+
[params.appId, params.name, params.description ?? null]
|
|
84
|
+
);
|
|
85
|
+
return res.rows[0].id;
|
|
86
|
+
}
|
|
87
|
+
async createRole(params) {
|
|
88
|
+
const s = qIdent(this.schema);
|
|
89
|
+
const res = await this.pool.query(
|
|
90
|
+
`insert into ${s}.roles (app_id, name, description)
|
|
91
|
+
values ($1, $2, $3)
|
|
92
|
+
on conflict (app_id, name) do update set description = excluded.description
|
|
93
|
+
returning id`,
|
|
94
|
+
[params.appId, params.name, params.description ?? null]
|
|
95
|
+
);
|
|
96
|
+
return res.rows[0].id;
|
|
97
|
+
}
|
|
98
|
+
async grantPermissionToRole(params) {
|
|
99
|
+
const s = qIdent(this.schema);
|
|
100
|
+
await this.pool.query(
|
|
101
|
+
`insert into ${s}.role_permissions (app_id, role_id, permission_id)
|
|
102
|
+
values ($1, $2, $3)
|
|
103
|
+
on conflict do nothing`,
|
|
104
|
+
[params.appId, params.roleId, params.permissionId]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
async revokePermissionFromRole(params) {
|
|
108
|
+
const s = qIdent(this.schema);
|
|
109
|
+
await this.pool.query(
|
|
110
|
+
`delete from ${s}.role_permissions
|
|
111
|
+
where app_id = $1 and role_id = $2 and permission_id = $3`,
|
|
112
|
+
[params.appId, params.roleId, params.permissionId]
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
async assignRole(params) {
|
|
116
|
+
const s = qIdent(this.schema);
|
|
117
|
+
await this.pool.query(
|
|
118
|
+
`insert into ${s}.user_roles (user_id, role_id)
|
|
119
|
+
values ($1, $2)
|
|
120
|
+
on conflict do nothing`,
|
|
121
|
+
[params.userId, params.roleId]
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
async removeRole(params) {
|
|
125
|
+
const s = qIdent(this.schema);
|
|
126
|
+
await this.pool.query(
|
|
127
|
+
`delete from ${s}.user_roles where user_id = $1 and role_id = $2`,
|
|
128
|
+
[params.userId, params.roleId]
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
async getUserRoleIds(params) {
|
|
132
|
+
const s = qIdent(this.schema);
|
|
133
|
+
const includeGlobal = params.includeGlobal ?? true;
|
|
134
|
+
const globalAppId = params.globalAppId ?? "global";
|
|
135
|
+
const appIds = includeGlobal ? [params.appId, globalAppId] : [params.appId];
|
|
136
|
+
const res = await this.pool.query(
|
|
137
|
+
`select r.id as role_id
|
|
138
|
+
from ${s}.user_roles ur
|
|
139
|
+
join ${s}.roles r on r.id = ur.role_id
|
|
140
|
+
where ur.user_id = $1 and r.app_id = any($2::text[])
|
|
141
|
+
order by r.app_id, r.id`,
|
|
142
|
+
[params.userId, appIds]
|
|
143
|
+
);
|
|
144
|
+
return res.rows.map((x) => x.role_id);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
148
|
+
0 && (module.exports = {
|
|
149
|
+
RBACManager
|
|
150
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import { U as UUID } from './types-BayIl-Ha.cjs';
|
|
3
|
+
|
|
4
|
+
interface RBACManagerOptions {
|
|
5
|
+
connectionString: string;
|
|
6
|
+
rbacSchema?: string;
|
|
7
|
+
}
|
|
8
|
+
declare class RBACManager {
|
|
9
|
+
private pool;
|
|
10
|
+
private schema;
|
|
11
|
+
constructor(opts: RBACManagerOptions);
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
/** EN: Run a transaction.
|
|
14
|
+
* 中文:事务封装
|
|
15
|
+
*/
|
|
16
|
+
tx<T>(fn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
17
|
+
createApp(params: {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
createPermission(params: {
|
|
22
|
+
appId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string | null;
|
|
25
|
+
}): Promise<UUID>;
|
|
26
|
+
createRole(params: {
|
|
27
|
+
appId: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string | null;
|
|
30
|
+
}): Promise<UUID>;
|
|
31
|
+
grantPermissionToRole(params: {
|
|
32
|
+
appId: string;
|
|
33
|
+
roleId: UUID;
|
|
34
|
+
permissionId: UUID;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
revokePermissionFromRole(params: {
|
|
37
|
+
appId: string;
|
|
38
|
+
roleId: UUID;
|
|
39
|
+
permissionId: UUID;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
assignRole(params: {
|
|
42
|
+
userId: UUID;
|
|
43
|
+
roleId: UUID;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
removeRole(params: {
|
|
46
|
+
userId: UUID;
|
|
47
|
+
roleId: UUID;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
getUserRoleIds(params: {
|
|
50
|
+
userId: UUID;
|
|
51
|
+
appId: string;
|
|
52
|
+
includeGlobal?: boolean;
|
|
53
|
+
globalAppId?: string;
|
|
54
|
+
}): Promise<UUID[]>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { RBACManager, type RBACManagerOptions };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import { U as UUID } from './types-BayIl-Ha.js';
|
|
3
|
+
|
|
4
|
+
interface RBACManagerOptions {
|
|
5
|
+
connectionString: string;
|
|
6
|
+
rbacSchema?: string;
|
|
7
|
+
}
|
|
8
|
+
declare class RBACManager {
|
|
9
|
+
private pool;
|
|
10
|
+
private schema;
|
|
11
|
+
constructor(opts: RBACManagerOptions);
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
/** EN: Run a transaction.
|
|
14
|
+
* 中文:事务封装
|
|
15
|
+
*/
|
|
16
|
+
tx<T>(fn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
17
|
+
createApp(params: {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
createPermission(params: {
|
|
22
|
+
appId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string | null;
|
|
25
|
+
}): Promise<UUID>;
|
|
26
|
+
createRole(params: {
|
|
27
|
+
appId: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string | null;
|
|
30
|
+
}): Promise<UUID>;
|
|
31
|
+
grantPermissionToRole(params: {
|
|
32
|
+
appId: string;
|
|
33
|
+
roleId: UUID;
|
|
34
|
+
permissionId: UUID;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
revokePermissionFromRole(params: {
|
|
37
|
+
appId: string;
|
|
38
|
+
roleId: UUID;
|
|
39
|
+
permissionId: UUID;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
assignRole(params: {
|
|
42
|
+
userId: UUID;
|
|
43
|
+
roleId: UUID;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
removeRole(params: {
|
|
46
|
+
userId: UUID;
|
|
47
|
+
roleId: UUID;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
getUserRoleIds(params: {
|
|
50
|
+
userId: UUID;
|
|
51
|
+
appId: string;
|
|
52
|
+
includeGlobal?: boolean;
|
|
53
|
+
globalAppId?: string;
|
|
54
|
+
}): Promise<UUID[]>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { RBACManager, type RBACManagerOptions };
|
package/dist/server.js
ADDED
package/dist/shared.cjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/shared.ts
|
|
21
|
+
var shared_exports = {};
|
|
22
|
+
__export(shared_exports, {
|
|
23
|
+
decodeClaims: () => decodeClaims,
|
|
24
|
+
getMergedRoleIds: () => getMergedRoleIds,
|
|
25
|
+
hasRole: () => hasRole,
|
|
26
|
+
isUuid: () => isUuid,
|
|
27
|
+
uniqueStable: () => uniqueStable
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(shared_exports);
|
|
30
|
+
var import_jose = require("jose");
|
|
31
|
+
var IS_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
32
|
+
function isUuid(v) {
|
|
33
|
+
return typeof v === "string" && IS_UUID.test(v);
|
|
34
|
+
}
|
|
35
|
+
function uniqueStable(arr) {
|
|
36
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const x of arr) {
|
|
39
|
+
if (seen.has(x)) continue;
|
|
40
|
+
seen.add(x);
|
|
41
|
+
out.push(x);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function decodeClaims(token) {
|
|
46
|
+
if (!token) return null;
|
|
47
|
+
try {
|
|
48
|
+
return (0, import_jose.decodeJwt)(token);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getMergedRoleIds(params) {
|
|
54
|
+
const { token, appId, includeGlobal = true, globalAppId = "global" } = params;
|
|
55
|
+
const claims = decodeClaims(token);
|
|
56
|
+
const roleMap = claims?.app_metadata?.role_ids ?? {};
|
|
57
|
+
const appRoles = Array.isArray(roleMap[appId]) ? roleMap[appId] : [];
|
|
58
|
+
const gRoles = includeGlobal && Array.isArray(roleMap[globalAppId]) ? roleMap[globalAppId] : [];
|
|
59
|
+
const merged = uniqueStable([...appRoles, ...gRoles]).filter(isUuid);
|
|
60
|
+
const userId = isUuid(claims?.sub) ? claims.sub : null;
|
|
61
|
+
const exp = typeof claims?.exp === "number" ? claims.exp : null;
|
|
62
|
+
return { roleIds: merged, claims, userId, exp };
|
|
63
|
+
}
|
|
64
|
+
function hasRole(userRoleIds, required, mode = "any") {
|
|
65
|
+
if (!required || required.length === 0) return true;
|
|
66
|
+
if (!userRoleIds || userRoleIds.length === 0) return false;
|
|
67
|
+
const set = new Set(userRoleIds);
|
|
68
|
+
if (mode === "all") {
|
|
69
|
+
for (const r of required) if (!set.has(r)) return false;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
for (const r of required) if (set.has(r)) return true;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
76
|
+
0 && (module.exports = {
|
|
77
|
+
decodeClaims,
|
|
78
|
+
getMergedRoleIds,
|
|
79
|
+
hasRole,
|
|
80
|
+
isUuid,
|
|
81
|
+
uniqueStable
|
|
82
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { U as UUID, b as RBACClaims, R as RoleCheckMode } from './types-BayIl-Ha.cjs';
|
|
2
|
+
|
|
3
|
+
declare function isUuid(v: unknown): v is UUID;
|
|
4
|
+
declare function uniqueStable<T>(arr: T[]): T[];
|
|
5
|
+
/** EN: Decode claims from JWT without verification (UI gating only). Fail-closed.
|
|
6
|
+
* 中文:不验签解码 JWT claims(仅用于 UI 显隐),失败则返回 null(默认拒绝)
|
|
7
|
+
*/
|
|
8
|
+
declare function decodeClaims(token: string | undefined): RBACClaims | null;
|
|
9
|
+
/** EN: Extract merged role ids (target app + optional global), sanitize UUIDs.
|
|
10
|
+
* 中文:提取并合并 roleIds(目标 app + 可选 global),并清洗为合法 UUID
|
|
11
|
+
*/
|
|
12
|
+
declare function getMergedRoleIds(params: {
|
|
13
|
+
token: string | undefined;
|
|
14
|
+
appId: string;
|
|
15
|
+
includeGlobal?: boolean | undefined;
|
|
16
|
+
globalAppId?: string | undefined;
|
|
17
|
+
}): {
|
|
18
|
+
roleIds: UUID[];
|
|
19
|
+
claims: RBACClaims | null;
|
|
20
|
+
userId: UUID | null;
|
|
21
|
+
exp: number | null;
|
|
22
|
+
};
|
|
23
|
+
/** EN: Role gating helper.
|
|
24
|
+
* 中文:role 判定辅助
|
|
25
|
+
*/
|
|
26
|
+
declare function hasRole(userRoleIds: UUID[], required: UUID[], mode?: RoleCheckMode): boolean;
|
|
27
|
+
|
|
28
|
+
export { decodeClaims, getMergedRoleIds, hasRole, isUuid, uniqueStable };
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { U as UUID, b as RBACClaims, R as RoleCheckMode } from './types-BayIl-Ha.js';
|
|
2
|
+
|
|
3
|
+
declare function isUuid(v: unknown): v is UUID;
|
|
4
|
+
declare function uniqueStable<T>(arr: T[]): T[];
|
|
5
|
+
/** EN: Decode claims from JWT without verification (UI gating only). Fail-closed.
|
|
6
|
+
* 中文:不验签解码 JWT claims(仅用于 UI 显隐),失败则返回 null(默认拒绝)
|
|
7
|
+
*/
|
|
8
|
+
declare function decodeClaims(token: string | undefined): RBACClaims | null;
|
|
9
|
+
/** EN: Extract merged role ids (target app + optional global), sanitize UUIDs.
|
|
10
|
+
* 中文:提取并合并 roleIds(目标 app + 可选 global),并清洗为合法 UUID
|
|
11
|
+
*/
|
|
12
|
+
declare function getMergedRoleIds(params: {
|
|
13
|
+
token: string | undefined;
|
|
14
|
+
appId: string;
|
|
15
|
+
includeGlobal?: boolean | undefined;
|
|
16
|
+
globalAppId?: string | undefined;
|
|
17
|
+
}): {
|
|
18
|
+
roleIds: UUID[];
|
|
19
|
+
claims: RBACClaims | null;
|
|
20
|
+
userId: UUID | null;
|
|
21
|
+
exp: number | null;
|
|
22
|
+
};
|
|
23
|
+
/** EN: Role gating helper.
|
|
24
|
+
* 中文:role 判定辅助
|
|
25
|
+
*/
|
|
26
|
+
declare function hasRole(userRoleIds: UUID[], required: UUID[], mode?: RoleCheckMode): boolean;
|
|
27
|
+
|
|
28
|
+
export { decodeClaims, getMergedRoleIds, hasRole, isUuid, uniqueStable };
|