@develit-services/rbac 0.0.1
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/dist/@types.cjs +24 -0
- package/dist/@types.d.cts +27 -0
- package/dist/@types.d.mts +27 -0
- package/dist/@types.d.ts +27 -0
- package/dist/@types.mjs +2 -0
- package/dist/database/schema.cjs +12 -0
- package/dist/database/schema.d.cts +2 -0
- package/dist/database/schema.d.mts +2 -0
- package/dist/database/schema.d.ts +2 -0
- package/dist/database/schema.mjs +3 -0
- package/dist/export/worker.cjs +776 -0
- package/dist/export/worker.d.cts +36 -0
- package/dist/export/worker.d.mts +34 -0
- package/dist/export/worker.d.ts +36 -0
- package/dist/export/worker.mjs +771 -0
- package/dist/export/wrangler.cjs +48 -0
- package/dist/export/wrangler.d.cts +33 -0
- package/dist/export/wrangler.d.mts +33 -0
- package/dist/export/wrangler.d.ts +33 -0
- package/dist/export/wrangler.mjs +46 -0
- package/dist/shared/rbac.2EhZ2epo.cjs +363 -0
- package/dist/shared/rbac.BmuK3PNh.d.cts +499 -0
- package/dist/shared/rbac.BmuK3PNh.d.mts +499 -0
- package/dist/shared/rbac.BmuK3PNh.d.ts +499 -0
- package/dist/shared/rbac.C9brkvW9.mjs +344 -0
- package/dist/shared/rbac.CHxy3VJb.d.ts +1034 -0
- package/dist/shared/rbac.CJLU5iuV.mjs +45 -0
- package/dist/shared/rbac.CaoDccv4.d.cts +1034 -0
- package/dist/shared/rbac.ClMKyW8J.d.cts +20 -0
- package/dist/shared/rbac.ClMKyW8J.d.mts +20 -0
- package/dist/shared/rbac.ClMKyW8J.d.ts +20 -0
- package/dist/shared/rbac.Cra1T2nC.cjs +51 -0
- package/dist/shared/rbac.DH5084jg.d.mts +1034 -0
- package/package.json +59 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const cloudflare_workers = require('cloudflare:workers');
|
|
6
|
+
const backendSdk = require('@develit-io/backend-sdk');
|
|
7
|
+
const verifyScope = require('../shared/rbac.2EhZ2epo.cjs');
|
|
8
|
+
const database_schema = require('../shared/rbac.Cra1T2nC.cjs');
|
|
9
|
+
const drizzleOrm = require('drizzle-orm');
|
|
10
|
+
const zod = require('zod');
|
|
11
|
+
const d1 = require('drizzle-orm/d1');
|
|
12
|
+
require('drizzle-orm/sqlite-core');
|
|
13
|
+
|
|
14
|
+
const tables = database_schema.schema;
|
|
15
|
+
|
|
16
|
+
const assignRoleToUserCommand = async ({
|
|
17
|
+
db,
|
|
18
|
+
userId,
|
|
19
|
+
roleId
|
|
20
|
+
}) => {
|
|
21
|
+
const id = backendSdk.uuidv4();
|
|
22
|
+
const command = db.insert(tables.userRole).values({
|
|
23
|
+
id,
|
|
24
|
+
roleId,
|
|
25
|
+
userId
|
|
26
|
+
}).returning();
|
|
27
|
+
return { command, id };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const assignRolesToUserCommand = async ({
|
|
31
|
+
db,
|
|
32
|
+
userId,
|
|
33
|
+
roleIds
|
|
34
|
+
}) => {
|
|
35
|
+
const userRoles = roleIds.map((roleId) => ({
|
|
36
|
+
id: backendSdk.uuidv4(),
|
|
37
|
+
roleId,
|
|
38
|
+
userId
|
|
39
|
+
}));
|
|
40
|
+
const command = db.insert(tables.userRole).values(userRoles).returning();
|
|
41
|
+
return { command, ids: userRoles.map((ur) => ur.id) };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const createRoleCommand = async ({
|
|
45
|
+
db,
|
|
46
|
+
name
|
|
47
|
+
}) => {
|
|
48
|
+
const id = backendSdk.uuidv4();
|
|
49
|
+
const command = db.insert(tables.role).values({
|
|
50
|
+
id,
|
|
51
|
+
name
|
|
52
|
+
}).returning();
|
|
53
|
+
return { command, id };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const deleteRoleCommand = async ({
|
|
57
|
+
db,
|
|
58
|
+
id
|
|
59
|
+
}) => {
|
|
60
|
+
const command = db.delete(tables.role).where(drizzleOrm.eq(tables.role.id, id));
|
|
61
|
+
return { command };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const grantScopeToRoleCommand = async ({
|
|
65
|
+
db,
|
|
66
|
+
scope,
|
|
67
|
+
roleId,
|
|
68
|
+
resourceId
|
|
69
|
+
}) => {
|
|
70
|
+
const id = backendSdk.uuidv4();
|
|
71
|
+
const command = db.insert(tables.roleScope).values({
|
|
72
|
+
id,
|
|
73
|
+
scope,
|
|
74
|
+
roleId,
|
|
75
|
+
resourceId
|
|
76
|
+
}).returning();
|
|
77
|
+
return { command, id };
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const grantScopeToUserCommand = async ({
|
|
81
|
+
db,
|
|
82
|
+
userId,
|
|
83
|
+
scope,
|
|
84
|
+
resourceId
|
|
85
|
+
}) => {
|
|
86
|
+
const id = backendSdk.uuidv4();
|
|
87
|
+
const command = db.insert(tables.userScope).values({
|
|
88
|
+
id,
|
|
89
|
+
scope,
|
|
90
|
+
userId,
|
|
91
|
+
resourceId
|
|
92
|
+
}).returning();
|
|
93
|
+
return { command, id };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const grantScopesToUserCommand = async ({
|
|
97
|
+
db,
|
|
98
|
+
userId,
|
|
99
|
+
scopes
|
|
100
|
+
}) => {
|
|
101
|
+
const userScopes = scopes.map((scope) => ({
|
|
102
|
+
id: backendSdk.uuidv4(),
|
|
103
|
+
scope: scope.scope,
|
|
104
|
+
userId,
|
|
105
|
+
resourceId: scope.resourceId
|
|
106
|
+
}));
|
|
107
|
+
const command = db.insert(tables.userScope).values(userScopes).returning();
|
|
108
|
+
return { command, ids: userScopes.map((us) => us.id) };
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const revokeRoleFromUserCommand = async ({
|
|
112
|
+
db,
|
|
113
|
+
userId,
|
|
114
|
+
roleId
|
|
115
|
+
}) => {
|
|
116
|
+
const command = db.delete(tables.userRole).where(
|
|
117
|
+
drizzleOrm.and(
|
|
118
|
+
drizzleOrm.eq(tables.userRole.userId, userId),
|
|
119
|
+
drizzleOrm.eq(tables.userRole.roleId, roleId)
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
return { command };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const revokeScopeFromRoleCommand = async ({
|
|
126
|
+
db,
|
|
127
|
+
roleId,
|
|
128
|
+
scope,
|
|
129
|
+
resourceId
|
|
130
|
+
}) => {
|
|
131
|
+
const command = db.delete(tables.roleScope).where(
|
|
132
|
+
drizzleOrm.and(
|
|
133
|
+
drizzleOrm.eq(tables.roleScope.roleId, roleId),
|
|
134
|
+
drizzleOrm.eq(tables.roleScope.scope, scope),
|
|
135
|
+
resourceId ? drizzleOrm.eq(tables.userScope.resourceId, resourceId) : void 0
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
return { command };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const revokeScopeFromUserCommand = async ({
|
|
142
|
+
db,
|
|
143
|
+
userId,
|
|
144
|
+
scope,
|
|
145
|
+
resourceId
|
|
146
|
+
}) => {
|
|
147
|
+
const command = db.delete(tables.userScope).where(
|
|
148
|
+
drizzleOrm.and(
|
|
149
|
+
drizzleOrm.eq(tables.userScope.userId, userId),
|
|
150
|
+
drizzleOrm.eq(tables.userScope.scope, scope),
|
|
151
|
+
resourceId ? drizzleOrm.eq(tables.userScope.resourceId, resourceId) : void 0
|
|
152
|
+
)
|
|
153
|
+
);
|
|
154
|
+
return { command };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const updateRoleCommand = async ({
|
|
158
|
+
db,
|
|
159
|
+
id,
|
|
160
|
+
name
|
|
161
|
+
}) => {
|
|
162
|
+
const command = db.update(tables.role).set({ name }).where(drizzleOrm.eq(tables.role.id, id));
|
|
163
|
+
return { command };
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const getAllRolesQuery = async ({
|
|
167
|
+
db
|
|
168
|
+
}) => {
|
|
169
|
+
const roles = await db.select().from(tables.role);
|
|
170
|
+
const result = [];
|
|
171
|
+
for (const role of roles) {
|
|
172
|
+
const scopeCount = await db.select({ count: drizzleOrm.count() }).from(tables.roleScope).where(drizzleOrm.eq(tables.roleScope.roleId, role.id));
|
|
173
|
+
const userCount = await db.select({ count: drizzleOrm.count() }).from(tables.userRole).where(drizzleOrm.eq(tables.userRole.roleId, role.id));
|
|
174
|
+
result.push({
|
|
175
|
+
id: role.id,
|
|
176
|
+
name: role.name,
|
|
177
|
+
numberOfScopes: scopeCount[0]?.count ?? 0,
|
|
178
|
+
numberOfUsers: userCount[0]?.count ?? 0
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const getRoleQuery = async ({
|
|
185
|
+
db,
|
|
186
|
+
roleId
|
|
187
|
+
}) => {
|
|
188
|
+
return await db.select().from(tables.role).where(drizzleOrm.eq(tables.role.id, roleId)).then(backendSdk.first);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const getRolesByUserQuery = async ({
|
|
192
|
+
db,
|
|
193
|
+
userId
|
|
194
|
+
}) => {
|
|
195
|
+
return await db.select({
|
|
196
|
+
id: tables.role.id,
|
|
197
|
+
name: tables.role.name
|
|
198
|
+
}).from(tables.userRole).leftJoin(tables.role, drizzleOrm.eq(tables.role.id, tables.userRole.roleId)).where(drizzleOrm.and(drizzleOrm.eq(tables.userRole.userId, userId)));
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const getScopesByRolesQuery = async ({
|
|
202
|
+
db,
|
|
203
|
+
roleIds
|
|
204
|
+
}) => {
|
|
205
|
+
return await db.select({
|
|
206
|
+
id: tables.roleScope.id,
|
|
207
|
+
scope: tables.roleScope.scope,
|
|
208
|
+
resourceId: tables.roleScope.resourceId
|
|
209
|
+
}).from(tables.roleScope).where(drizzleOrm.inArray(tables.roleScope.roleId, roleIds));
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const getScopesByUserQuery = async ({
|
|
213
|
+
db,
|
|
214
|
+
userId
|
|
215
|
+
}) => {
|
|
216
|
+
return await db.select({
|
|
217
|
+
id: tables.userScope.id,
|
|
218
|
+
scope: tables.userScope.scope,
|
|
219
|
+
resourceId: tables.userScope.resourceId
|
|
220
|
+
}).from(tables.userScope).where(drizzleOrm.and(drizzleOrm.eq(tables.userScope.userId, userId)));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
function parseScopeTemplate(scope) {
|
|
224
|
+
if (typeof scope !== "string") {
|
|
225
|
+
throw backendSdk.createInternalError(null, {
|
|
226
|
+
message: "Scope template must be a string",
|
|
227
|
+
status: 400,
|
|
228
|
+
code: "INVALID_SCOPE_TYPE"
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (scope.length === 0) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
const placeholderRegex = /\{([^.]+)\.(.+?)\}/g;
|
|
235
|
+
const allBraceMatches = /\{[^}]*\}/g;
|
|
236
|
+
const allMatches = [...scope.matchAll(allBraceMatches)];
|
|
237
|
+
const validMatches = [...scope.matchAll(placeholderRegex)];
|
|
238
|
+
if (allMatches.length !== validMatches.length) {
|
|
239
|
+
throw backendSdk.createInternalError(null, {
|
|
240
|
+
message: "Invalid placeholder format found. Expected format: {type.key}",
|
|
241
|
+
status: 400,
|
|
242
|
+
code: "INVALID_PLACEHOLDER_FORMAT"
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const placeholders = [];
|
|
246
|
+
let match;
|
|
247
|
+
placeholderRegex.lastIndex = 0;
|
|
248
|
+
while ((match = placeholderRegex.exec(scope)) !== null) {
|
|
249
|
+
const type = match[1];
|
|
250
|
+
const path = match[2];
|
|
251
|
+
if (!type || !path) {
|
|
252
|
+
throw backendSdk.createInternalError(null, {
|
|
253
|
+
message: `Placeholder '${match[0]}' has empty type or path`,
|
|
254
|
+
status: 400,
|
|
255
|
+
code: "EMPTY_PLACEHOLDER_PARTS"
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
placeholders.push({
|
|
259
|
+
type,
|
|
260
|
+
// jwt
|
|
261
|
+
path,
|
|
262
|
+
// userData.organizationId
|
|
263
|
+
fullMatch: match[0],
|
|
264
|
+
// "{jwt.userData.organizationId}"
|
|
265
|
+
position: match.index
|
|
266
|
+
// Position in string for ordered replacement
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return placeholders.sort((a, b) => a.position - b.position);
|
|
270
|
+
}
|
|
271
|
+
function extractResourcesFromPath(scope, resourcePath) {
|
|
272
|
+
const placeholders = parseScopeTemplate(scope);
|
|
273
|
+
if (placeholders.length === 0) return {};
|
|
274
|
+
let regexPattern = scope.replace(/\./g, "\\.");
|
|
275
|
+
placeholders.forEach(() => {
|
|
276
|
+
regexPattern = regexPattern.replace(/\{[^}]+\}/, "([^.]+)");
|
|
277
|
+
});
|
|
278
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
279
|
+
const match = resourcePath.match(regex);
|
|
280
|
+
if (!match) {
|
|
281
|
+
throw backendSdk.createInternalError(null, {
|
|
282
|
+
message: `Resource path '${resourcePath}' does not match scope template '${scope}'`,
|
|
283
|
+
status: 400,
|
|
284
|
+
code: "PATH_MISMATCH"
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const result = {};
|
|
288
|
+
placeholders.forEach((placeholder, index) => {
|
|
289
|
+
const capturedValue = match[index + 1];
|
|
290
|
+
const { error } = zod.z.uuid().safeParse(capturedValue);
|
|
291
|
+
if (error)
|
|
292
|
+
throw backendSdk.createInternalError(null, {
|
|
293
|
+
message: `Invalid UUID format: '${capturedValue}' for placeholder '${placeholder.type}.${placeholder.path}'`,
|
|
294
|
+
status: 400,
|
|
295
|
+
code: "INVALID_UUID"
|
|
296
|
+
});
|
|
297
|
+
result[`${placeholder.type}.${placeholder.path}`] = capturedValue;
|
|
298
|
+
});
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
const inputGetValueByKeySchema = zod.z.object({
|
|
302
|
+
type: zod.z.string().nonempty("Type parameter cannot be empty"),
|
|
303
|
+
path: zod.z.string().nonempty("Path parameter cannot be empty"),
|
|
304
|
+
jwt: verifyScope.jwtPayloadSchema.optional()
|
|
305
|
+
});
|
|
306
|
+
const parseJson = (data) => {
|
|
307
|
+
try {
|
|
308
|
+
return JSON.parse(data);
|
|
309
|
+
} catch (_e) {
|
|
310
|
+
return data;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
function getNestedValue(jwt, path) {
|
|
314
|
+
if (path === "") {
|
|
315
|
+
return jwt;
|
|
316
|
+
}
|
|
317
|
+
const keys = path.split(".");
|
|
318
|
+
let current = jwt;
|
|
319
|
+
for (const key of keys) {
|
|
320
|
+
if (current && typeof current === "object" && current !== null && !Array.isArray(current) && key in current) {
|
|
321
|
+
current = parseJson(current[key]);
|
|
322
|
+
} else {
|
|
323
|
+
return void 0;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return current;
|
|
327
|
+
}
|
|
328
|
+
function getValueByKey(type, path, jwt) {
|
|
329
|
+
const { error } = inputGetValueByKeySchema.safeParse({ type, path, jwt });
|
|
330
|
+
if (error) {
|
|
331
|
+
const message = error.issues.map((e) => e.message).join(", ");
|
|
332
|
+
throw backendSdk.createInternalError(error, {
|
|
333
|
+
message,
|
|
334
|
+
status: 500
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
switch (type) {
|
|
338
|
+
case "jwt": {
|
|
339
|
+
if (!jwt)
|
|
340
|
+
throw backendSdk.createInternalError(null, {
|
|
341
|
+
message: "JWT is required when using JWT parameters in scope template",
|
|
342
|
+
status: 400
|
|
343
|
+
});
|
|
344
|
+
const value = getNestedValue(jwt, path);
|
|
345
|
+
if (value === void 0 || value === null)
|
|
346
|
+
throw backendSdk.createInternalError(null, {
|
|
347
|
+
message: `Property '${path}' not found in JWT payload.`,
|
|
348
|
+
status: 400
|
|
349
|
+
});
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
352
|
+
case "params":
|
|
353
|
+
break;
|
|
354
|
+
default:
|
|
355
|
+
throw backendSdk.createInternalError(null, {
|
|
356
|
+
message: `Unsupported param type '${type}'`,
|
|
357
|
+
status: 400
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
var __defProp = Object.defineProperty;
|
|
363
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
364
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
365
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
366
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
367
|
+
if (decorator = decorators[i])
|
|
368
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
369
|
+
if (kind && result) __defProp(target, key, result);
|
|
370
|
+
return result;
|
|
371
|
+
};
|
|
372
|
+
let RbacServiceBase = class extends backendSdk.develitWorker(
|
|
373
|
+
cloudflare_workers.WorkerEntrypoint
|
|
374
|
+
) {
|
|
375
|
+
constructor(ctx, env) {
|
|
376
|
+
super(ctx, env);
|
|
377
|
+
this.db = d1.drizzle(this.env.RBAC_D1, { schema: tables });
|
|
378
|
+
}
|
|
379
|
+
async createRole(input) {
|
|
380
|
+
return this.handleAction(
|
|
381
|
+
{ data: input, schema: verifyScope.createRoleInputSchema },
|
|
382
|
+
{ successMessage: "Role successfully created." },
|
|
383
|
+
async ({ name }) => {
|
|
384
|
+
const { command } = await createRoleCommand({ db: this.db, name });
|
|
385
|
+
const [roles] = await this.db.batch([command]);
|
|
386
|
+
return backendSdk.first(roles);
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
async assignRoleToUser(input) {
|
|
391
|
+
return this.handleAction(
|
|
392
|
+
{ data: input, schema: verifyScope.assignRoleToUserInputSchema },
|
|
393
|
+
{ successMessage: "Role successfully assigned." },
|
|
394
|
+
async ({ userId, roleId }) => {
|
|
395
|
+
const [role, userRole] = await Promise.all([
|
|
396
|
+
await getRoleQuery({ db: this.db, roleId }),
|
|
397
|
+
await getRolesByUserQuery({ db: this.db, userId })
|
|
398
|
+
]);
|
|
399
|
+
if (!role) {
|
|
400
|
+
throw backendSdk.createInternalError(null, {
|
|
401
|
+
message: "Role not found.",
|
|
402
|
+
status: 404
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (userRole.some((r) => r.id === roleId)) {
|
|
406
|
+
throw backendSdk.createInternalError(null, {
|
|
407
|
+
message: "Role already assigned to user.",
|
|
408
|
+
status: 409
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
const { command } = await assignRoleToUserCommand({
|
|
412
|
+
db: this.db,
|
|
413
|
+
userId,
|
|
414
|
+
roleId
|
|
415
|
+
});
|
|
416
|
+
const [record] = await this.db.batch([command]);
|
|
417
|
+
return backendSdk.first(record);
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
async assignRolesToUser(input) {
|
|
422
|
+
return this.handleAction(
|
|
423
|
+
{ data: input, schema: verifyScope.assignRolesToUserInputSchema },
|
|
424
|
+
{ successMessage: "Roles successfully assigned." },
|
|
425
|
+
async ({ userId, roles: roleIds }) => {
|
|
426
|
+
for (const roleId of roleIds) {
|
|
427
|
+
const [role, userRole] = await Promise.all([
|
|
428
|
+
await getRoleQuery({ db: this.db, roleId }),
|
|
429
|
+
await getRolesByUserQuery({ db: this.db, userId })
|
|
430
|
+
]);
|
|
431
|
+
if (!role) {
|
|
432
|
+
throw backendSdk.createInternalError(null, {
|
|
433
|
+
message: "Role not found.",
|
|
434
|
+
status: 404
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (userRole.some((r) => r.id === roleId)) {
|
|
438
|
+
throw backendSdk.createInternalError(null, {
|
|
439
|
+
message: "Role already assigned to user.",
|
|
440
|
+
status: 409
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const { command } = await assignRolesToUserCommand({
|
|
445
|
+
db: this.db,
|
|
446
|
+
userId,
|
|
447
|
+
roleIds
|
|
448
|
+
});
|
|
449
|
+
const [record] = await this.db.batch([command]);
|
|
450
|
+
return backendSdk.first(record);
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
async revokeRoleFromUser(input) {
|
|
455
|
+
return this.handleAction(
|
|
456
|
+
{ data: input, schema: verifyScope.revokeRoleFromUserInputSchema },
|
|
457
|
+
{ successMessage: "Role successfully revoked." },
|
|
458
|
+
async ({ userId, roleId }) => {
|
|
459
|
+
const { command } = await revokeRoleFromUserCommand({
|
|
460
|
+
db: this.db,
|
|
461
|
+
userId,
|
|
462
|
+
roleId
|
|
463
|
+
});
|
|
464
|
+
await this.db.batch([command]);
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
async grantScopeToUser(input) {
|
|
470
|
+
return this.handleAction(
|
|
471
|
+
{ data: input, schema: verifyScope.grantScopeToUserInputSchema },
|
|
472
|
+
{ successMessage: "Scope successfully granted to user." },
|
|
473
|
+
async ({ userId, scope, resourceId }) => {
|
|
474
|
+
const userScope = await getScopesByUserQuery({ db: this.db, userId });
|
|
475
|
+
if (userScope.some((s) => s.scope === scope)) {
|
|
476
|
+
throw backendSdk.createInternalError(null, {
|
|
477
|
+
message: "Scope already assigned to user.",
|
|
478
|
+
status: 409
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
const { command } = await grantScopeToUserCommand({
|
|
482
|
+
db: this.db,
|
|
483
|
+
userId,
|
|
484
|
+
scope,
|
|
485
|
+
resourceId
|
|
486
|
+
});
|
|
487
|
+
const [record] = await this.db.batch([command]);
|
|
488
|
+
return backendSdk.first(record);
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
async grantScopesToUser(input) {
|
|
493
|
+
return this.handleAction(
|
|
494
|
+
{ data: input, schema: verifyScope.grantScopesToUserInputSchema },
|
|
495
|
+
{ successMessage: "Scopes successfully granted to user." },
|
|
496
|
+
async ({ userId, scopes }) => {
|
|
497
|
+
for (const scope of scopes) {
|
|
498
|
+
const userScopes = await getScopesByUserQuery({ db: this.db, userId });
|
|
499
|
+
if (userScopes.some((s) => s.scope === scope.scope)) {
|
|
500
|
+
throw backendSdk.createInternalError(null, {
|
|
501
|
+
message: "Scope already assigned to user.",
|
|
502
|
+
status: 409
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const { command } = await grantScopesToUserCommand({
|
|
507
|
+
db: this.db,
|
|
508
|
+
userId,
|
|
509
|
+
scopes
|
|
510
|
+
});
|
|
511
|
+
const [record] = await this.db.batch([command]);
|
|
512
|
+
return backendSdk.first(record);
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
async revokeScopeFromUser(input) {
|
|
517
|
+
return this.handleAction(
|
|
518
|
+
{ data: input, schema: verifyScope.revokeScopeFromUserInputSchema },
|
|
519
|
+
{ successMessage: "Scope successfully revoked from user." },
|
|
520
|
+
async ({ userId, scope, resourceId }) => {
|
|
521
|
+
const { command } = await revokeScopeFromUserCommand({
|
|
522
|
+
db: this.db,
|
|
523
|
+
userId,
|
|
524
|
+
scope,
|
|
525
|
+
resourceId
|
|
526
|
+
});
|
|
527
|
+
await this.db.batch([command]);
|
|
528
|
+
return {};
|
|
529
|
+
}
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
async grantScopeToRole(input) {
|
|
533
|
+
return this.handleAction(
|
|
534
|
+
{ data: input, schema: verifyScope.grantScopeToRoleInputSchema },
|
|
535
|
+
{ successMessage: "Scope successfully granted to role." },
|
|
536
|
+
async ({ roleId, scope, resourceId }) => {
|
|
537
|
+
const { command } = await grantScopeToRoleCommand({
|
|
538
|
+
db: this.db,
|
|
539
|
+
scope,
|
|
540
|
+
roleId,
|
|
541
|
+
resourceId
|
|
542
|
+
});
|
|
543
|
+
const [record] = await this.db.batch([command]);
|
|
544
|
+
return backendSdk.first(record);
|
|
545
|
+
}
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
async revokeScopeFromRole(input) {
|
|
549
|
+
return this.handleAction(
|
|
550
|
+
{ data: input, schema: verifyScope.revokeScopeFromRoleInputSchema },
|
|
551
|
+
{ successMessage: "Scope successfully revoked from role." },
|
|
552
|
+
async ({ roleId, scope, resourceId }) => {
|
|
553
|
+
const { command } = await revokeScopeFromRoleCommand({
|
|
554
|
+
db: this.db,
|
|
555
|
+
roleId,
|
|
556
|
+
scope,
|
|
557
|
+
resourceId
|
|
558
|
+
});
|
|
559
|
+
await this.db.batch([command]);
|
|
560
|
+
return {};
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
async getPermissions() {
|
|
565
|
+
return this.handleAction(
|
|
566
|
+
null,
|
|
567
|
+
{ successMessage: "Permissions successfully returned." },
|
|
568
|
+
async () => {
|
|
569
|
+
const roles = await getAllRolesQuery({ db: this.db });
|
|
570
|
+
const roleScopes = await Promise.all(
|
|
571
|
+
roles.filter((role) => role.id).map(async (role) => {
|
|
572
|
+
const scopes = await getScopesByRolesQuery({
|
|
573
|
+
db: this.db,
|
|
574
|
+
roleIds: [role.id]
|
|
575
|
+
});
|
|
576
|
+
return {
|
|
577
|
+
roleId: role.id,
|
|
578
|
+
roleName: role.name,
|
|
579
|
+
scopes: scopes.map((scope) => ({
|
|
580
|
+
id: scope.id,
|
|
581
|
+
scope: scope.scope,
|
|
582
|
+
resourceId: scope.resourceId
|
|
583
|
+
}))
|
|
584
|
+
};
|
|
585
|
+
})
|
|
586
|
+
);
|
|
587
|
+
return {
|
|
588
|
+
roles,
|
|
589
|
+
rolesCount: roles.length,
|
|
590
|
+
scopes: [...verifyScope.SCOPES],
|
|
591
|
+
scopesCount: [...verifyScope.SCOPES].length,
|
|
592
|
+
roleScopes,
|
|
593
|
+
roleScopesCount: roleScopes.length
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
async getUserPermissions(input) {
|
|
599
|
+
return this.handleAction(
|
|
600
|
+
{ data: input, schema: verifyScope.getUserPermissionsInputSchema },
|
|
601
|
+
{ successMessage: "User permissions successfully returned." },
|
|
602
|
+
async ({ userId }) => {
|
|
603
|
+
const resultRoles = await getRolesByUserQuery({ db: this.db, userId });
|
|
604
|
+
const resultScopes = await getScopesByUserQuery({ db: this.db, userId });
|
|
605
|
+
const resultRoleScopes = await getScopesByRolesQuery({
|
|
606
|
+
db: this.db,
|
|
607
|
+
roleIds: resultRoles.filter((role) => role.id).map((role) => role.id)
|
|
608
|
+
});
|
|
609
|
+
const roles = resultRoles.map((role) => ({
|
|
610
|
+
id: role.id,
|
|
611
|
+
name: role.name
|
|
612
|
+
}));
|
|
613
|
+
const scopes = resultScopes.map((scope) => ({
|
|
614
|
+
id: scope.id,
|
|
615
|
+
scope: scope.scope,
|
|
616
|
+
resourceId: scope.resourceId
|
|
617
|
+
}));
|
|
618
|
+
const roleScopes = resultRoleScopes.map((scope) => ({
|
|
619
|
+
id: scope.id,
|
|
620
|
+
scope: scope.scope,
|
|
621
|
+
resourceId: scope.resourceId
|
|
622
|
+
}));
|
|
623
|
+
return {
|
|
624
|
+
roles,
|
|
625
|
+
rolesCount: roles.length,
|
|
626
|
+
scopes,
|
|
627
|
+
scopesCount: scopes.length,
|
|
628
|
+
roleScopes,
|
|
629
|
+
roleScopesCount: roleScopes.length
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
async verifyAccess(input) {
|
|
635
|
+
return this.handleAction(
|
|
636
|
+
// TODO: This input schema is just copied from auth and is not 100% type safe
|
|
637
|
+
{ data: input, schema: verifyScope.verifyAccessInputSchema },
|
|
638
|
+
{ successMessage: "Access verification completed." },
|
|
639
|
+
async ({ userId, accessRequests, jwt }) => {
|
|
640
|
+
const userPermissionsResponse = await this.getUserPermissions({
|
|
641
|
+
userId
|
|
642
|
+
});
|
|
643
|
+
if (!userPermissionsResponse.data) {
|
|
644
|
+
throw backendSdk.createInternalError(null, {
|
|
645
|
+
message: "Unable to retrieve user permissions",
|
|
646
|
+
status: 500
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const allScopes = [
|
|
650
|
+
...userPermissionsResponse.data.roleScopes,
|
|
651
|
+
...userPermissionsResponse.data.scopes
|
|
652
|
+
];
|
|
653
|
+
const allAccessRequestsSatisfied = accessRequests.every((request) => {
|
|
654
|
+
const placeholders = parseScopeTemplate(request.scope);
|
|
655
|
+
return allScopes.some((userScope) => {
|
|
656
|
+
const scopesMatch = userScope.scope === request.scope;
|
|
657
|
+
let resourceMatches = false;
|
|
658
|
+
if (placeholders.length > 0) {
|
|
659
|
+
if (!request.resourcePath) {
|
|
660
|
+
throw backendSdk.createInternalError(null, {
|
|
661
|
+
message: `Resource path is required when scope '${request.scope}' contains placeholders`,
|
|
662
|
+
status: 400,
|
|
663
|
+
code: "RESOURCE_PATH_REQUIRED"
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
const extractedResources = extractResourcesFromPath(
|
|
667
|
+
request.scope,
|
|
668
|
+
request.resourcePath
|
|
669
|
+
);
|
|
670
|
+
const allPlaceholdersMatch = placeholders.every((placeholder) => {
|
|
671
|
+
const extractedValue = extractedResources[`${placeholder.type}.${placeholder.path}`];
|
|
672
|
+
const jwtParam = placeholder.type === "jwt" ? jwt : void 0;
|
|
673
|
+
const expectedValue = getValueByKey(
|
|
674
|
+
placeholder.type,
|
|
675
|
+
placeholder.path,
|
|
676
|
+
jwtParam
|
|
677
|
+
);
|
|
678
|
+
if (expectedValue === void 0) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
return String(extractedValue) === String(expectedValue);
|
|
682
|
+
});
|
|
683
|
+
const resourceIdMatches = userScope.resourceId === null || userScope.resourceId === request.resourceId;
|
|
684
|
+
resourceMatches = allPlaceholdersMatch && resourceIdMatches;
|
|
685
|
+
} else {
|
|
686
|
+
resourceMatches = userScope.resourceId === null || userScope.resourceId === request.resourceId;
|
|
687
|
+
}
|
|
688
|
+
return scopesMatch && resourceMatches;
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
isVerified: allAccessRequestsSatisfied
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
async deleteRole(input) {
|
|
698
|
+
return this.handleAction(
|
|
699
|
+
{ data: input, schema: verifyScope.deleteRoleInputSchema },
|
|
700
|
+
{ successMessage: "Role successfully deleted" },
|
|
701
|
+
async ({ id }) => {
|
|
702
|
+
const { command } = await deleteRoleCommand({ db: this.db, id });
|
|
703
|
+
await this.db.batch([command]);
|
|
704
|
+
return {};
|
|
705
|
+
}
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
async updateRole(input) {
|
|
709
|
+
return this.handleAction(
|
|
710
|
+
{ data: input, schema: verifyScope.updateRoleInputSchema },
|
|
711
|
+
{ successMessage: "Role successfully updated" },
|
|
712
|
+
async ({ id, name }) => {
|
|
713
|
+
const { command } = await updateRoleCommand({ db: this.db, id, name });
|
|
714
|
+
await this.db.batch([command]);
|
|
715
|
+
return {};
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
__decorateClass([
|
|
721
|
+
backendSdk.action("createRole")
|
|
722
|
+
], RbacServiceBase.prototype, "createRole", 1);
|
|
723
|
+
__decorateClass([
|
|
724
|
+
backendSdk.action("assignRoleToUser")
|
|
725
|
+
], RbacServiceBase.prototype, "assignRoleToUser", 1);
|
|
726
|
+
__decorateClass([
|
|
727
|
+
backendSdk.action("assign-roles-to-user")
|
|
728
|
+
], RbacServiceBase.prototype, "assignRolesToUser", 1);
|
|
729
|
+
__decorateClass([
|
|
730
|
+
backendSdk.action("revokeRoleFromUser")
|
|
731
|
+
], RbacServiceBase.prototype, "revokeRoleFromUser", 1);
|
|
732
|
+
__decorateClass([
|
|
733
|
+
backendSdk.action("grantScopeToUser")
|
|
734
|
+
], RbacServiceBase.prototype, "grantScopeToUser", 1);
|
|
735
|
+
__decorateClass([
|
|
736
|
+
backendSdk.action("grant-scopes-to-user")
|
|
737
|
+
], RbacServiceBase.prototype, "grantScopesToUser", 1);
|
|
738
|
+
__decorateClass([
|
|
739
|
+
backendSdk.action("revokeScopeFromUser")
|
|
740
|
+
], RbacServiceBase.prototype, "revokeScopeFromUser", 1);
|
|
741
|
+
__decorateClass([
|
|
742
|
+
backendSdk.action("grantScopeToRole")
|
|
743
|
+
], RbacServiceBase.prototype, "grantScopeToRole", 1);
|
|
744
|
+
__decorateClass([
|
|
745
|
+
backendSdk.action("revokeScopeFromRole")
|
|
746
|
+
], RbacServiceBase.prototype, "revokeScopeFromRole", 1);
|
|
747
|
+
__decorateClass([
|
|
748
|
+
backendSdk.action("getPermissions")
|
|
749
|
+
], RbacServiceBase.prototype, "getPermissions", 1);
|
|
750
|
+
__decorateClass([
|
|
751
|
+
backendSdk.action("getUserPermissions")
|
|
752
|
+
], RbacServiceBase.prototype, "getUserPermissions", 1);
|
|
753
|
+
__decorateClass([
|
|
754
|
+
backendSdk.action("verifyAccess")
|
|
755
|
+
], RbacServiceBase.prototype, "verifyAccess", 1);
|
|
756
|
+
__decorateClass([
|
|
757
|
+
backendSdk.action("deleteRole")
|
|
758
|
+
], RbacServiceBase.prototype, "deleteRole", 1);
|
|
759
|
+
__decorateClass([
|
|
760
|
+
backendSdk.action("updateRole")
|
|
761
|
+
], RbacServiceBase.prototype, "updateRole", 1);
|
|
762
|
+
RbacServiceBase = __decorateClass([
|
|
763
|
+
backendSdk.service("rbac")
|
|
764
|
+
], RbacServiceBase);
|
|
765
|
+
function defineRbacService() {
|
|
766
|
+
return class RbacService extends RbacServiceBase {
|
|
767
|
+
constructor(ctx, env) {
|
|
768
|
+
super(ctx, env);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const RbacService = defineRbacService();
|
|
774
|
+
|
|
775
|
+
exports.default = RbacService;
|
|
776
|
+
exports.defineRbacService = defineRbacService;
|