@robelest/convex-auth 0.0.4-preview.16 → 0.0.4-preview.18
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/authorization/index.d.ts +17 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/authorization/index.js +17 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/client/index.js +73 -0
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +52 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/index.js +2 -2
- package/dist/component/model.d.ts +75 -75
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +2 -0
- package/dist/component/model.js.map +1 -1
- package/dist/component/public/factors.d.ts.map +1 -1
- package/dist/component/public/factors.js +1 -1
- package/dist/component/public/factors.js.map +1 -1
- package/dist/component/public/groups.d.ts +10 -2
- package/dist/component/public/groups.d.ts.map +1 -1
- package/dist/component/public/groups.js +92 -8
- package/dist/component/public/groups.js.map +1 -1
- package/dist/component/public/identity.d.ts.map +1 -1
- package/dist/component/public/identity.js +3 -3
- package/dist/component/public/identity.js.map +1 -1
- package/dist/component/public/shared.d.ts +4 -4
- package/dist/component/public/shared.d.ts.map +1 -1
- package/dist/component/public.d.ts +3 -3
- package/dist/component/public.js +2 -2
- package/dist/component/schema.d.ts +13 -2
- package/dist/component/schema.js +7 -5
- package/dist/component/schema.js.map +1 -1
- package/dist/component/server/auth.d.ts +4 -25
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +4 -10
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/domains/core.js +196 -131
- package/dist/component/server/domains/core.js.map +1 -1
- package/dist/component/server/factory.d.ts +3 -3
- package/dist/component/server/signin.js +20 -1
- package/dist/component/server/signin.js.map +1 -1
- package/dist/server/auth.d.ts +4 -25
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +4 -10
- package/dist/server/auth.js.map +1 -1
- package/dist/server/domains/core.d.ts +73 -58
- package/dist/server/domains/core.d.ts.map +1 -1
- package/dist/server/domains/core.js +196 -131
- package/dist/server/domains/core.js.map +1 -1
- package/dist/server/http.d.ts +2 -2
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/index.d.ts +108 -69
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +138 -54
- package/dist/server/index.js.map +1 -1
- package/dist/server/mutations/code.d.ts +9 -9
- package/dist/server/mutations/index.d.ts +69 -69
- package/dist/server/mutations/invalidate.d.ts +4 -4
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/oauth.d.ts +7 -7
- package/dist/server/mutations/register.d.ts +9 -9
- package/dist/server/mutations/retrieve.d.ts +6 -6
- package/dist/server/mutations/retrieve.d.ts.map +1 -1
- package/dist/server/mutations/signature.d.ts +4 -4
- package/dist/server/mutations/signature.d.ts.map +1 -1
- package/dist/server/mutations/signin.d.ts +5 -5
- package/dist/server/mutations/signin.d.ts.map +1 -1
- package/dist/server/mutations/verify.d.ts +7 -7
- package/dist/server/signin.js +20 -1
- package/dist/server/signin.js.map +1 -1
- package/dist/server/version.d.ts +1 -1
- package/dist/server/version.js +1 -1
- package/dist/server/version.js.map +1 -1
- package/package.json +9 -5
- package/src/authorization/index.ts +37 -0
- package/src/client/index.ts +122 -0
- package/src/component/_generated/component.ts +64 -0
- package/src/component/index.ts +1 -1
- package/src/component/model.ts +2 -0
- package/src/component/public/factors.ts +3 -2
- package/src/component/public/groups.ts +142 -8
- package/src/component/public/identity.ts +9 -6
- package/src/component/schema.ts +14 -2
- package/src/server/auth.ts +9 -61
- package/src/server/domains/core.ts +235 -210
- package/src/server/index.ts +158 -75
- package/src/server/signin.ts +52 -0
- package/src/server/version.ts +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AuthError, Fx } from "../fx.js";
|
|
2
1
|
import { TOKEN_SUB_CLAIM_DIVIDER, generateRandomString, sha256 } from "../utils.js";
|
|
3
2
|
import { buildScopeChecker, checkKeyRateLimit, generateApiKey, hashApiKey } from "../keys.js";
|
|
4
3
|
import { materializeProvider } from "../providers.js";
|
|
@@ -12,25 +11,39 @@ function createCoreDomains(deps) {
|
|
|
12
11
|
const { config, getAuth, callInvalidateSessions, callCreateAccountFromCredentials, callRetrieveAccountWithCredentials, callModifyAccount, getEnrichCtx, inviteTokenAlphabet, inviteTokenLength } = deps;
|
|
13
12
|
const roleDefinitions = config.authorization.roles;
|
|
14
13
|
const getRoleDefinition = (roleId) => {
|
|
15
|
-
|
|
16
|
-
if (!role) throw new AuthError("INVALID_PARAMETERS", `Unknown roleId "${roleId}".`).toConvexError();
|
|
17
|
-
return role;
|
|
14
|
+
return roleDefinitions[roleId] ?? null;
|
|
18
15
|
};
|
|
19
16
|
const normalizeRoleIds = (roleIds) => {
|
|
20
17
|
const normalized = Array.from(new Set(roleIds ?? []));
|
|
21
|
-
|
|
22
|
-
return
|
|
18
|
+
const invalid = normalized.filter((id) => getRoleDefinition(id) === null);
|
|
19
|
+
if (invalid.length > 0) return {
|
|
20
|
+
ok: false,
|
|
21
|
+
invalidRoleIds: invalid
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
roleIds: normalized
|
|
26
|
+
};
|
|
23
27
|
};
|
|
24
28
|
const resolveGrantedPermissions = (roleIds) => {
|
|
25
29
|
const grants = /* @__PURE__ */ new Set();
|
|
26
30
|
for (const roleId of roleIds ?? []) {
|
|
27
31
|
const role = getRoleDefinition(roleId);
|
|
32
|
+
if (role === null) continue;
|
|
28
33
|
for (const grant of role.grants) grants.add(grant);
|
|
29
34
|
}
|
|
30
35
|
return Array.from(grants).sort();
|
|
31
36
|
};
|
|
37
|
+
const AUTH_CACHE = Symbol("__convexAuthCache");
|
|
38
|
+
function cache(ctx) {
|
|
39
|
+
if (!ctx[AUTH_CACHE]) ctx[AUTH_CACHE] = {
|
|
40
|
+
users: /* @__PURE__ */ new Map(),
|
|
41
|
+
groups: /* @__PURE__ */ new Map()
|
|
42
|
+
};
|
|
43
|
+
return ctx[AUTH_CACHE];
|
|
44
|
+
}
|
|
32
45
|
const user = {
|
|
33
|
-
|
|
46
|
+
id: async (ctx, request) => {
|
|
34
47
|
const identity = await ctx.auth.getUserIdentity();
|
|
35
48
|
if (identity !== null) {
|
|
36
49
|
const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
|
|
@@ -40,30 +53,27 @@ function createCoreDomains(deps) {
|
|
|
40
53
|
const authHeader = request.headers.get("Authorization");
|
|
41
54
|
if (authHeader?.startsWith("Bearer sk_")) {
|
|
42
55
|
const rawKey = authHeader.slice(7);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
56
|
+
const result = await getAuth().key.verify(ctx, rawKey);
|
|
57
|
+
if (result.ok) return result.userId;
|
|
58
|
+
return null;
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
return null;
|
|
51
62
|
},
|
|
52
|
-
require: async (ctx, request) => {
|
|
53
|
-
const userId = await user.current(ctx, request);
|
|
54
|
-
if (userId === null) throw new AuthError("NOT_SIGNED_IN").toConvexError();
|
|
55
|
-
return userId;
|
|
56
|
-
},
|
|
57
63
|
get: async (ctx, userId) => {
|
|
58
|
-
|
|
64
|
+
const c = cache(ctx);
|
|
65
|
+
if (c.users.has(userId)) return c.users.get(userId);
|
|
66
|
+
const result = await ctx.runQuery(config.component.public.userGetById, { userId });
|
|
67
|
+
c.users.set(userId, result);
|
|
68
|
+
return result;
|
|
59
69
|
},
|
|
60
70
|
list: async (ctx, opts = {}) => {
|
|
61
71
|
return await ctx.runQuery(config.component.public.userList, opts);
|
|
62
72
|
},
|
|
63
73
|
viewer: async (ctx) => {
|
|
64
|
-
const userId = await user.
|
|
74
|
+
const userId = await user.id(ctx);
|
|
65
75
|
if (userId === null) return null;
|
|
66
|
-
return await
|
|
76
|
+
return await user.get(ctx, userId);
|
|
67
77
|
},
|
|
68
78
|
update: async (ctx, userId, data) => {
|
|
69
79
|
await ctx.runMutation(config.component.public.userPatch, {
|
|
@@ -116,7 +126,10 @@ function createCoreDomains(deps) {
|
|
|
116
126
|
ctx.runQuery(config.component.public.totpListByUserId, { userId })
|
|
117
127
|
]);
|
|
118
128
|
const totalLinked = sessions.length + accounts.length + keys.length + members.length + passkeys.length + totps.length;
|
|
119
|
-
if (!cascade && totalLinked > 0)
|
|
129
|
+
if (!cascade && totalLinked > 0) return {
|
|
130
|
+
ok: false,
|
|
131
|
+
code: "INVALID_PARAMETERS"
|
|
132
|
+
};
|
|
120
133
|
const deletions = [];
|
|
121
134
|
for (const s of sessions) deletions.push(ctx.runMutation(config.component.public.sessionDelete, { sessionId: s._id }));
|
|
122
135
|
for (const a of accounts) deletions.push(ctx.runMutation(config.component.public.accountDelete, { accountId: a._id }));
|
|
@@ -163,7 +176,7 @@ function createCoreDomains(deps) {
|
|
|
163
176
|
},
|
|
164
177
|
get: async (ctx, args) => {
|
|
165
178
|
const result = await callRetrieveAccountWithCredentials(ctx, args);
|
|
166
|
-
if (typeof result === "string")
|
|
179
|
+
if (typeof result === "string") return null;
|
|
167
180
|
return result;
|
|
168
181
|
},
|
|
169
182
|
update: async (ctx, args) => {
|
|
@@ -175,8 +188,14 @@ function createCoreDomains(deps) {
|
|
|
175
188
|
},
|
|
176
189
|
delete: async (ctx, accountId) => {
|
|
177
190
|
const doc = await ctx.runQuery(config.component.public.accountGetById, { accountId });
|
|
178
|
-
if (doc === null)
|
|
179
|
-
|
|
191
|
+
if (doc === null) return {
|
|
192
|
+
ok: false,
|
|
193
|
+
code: "ACCOUNT_NOT_FOUND"
|
|
194
|
+
};
|
|
195
|
+
if ((await ctx.runQuery(config.component.public.accountListByUser, { userId: doc.userId })).length <= 1) return {
|
|
196
|
+
ok: false,
|
|
197
|
+
code: "INVALID_PARAMETERS"
|
|
198
|
+
};
|
|
180
199
|
await ctx.runMutation(config.component.public.accountDelete, { accountId });
|
|
181
200
|
return {
|
|
182
201
|
ok: true,
|
|
@@ -232,7 +251,11 @@ function createCoreDomains(deps) {
|
|
|
232
251
|
};
|
|
233
252
|
},
|
|
234
253
|
get: async (ctx, groupId) => {
|
|
235
|
-
|
|
254
|
+
const c = cache(ctx);
|
|
255
|
+
if (c.groups.has(groupId)) return c.groups.get(groupId);
|
|
256
|
+
const result = await ctx.runQuery(config.component.public.groupGet, { groupId });
|
|
257
|
+
c.groups.set(groupId, result);
|
|
258
|
+
return result;
|
|
236
259
|
},
|
|
237
260
|
list: async (ctx, opts) => {
|
|
238
261
|
return await ctx.runQuery(config.component.public.groupList, {
|
|
@@ -301,12 +324,17 @@ function createCoreDomains(deps) {
|
|
|
301
324
|
};
|
|
302
325
|
const member = {
|
|
303
326
|
create: async (ctx, data) => {
|
|
304
|
-
const
|
|
327
|
+
const normalized = normalizeRoleIds(data.roleIds);
|
|
328
|
+
if (!normalized.ok) return {
|
|
329
|
+
ok: false,
|
|
330
|
+
code: "INVALID_ROLE_IDS",
|
|
331
|
+
invalidRoleIds: normalized.invalidRoleIds
|
|
332
|
+
};
|
|
305
333
|
return {
|
|
306
334
|
ok: true,
|
|
307
335
|
memberId: await ctx.runMutation(config.component.public.memberAdd, {
|
|
308
336
|
...data,
|
|
309
|
-
roleIds
|
|
337
|
+
roleIds: normalized.roleIds
|
|
310
338
|
})
|
|
311
339
|
};
|
|
312
340
|
},
|
|
@@ -334,7 +362,15 @@ function createCoreDomains(deps) {
|
|
|
334
362
|
},
|
|
335
363
|
update: async (ctx, memberId, data) => {
|
|
336
364
|
const nextData = { ...data };
|
|
337
|
-
if ("roleIds" in nextData)
|
|
365
|
+
if ("roleIds" in nextData) {
|
|
366
|
+
const normalized = normalizeRoleIds(Array.isArray(nextData.roleIds) ? nextData.roleIds : void 0);
|
|
367
|
+
if (!normalized.ok) return {
|
|
368
|
+
ok: false,
|
|
369
|
+
code: "INVALID_ROLE_IDS",
|
|
370
|
+
invalidRoleIds: normalized.invalidRoleIds
|
|
371
|
+
};
|
|
372
|
+
nextData.roleIds = normalized.roleIds;
|
|
373
|
+
}
|
|
338
374
|
await ctx.runMutation(config.component.public.memberUpdate, {
|
|
339
375
|
memberId,
|
|
340
376
|
data: nextData
|
|
@@ -344,54 +380,25 @@ function createCoreDomains(deps) {
|
|
|
344
380
|
memberId
|
|
345
381
|
};
|
|
346
382
|
},
|
|
347
|
-
|
|
348
|
-
const
|
|
383
|
+
resolve: async (ctx, opts) => {
|
|
384
|
+
const normalized = normalizeRoleIds(opts.roleIds);
|
|
385
|
+
if (!normalized.ok) return {
|
|
386
|
+
ok: false,
|
|
387
|
+
code: "INVALID_ROLE_IDS",
|
|
388
|
+
invalidRoleIds: normalized.invalidRoleIds
|
|
389
|
+
};
|
|
390
|
+
const requestedRoleIds = normalized.roleIds;
|
|
349
391
|
const roleFilter = requestedRoleIds.length > 0 ? new Set(requestedRoleIds) : null;
|
|
350
392
|
const requiredGrants = Array.from(new Set(opts.grants ?? []));
|
|
351
393
|
const maxDepth = Math.max(0, Math.floor(opts.maxDepth ?? 32));
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
maxDepthReached = true;
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
if (visited.has(currentGroupId)) {
|
|
364
|
-
cycleDetected = true;
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
visited.add(currentGroupId);
|
|
368
|
-
traversedGroupIds.push(currentGroupId);
|
|
369
|
-
const membership = await member.getByUserAndGroup(ctx, {
|
|
370
|
-
userId: opts.userId,
|
|
371
|
-
groupId: currentGroupId
|
|
372
|
-
});
|
|
373
|
-
const membershipRoleIds = membership?.roleIds ?? [];
|
|
374
|
-
const membershipGrants = resolveGrantedPermissions(membershipRoleIds);
|
|
375
|
-
if (membership !== null && (roleFilter === null || membershipRoleIds.some((roleId) => roleFilter.has(roleId))) && requiredGrants.every((grant) => membershipGrants.includes(grant))) return {
|
|
376
|
-
requestedGroupId: opts.groupId,
|
|
377
|
-
matchedGroupId: currentGroupId,
|
|
378
|
-
membership,
|
|
379
|
-
roleIds: membershipRoleIds,
|
|
380
|
-
grants: membershipGrants,
|
|
381
|
-
missingGrants: [],
|
|
382
|
-
depth,
|
|
383
|
-
isDirect: depth === 0,
|
|
384
|
-
isInherited: depth > 0,
|
|
385
|
-
traversedGroupIds,
|
|
386
|
-
cycleDetected: false,
|
|
387
|
-
maxDepthReached: false
|
|
388
|
-
};
|
|
389
|
-
const doc = await group.get(ctx, currentGroupId);
|
|
390
|
-
if (doc === null || doc.parentGroupId === void 0) break;
|
|
391
|
-
currentGroupId = doc.parentGroupId;
|
|
392
|
-
depth += 1;
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
394
|
+
const result = await ctx.runQuery(config.component.public.memberResolve, {
|
|
395
|
+
userId: opts.userId,
|
|
396
|
+
groupId: opts.groupId,
|
|
397
|
+
maxDepth,
|
|
398
|
+
ancestry: true
|
|
399
|
+
});
|
|
400
|
+
const traversedGroupIds = result.traversedGroupIds ?? [];
|
|
401
|
+
if (result.membership === null) return {
|
|
395
402
|
requestedGroupId: opts.groupId,
|
|
396
403
|
matchedGroupId: null,
|
|
397
404
|
membership: null,
|
|
@@ -402,62 +409,103 @@ function createCoreDomains(deps) {
|
|
|
402
409
|
isDirect: false,
|
|
403
410
|
isInherited: false,
|
|
404
411
|
traversedGroupIds,
|
|
405
|
-
cycleDetected,
|
|
406
|
-
maxDepthReached
|
|
412
|
+
cycleDetected: false,
|
|
413
|
+
maxDepthReached: false
|
|
407
414
|
};
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
membership:
|
|
415
|
+
const membershipRoleIds = result.membership.roleIds ?? [];
|
|
416
|
+
const membershipGrants = resolveGrantedPermissions(membershipRoleIds);
|
|
417
|
+
if (roleFilter !== null && !membershipRoleIds.some((roleId) => roleFilter.has(roleId))) return {
|
|
418
|
+
requestedGroupId: opts.groupId,
|
|
419
|
+
matchedGroupId: null,
|
|
420
|
+
membership: null,
|
|
421
|
+
roleIds: [],
|
|
422
|
+
grants: [],
|
|
423
|
+
missingGrants: requiredGrants,
|
|
424
|
+
depth: null,
|
|
425
|
+
isDirect: false,
|
|
426
|
+
isInherited: false,
|
|
427
|
+
traversedGroupIds,
|
|
428
|
+
cycleDetected: false,
|
|
429
|
+
maxDepthReached: false
|
|
430
|
+
};
|
|
431
|
+
const missingGrants = requiredGrants.filter((grant) => !membershipGrants.includes(grant));
|
|
432
|
+
if (missingGrants.length > 0) return {
|
|
433
|
+
requestedGroupId: opts.groupId,
|
|
414
434
|
matchedGroupId: result.matchedGroupId,
|
|
415
|
-
|
|
416
|
-
|
|
435
|
+
membership: result.membership,
|
|
436
|
+
roleIds: membershipRoleIds,
|
|
437
|
+
grants: membershipGrants,
|
|
438
|
+
missingGrants,
|
|
439
|
+
depth: result.depth,
|
|
417
440
|
isDirect: result.isDirect,
|
|
418
441
|
isInherited: result.isInherited,
|
|
419
|
-
|
|
442
|
+
traversedGroupIds,
|
|
443
|
+
cycleDetected: false,
|
|
444
|
+
maxDepthReached: false
|
|
420
445
|
};
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
const access = {
|
|
424
|
-
check: async (ctx, opts) => {
|
|
425
|
-
const requiredGrants = Array.from(new Set(opts.grants));
|
|
426
|
-
const result = await member.inherit(ctx, {
|
|
427
|
-
userId: opts.userId,
|
|
428
|
-
groupId: opts.groupId,
|
|
429
|
-
grants: requiredGrants,
|
|
430
|
-
maxDepth: opts.maxDepth
|
|
431
|
-
});
|
|
432
|
-
const missingGrants = requiredGrants.filter((grant) => !result.grants.includes(grant));
|
|
433
446
|
return {
|
|
434
|
-
|
|
435
|
-
membership: result.membership,
|
|
447
|
+
requestedGroupId: opts.groupId,
|
|
436
448
|
matchedGroupId: result.matchedGroupId,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
449
|
+
membership: result.membership,
|
|
450
|
+
roleIds: membershipRoleIds,
|
|
451
|
+
grants: membershipGrants,
|
|
452
|
+
missingGrants: [],
|
|
453
|
+
depth: result.depth,
|
|
440
454
|
isDirect: result.isDirect,
|
|
441
455
|
isInherited: result.isInherited,
|
|
442
|
-
|
|
456
|
+
traversedGroupIds,
|
|
457
|
+
cycleDetected: false,
|
|
458
|
+
maxDepthReached: false
|
|
443
459
|
};
|
|
444
|
-
},
|
|
445
|
-
require: async (ctx, opts) => {
|
|
446
|
-
const result = await access.check(ctx, opts);
|
|
447
|
-
if (!result.ok) throw new AuthError("FORBIDDEN", `User ${opts.userId} is missing required grants on group ${opts.groupId}: ${result.missingGrants.join(", ")}.`).toConvexError();
|
|
448
|
-
return result;
|
|
449
460
|
}
|
|
450
461
|
};
|
|
462
|
+
const access = { check: async (ctx, opts) => {
|
|
463
|
+
const requiredGrants = Array.from(new Set(opts.grants));
|
|
464
|
+
const result = await member.resolve(ctx, {
|
|
465
|
+
userId: opts.userId,
|
|
466
|
+
groupId: opts.groupId,
|
|
467
|
+
grants: requiredGrants,
|
|
468
|
+
maxDepth: opts.maxDepth
|
|
469
|
+
});
|
|
470
|
+
if ("code" in result && result.code === "INVALID_ROLE_IDS") return {
|
|
471
|
+
ok: false,
|
|
472
|
+
membership: null,
|
|
473
|
+
matchedGroupId: null,
|
|
474
|
+
roleIds: [],
|
|
475
|
+
grants: [],
|
|
476
|
+
missingGrants: requiredGrants,
|
|
477
|
+
isDirect: false,
|
|
478
|
+
isInherited: false,
|
|
479
|
+
depth: null
|
|
480
|
+
};
|
|
481
|
+
const missingGrants = requiredGrants.filter((grant) => !result.grants.includes(grant));
|
|
482
|
+
return {
|
|
483
|
+
ok: result.membership !== null && missingGrants.length === 0,
|
|
484
|
+
membership: result.membership,
|
|
485
|
+
matchedGroupId: result.matchedGroupId,
|
|
486
|
+
roleIds: result.roleIds,
|
|
487
|
+
grants: result.grants,
|
|
488
|
+
missingGrants,
|
|
489
|
+
isDirect: result.isDirect,
|
|
490
|
+
isInherited: result.isInherited,
|
|
491
|
+
depth: result.depth
|
|
492
|
+
};
|
|
493
|
+
} };
|
|
451
494
|
const invite = {
|
|
452
495
|
create: async (ctx, data) => {
|
|
453
|
-
const
|
|
496
|
+
const normalized = normalizeRoleIds(data.roleIds);
|
|
497
|
+
if (!normalized.ok) return {
|
|
498
|
+
ok: false,
|
|
499
|
+
code: "INVALID_ROLE_IDS",
|
|
500
|
+
invalidRoleIds: normalized.invalidRoleIds
|
|
501
|
+
};
|
|
454
502
|
const token = generateRandomString(inviteTokenLength, inviteTokenAlphabet);
|
|
455
503
|
const tokenHash = await sha256(token);
|
|
456
504
|
return {
|
|
457
505
|
ok: true,
|
|
458
506
|
inviteId: await ctx.runMutation(config.component.public.inviteCreate, {
|
|
459
507
|
...data,
|
|
460
|
-
roleIds,
|
|
508
|
+
roleIds: normalized.roleIds,
|
|
461
509
|
tokenHash,
|
|
462
510
|
status: "pending"
|
|
463
511
|
}),
|
|
@@ -532,27 +580,38 @@ function createCoreDomains(deps) {
|
|
|
532
580
|
verify: async (ctx, rawKey) => {
|
|
533
581
|
const hashedKey = await hashApiKey(rawKey);
|
|
534
582
|
const doc = await ctx.runQuery(config.component.public.keyGetByHashedKey, { hashedKey });
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
583
|
+
if (!doc) return {
|
|
584
|
+
ok: false,
|
|
585
|
+
code: "INVALID_API_KEY"
|
|
586
|
+
};
|
|
587
|
+
const k = doc;
|
|
588
|
+
if (k.revoked) return {
|
|
589
|
+
ok: false,
|
|
590
|
+
code: "API_KEY_REVOKED"
|
|
591
|
+
};
|
|
592
|
+
if (k.expiresAt && k.expiresAt < Date.now()) return {
|
|
593
|
+
ok: false,
|
|
594
|
+
code: "API_KEY_EXPIRED"
|
|
595
|
+
};
|
|
596
|
+
const patchData = { lastUsedAt: Date.now() };
|
|
597
|
+
if (k.rateLimit) {
|
|
598
|
+
const { limited, newState } = checkKeyRateLimit(k.rateLimit, k.rateLimitState ?? void 0);
|
|
599
|
+
if (limited) return {
|
|
600
|
+
ok: false,
|
|
601
|
+
code: "API_KEY_RATE_LIMITED"
|
|
554
602
|
};
|
|
555
|
-
|
|
603
|
+
patchData.rateLimitState = newState;
|
|
604
|
+
}
|
|
605
|
+
await ctx.runMutation(config.component.public.keyPatch, {
|
|
606
|
+
keyId: k._id,
|
|
607
|
+
data: patchData
|
|
608
|
+
});
|
|
609
|
+
return {
|
|
610
|
+
ok: true,
|
|
611
|
+
userId: k.userId,
|
|
612
|
+
keyId: k._id,
|
|
613
|
+
scopes: buildScopeChecker(k.scopes)
|
|
614
|
+
};
|
|
556
615
|
},
|
|
557
616
|
list: async (ctx, opts) => {
|
|
558
617
|
return await ctx.runQuery(config.component.public.keyList, {
|
|
@@ -595,8 +654,14 @@ function createCoreDomains(deps) {
|
|
|
595
654
|
},
|
|
596
655
|
rotate: async (ctx, keyId, opts) => {
|
|
597
656
|
const existing = await ctx.runQuery(config.component.public.keyGetById, { keyId });
|
|
598
|
-
if (!existing)
|
|
599
|
-
|
|
657
|
+
if (!existing) return {
|
|
658
|
+
ok: false,
|
|
659
|
+
code: "INVALID_PARAMETERS"
|
|
660
|
+
};
|
|
661
|
+
if (existing.revoked === true) return {
|
|
662
|
+
ok: false,
|
|
663
|
+
code: "API_KEY_REVOKED"
|
|
664
|
+
};
|
|
600
665
|
await ctx.runMutation(config.component.public.keyPatch, {
|
|
601
666
|
keyId,
|
|
602
667
|
data: { revoked: true }
|