@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
|
@@ -39,10 +39,23 @@ export const groupCreate = mutation({
|
|
|
39
39
|
handler: async (ctx, args) => {
|
|
40
40
|
const { tags: rawTags, ...rest } = args;
|
|
41
41
|
const normalizedTags = rawTags ? normalizeTags(rawTags) : undefined;
|
|
42
|
+
const isRoot = !args.parentGroupId;
|
|
43
|
+
// Compute rootGroupId: root groups self-reference, children inherit from parent
|
|
44
|
+
let rootGroupId: Id<"Group"> | undefined;
|
|
45
|
+
if (!isRoot && args.parentGroupId) {
|
|
46
|
+
const parent = await ctx.db.get(args.parentGroupId);
|
|
47
|
+
rootGroupId = parent?.rootGroupId ?? args.parentGroupId;
|
|
48
|
+
}
|
|
42
49
|
const groupId = await ctx.db.insert("Group", {
|
|
43
50
|
...rest,
|
|
44
51
|
tags: normalizedTags,
|
|
52
|
+
isRoot,
|
|
53
|
+
rootGroupId: isRoot ? undefined : rootGroupId,
|
|
45
54
|
});
|
|
55
|
+
// Self-reference for root groups (need the ID after insert)
|
|
56
|
+
if (isRoot) {
|
|
57
|
+
await ctx.db.patch(groupId, { rootGroupId: groupId });
|
|
58
|
+
}
|
|
46
59
|
// Sync companion group_tag rows
|
|
47
60
|
if (normalizedTags) {
|
|
48
61
|
for (const tag of normalizedTags) {
|
|
@@ -178,6 +191,10 @@ export const groupList = query({
|
|
|
178
191
|
.withIndex("parent_group_id", (idx) =>
|
|
179
192
|
idx.eq("parentGroupId", where.parentGroupId!),
|
|
180
193
|
);
|
|
194
|
+
} else if (where.isRoot !== undefined) {
|
|
195
|
+
q = ctx.db
|
|
196
|
+
.query("Group")
|
|
197
|
+
.withIndex("is_root", (idx) => idx.eq("isRoot", where.isRoot!));
|
|
181
198
|
} else {
|
|
182
199
|
q = ctx.db.query("Group");
|
|
183
200
|
}
|
|
@@ -186,10 +203,13 @@ export const groupList = query({
|
|
|
186
203
|
if (where.name !== undefined) {
|
|
187
204
|
q = q.filter((f) => f.eq(f.field("name"), where.name!));
|
|
188
205
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
// isRoot filter when not already used as primary index
|
|
207
|
+
if (
|
|
208
|
+
where.isRoot !== undefined &&
|
|
209
|
+
where.parentGroupId === undefined &&
|
|
210
|
+
(where.type !== undefined || where.slug !== undefined)
|
|
211
|
+
) {
|
|
212
|
+
q = q.filter((f) => f.eq(f.field("isRoot"), where.isRoot!));
|
|
193
213
|
}
|
|
194
214
|
// slug filter when not used as index
|
|
195
215
|
if (where.slug !== undefined && where.type !== undefined) {
|
|
@@ -226,6 +246,38 @@ export const groupUpdate = mutation({
|
|
|
226
246
|
args: { groupId: v.id("Group"), data: v.any() },
|
|
227
247
|
returns: v.null(),
|
|
228
248
|
handler: async (ctx, { groupId, data }) => {
|
|
249
|
+
// If parentGroupId is changing, recompute rootGroupId + isRoot for this group and descendants
|
|
250
|
+
if (data.parentGroupId !== undefined) {
|
|
251
|
+
const oldGroup = await ctx.db.get("Group", groupId);
|
|
252
|
+
const oldRootGroupId = oldGroup?.rootGroupId;
|
|
253
|
+
const newParentGroupId = data.parentGroupId as Id<"Group"> | undefined;
|
|
254
|
+
const newIsRoot = !newParentGroupId;
|
|
255
|
+
let newRootGroupId: Id<"Group">;
|
|
256
|
+
if (newIsRoot) {
|
|
257
|
+
newRootGroupId = groupId;
|
|
258
|
+
} else {
|
|
259
|
+
const parent = await ctx.db.get("Group", newParentGroupId!);
|
|
260
|
+
newRootGroupId = parent?.rootGroupId ?? newParentGroupId!;
|
|
261
|
+
}
|
|
262
|
+
data.isRoot = newIsRoot;
|
|
263
|
+
data.rootGroupId = newRootGroupId;
|
|
264
|
+
// Cascade to descendants if rootGroupId changed
|
|
265
|
+
if (oldRootGroupId && oldRootGroupId !== newRootGroupId) {
|
|
266
|
+
const descendants = await ctx.db
|
|
267
|
+
.query("Group")
|
|
268
|
+
.withIndex("root_group_id", (q) =>
|
|
269
|
+
q.eq("rootGroupId", oldRootGroupId),
|
|
270
|
+
)
|
|
271
|
+
.collect();
|
|
272
|
+
for (const desc of descendants) {
|
|
273
|
+
if (desc._id !== groupId) {
|
|
274
|
+
await ctx.db.patch("Group", desc._id, {
|
|
275
|
+
rootGroupId: newRootGroupId,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
229
281
|
// If tags are being updated, normalize and replace the full tag set
|
|
230
282
|
if (data.tags !== undefined) {
|
|
231
283
|
const normalizedTags: TagPair[] = Array.isArray(data.tags)
|
|
@@ -401,6 +453,15 @@ export const memberList = query({
|
|
|
401
453
|
.withIndex("group_id_user_id", (idx) =>
|
|
402
454
|
idx.eq("groupId", where.groupId!).eq("userId", where.userId!),
|
|
403
455
|
);
|
|
456
|
+
if (where.status !== undefined) {
|
|
457
|
+
q = q.filter((f) => f.eq(f.field("status"), where.status!));
|
|
458
|
+
}
|
|
459
|
+
} else if (where.groupId !== undefined && where.status !== undefined) {
|
|
460
|
+
q = ctx.db
|
|
461
|
+
.query("GroupMember")
|
|
462
|
+
.withIndex("group_id_status", (idx) =>
|
|
463
|
+
idx.eq("groupId", where.groupId!).eq("status", where.status!),
|
|
464
|
+
);
|
|
404
465
|
} else if (where.groupId !== undefined) {
|
|
405
466
|
q = ctx.db
|
|
406
467
|
.query("GroupMember")
|
|
@@ -409,12 +470,14 @@ export const memberList = query({
|
|
|
409
470
|
q = ctx.db
|
|
410
471
|
.query("GroupMember")
|
|
411
472
|
.withIndex("user_id", (idx) => idx.eq("userId", where.userId!));
|
|
473
|
+
if (where.status !== undefined) {
|
|
474
|
+
q = q.filter((f) => f.eq(f.field("status"), where.status!));
|
|
475
|
+
}
|
|
412
476
|
} else {
|
|
413
477
|
q = ctx.db.query("GroupMember");
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
q = q.filter((f) => f.eq(f.field("status"), where.status!));
|
|
478
|
+
if (where.status !== undefined) {
|
|
479
|
+
q = q.filter((f) => f.eq(f.field("status"), where.status!));
|
|
480
|
+
}
|
|
418
481
|
}
|
|
419
482
|
|
|
420
483
|
q = q.order(order);
|
|
@@ -470,6 +533,77 @@ export const memberGetByGroupAndUser = query({
|
|
|
470
533
|
},
|
|
471
534
|
});
|
|
472
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Resolve a user's membership by walking the group hierarchy from the
|
|
538
|
+
* requested group up to the root. Returns the first matching membership
|
|
539
|
+
* found. When `ancestry` is true, includes the list of traversed group IDs.
|
|
540
|
+
*
|
|
541
|
+
* This runs entirely inside the component (no cross-component RPCs per level).
|
|
542
|
+
*/
|
|
543
|
+
export const memberResolve = query({
|
|
544
|
+
args: {
|
|
545
|
+
userId: v.id("User"),
|
|
546
|
+
groupId: v.id("Group"),
|
|
547
|
+
maxDepth: v.optional(v.number()),
|
|
548
|
+
ancestry: v.optional(v.boolean()),
|
|
549
|
+
},
|
|
550
|
+
returns: v.object({
|
|
551
|
+
membership: v.union(vGroupMemberDoc, v.null()),
|
|
552
|
+
matchedGroupId: v.union(v.id("Group"), v.null()),
|
|
553
|
+
depth: v.union(v.number(), v.null()),
|
|
554
|
+
isDirect: v.boolean(),
|
|
555
|
+
isInherited: v.boolean(),
|
|
556
|
+
traversedGroupIds: v.optional(v.array(v.id("Group"))),
|
|
557
|
+
}),
|
|
558
|
+
handler: async (ctx, args) => {
|
|
559
|
+
const maxDepth = Math.max(0, Math.floor(args.maxDepth ?? 32));
|
|
560
|
+
const includeAncestry = args.ancestry ?? false;
|
|
561
|
+
const visited = new Set<string>();
|
|
562
|
+
const traversedGroupIds: Id<"Group">[] = [];
|
|
563
|
+
let currentGroupId: Id<"Group"> | undefined = args.groupId;
|
|
564
|
+
let depth = 0;
|
|
565
|
+
|
|
566
|
+
while (currentGroupId !== undefined && depth <= maxDepth) {
|
|
567
|
+
if (visited.has(currentGroupId)) break;
|
|
568
|
+
visited.add(currentGroupId);
|
|
569
|
+
if (includeAncestry) traversedGroupIds.push(currentGroupId);
|
|
570
|
+
|
|
571
|
+
const membership = await ctx.db
|
|
572
|
+
.query("GroupMember")
|
|
573
|
+
.withIndex("group_id_user_id", (q) =>
|
|
574
|
+
q.eq("groupId", currentGroupId!).eq("userId", args.userId),
|
|
575
|
+
)
|
|
576
|
+
.unique();
|
|
577
|
+
|
|
578
|
+
if (membership !== null) {
|
|
579
|
+
return {
|
|
580
|
+
membership,
|
|
581
|
+
matchedGroupId: currentGroupId,
|
|
582
|
+
depth,
|
|
583
|
+
isDirect: depth === 0,
|
|
584
|
+
isInherited: depth > 0,
|
|
585
|
+
...(includeAncestry ? { traversedGroupIds } : {}),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const groupDoc: { parentGroupId?: Id<"Group"> } | null =
|
|
590
|
+
await ctx.db.get(currentGroupId);
|
|
591
|
+
if (!groupDoc?.parentGroupId) break;
|
|
592
|
+
currentGroupId = groupDoc.parentGroupId;
|
|
593
|
+
depth++;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
membership: null,
|
|
598
|
+
matchedGroupId: null,
|
|
599
|
+
depth: null,
|
|
600
|
+
isDirect: false,
|
|
601
|
+
isInherited: false,
|
|
602
|
+
...(includeAncestry ? { traversedGroupIds } : {}),
|
|
603
|
+
};
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
|
|
473
607
|
/** Remove a member from a group by deleting the member record. */
|
|
474
608
|
export const memberRemove = mutation({
|
|
475
609
|
args: { memberId: v.id("GroupMember") },
|
|
@@ -114,8 +114,9 @@ export const userFindByVerifiedEmail = query({
|
|
|
114
114
|
handler: async (ctx, { email }) => {
|
|
115
115
|
const users = await ctx.db
|
|
116
116
|
.query("User")
|
|
117
|
-
.withIndex("
|
|
118
|
-
|
|
117
|
+
.withIndex("email_verified", (q) =>
|
|
118
|
+
q.eq("email", email).gt("emailVerificationTime", undefined),
|
|
119
|
+
)
|
|
119
120
|
.take(2);
|
|
120
121
|
return users.length === 1 ? users[0] : null;
|
|
121
122
|
},
|
|
@@ -132,8 +133,9 @@ export const userFindByVerifiedPhone = query({
|
|
|
132
133
|
handler: async (ctx, { phone }) => {
|
|
133
134
|
const users = await ctx.db
|
|
134
135
|
.query("User")
|
|
135
|
-
.withIndex("
|
|
136
|
-
|
|
136
|
+
.withIndex("phone_verified", (q) =>
|
|
137
|
+
q.eq("phone", phone).gt("phoneVerificationTime", undefined),
|
|
138
|
+
)
|
|
137
139
|
.take(2);
|
|
138
140
|
return users.length === 1 ? users[0] : null;
|
|
139
141
|
},
|
|
@@ -558,8 +560,9 @@ export const refreshTokenGetActive = query({
|
|
|
558
560
|
handler: async (ctx, { sessionId }) => {
|
|
559
561
|
return await ctx.db
|
|
560
562
|
.query("RefreshToken")
|
|
561
|
-
.withIndex("
|
|
562
|
-
|
|
563
|
+
.withIndex("session_id_first_used", (q) =>
|
|
564
|
+
q.eq("sessionId", sessionId as any).eq("firstUsedTime", undefined),
|
|
565
|
+
)
|
|
563
566
|
.order("desc")
|
|
564
567
|
.first();
|
|
565
568
|
},
|
package/src/component/schema.ts
CHANGED
|
@@ -42,7 +42,9 @@ export default defineSchema({
|
|
|
42
42
|
extend: v.optional(v.any()),
|
|
43
43
|
})
|
|
44
44
|
.index("email", ["email"])
|
|
45
|
-
.index("
|
|
45
|
+
.index("email_verified", ["email", "emailVerificationTime"])
|
|
46
|
+
.index("phone", ["phone"])
|
|
47
|
+
.index("phone_verified", ["phone", "phoneVerificationTime"]),
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Active sessions. A single user can have multiple concurrent sessions
|
|
@@ -86,6 +88,7 @@ export default defineSchema({
|
|
|
86
88
|
parentRefreshTokenId: v.optional(v.id("RefreshToken")),
|
|
87
89
|
})
|
|
88
90
|
.index("session_id", ["sessionId"])
|
|
91
|
+
.index("session_id_first_used", ["sessionId", "firstUsedTime"])
|
|
89
92
|
.index("session_id_parent_refresh_token_id", [
|
|
90
93
|
"sessionId",
|
|
91
94
|
"parentRefreshTokenId",
|
|
@@ -167,7 +170,9 @@ export default defineSchema({
|
|
|
167
170
|
name: v.optional(v.string()),
|
|
168
171
|
createdAt: v.number(),
|
|
169
172
|
lastUsedAt: v.optional(v.number()),
|
|
170
|
-
})
|
|
173
|
+
})
|
|
174
|
+
.index("user_id", ["userId"])
|
|
175
|
+
.index("user_id_verified", ["userId", "verified"]),
|
|
171
176
|
|
|
172
177
|
/**
|
|
173
178
|
* Device authorization codes (RFC 8628). Each record tracks a pending
|
|
@@ -214,12 +219,18 @@ export default defineSchema({
|
|
|
214
219
|
slug: v.optional(v.string()),
|
|
215
220
|
type: v.optional(v.string()),
|
|
216
221
|
parentGroupId: v.optional(v.id("Group")),
|
|
222
|
+
/** Denormalized root group ID. Self-referencing for root groups. */
|
|
223
|
+
rootGroupId: v.optional(v.id("Group")),
|
|
224
|
+
/** Denormalized flag: `true` when `parentGroupId` is absent. */
|
|
225
|
+
isRoot: v.optional(v.boolean()),
|
|
217
226
|
/** Faceted classification tags. Normalized at write time (trimmed, lowercased). */
|
|
218
227
|
tags: v.optional(v.array(vTag)),
|
|
219
228
|
extend: v.optional(v.any()),
|
|
220
229
|
})
|
|
221
230
|
.index("slug", ["slug"])
|
|
222
231
|
.index("parent_group_id", ["parentGroupId"])
|
|
232
|
+
.index("root_group_id", ["rootGroupId"])
|
|
233
|
+
.index("is_root", ["isRoot"])
|
|
223
234
|
.index("type", ["type"])
|
|
224
235
|
.index("type_parent_group_id", ["type", "parentGroupId"]),
|
|
225
236
|
|
|
@@ -252,6 +263,7 @@ export default defineSchema({
|
|
|
252
263
|
})
|
|
253
264
|
.index("group_id", ["groupId"])
|
|
254
265
|
.index("group_id_user_id", ["groupId", "userId"])
|
|
266
|
+
.index("group_id_status", ["groupId", "status"])
|
|
255
267
|
.index("user_id", ["userId"]),
|
|
256
268
|
|
|
257
269
|
/**
|
package/src/server/auth.ts
CHANGED
|
@@ -39,7 +39,7 @@ type MemberApiWithAuthorization<
|
|
|
39
39
|
TAuthorization extends AuthAuthorizationConfig | undefined,
|
|
40
40
|
> = Omit<
|
|
41
41
|
ReturnType<typeof AuthFactory>["auth"]["member"],
|
|
42
|
-
"create" | "list" | "update" | "
|
|
42
|
+
"create" | "list" | "update" | "resolve"
|
|
43
43
|
> & {
|
|
44
44
|
create: (
|
|
45
45
|
ctx: Parameters<
|
|
@@ -77,9 +77,9 @@ type MemberApiWithAuthorization<
|
|
|
77
77
|
memberId: string,
|
|
78
78
|
data: Record<string, unknown> & { roleIds?: AuthRoleId<TAuthorization>[] },
|
|
79
79
|
) => Promise<{ ok: true; memberId: string }>;
|
|
80
|
-
|
|
80
|
+
resolve: (
|
|
81
81
|
ctx: Parameters<
|
|
82
|
-
ReturnType<typeof AuthFactory>["auth"]["member"]["
|
|
82
|
+
ReturnType<typeof AuthFactory>["auth"]["member"]["resolve"]
|
|
83
83
|
>[0],
|
|
84
84
|
opts: {
|
|
85
85
|
userId: string;
|
|
@@ -88,19 +88,7 @@ type MemberApiWithAuthorization<
|
|
|
88
88
|
grants?: AuthGrant<TAuthorization>[];
|
|
89
89
|
maxDepth?: number;
|
|
90
90
|
},
|
|
91
|
-
) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["member"]["
|
|
92
|
-
require: (
|
|
93
|
-
ctx: Parameters<
|
|
94
|
-
ReturnType<typeof AuthFactory>["auth"]["member"]["require"]
|
|
95
|
-
>[0],
|
|
96
|
-
opts: {
|
|
97
|
-
userId: string;
|
|
98
|
-
groupId: string;
|
|
99
|
-
roleIds?: AuthRoleId<TAuthorization>[];
|
|
100
|
-
grants?: AuthGrant<TAuthorization>[];
|
|
101
|
-
maxDepth?: number;
|
|
102
|
-
},
|
|
103
|
-
) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["member"]["require"]>;
|
|
91
|
+
) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["member"]["resolve"]>;
|
|
104
92
|
};
|
|
105
93
|
|
|
106
94
|
type AccessApiWithAuthorization<
|
|
@@ -117,17 +105,6 @@ type AccessApiWithAuthorization<
|
|
|
117
105
|
maxDepth?: number;
|
|
118
106
|
},
|
|
119
107
|
) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["access"]["check"]>;
|
|
120
|
-
require: (
|
|
121
|
-
ctx: Parameters<
|
|
122
|
-
ReturnType<typeof AuthFactory>["auth"]["access"]["require"]
|
|
123
|
-
>[0],
|
|
124
|
-
opts: {
|
|
125
|
-
userId: string;
|
|
126
|
-
groupId: string;
|
|
127
|
-
grants: AuthGrant<TAuthorization>[];
|
|
128
|
-
maxDepth?: number;
|
|
129
|
-
},
|
|
130
|
-
) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["access"]["require"]>;
|
|
131
108
|
};
|
|
132
109
|
|
|
133
110
|
/** The base auth API surface, without conditional namespaces. */
|
|
@@ -488,38 +465,6 @@ export function createAuth<
|
|
|
488
465
|
} as unknown as ConvexAuthResult<P, TAuthorization>;
|
|
489
466
|
}
|
|
490
467
|
|
|
491
|
-
export function defineRoles<
|
|
492
|
-
const TRoles extends Record<
|
|
493
|
-
string,
|
|
494
|
-
{ label?: string; grants: readonly string[] }
|
|
495
|
-
>,
|
|
496
|
-
>(
|
|
497
|
-
roles: TRoles,
|
|
498
|
-
): {
|
|
499
|
-
[K in keyof TRoles]: {
|
|
500
|
-
id: K & string;
|
|
501
|
-
label?: TRoles[K]["label"];
|
|
502
|
-
grants: Array<TRoles[K]["grants"][number] & string>;
|
|
503
|
-
};
|
|
504
|
-
} {
|
|
505
|
-
return Object.fromEntries(
|
|
506
|
-
Object.entries(roles).map(([id, role]) => [
|
|
507
|
-
id,
|
|
508
|
-
{
|
|
509
|
-
id,
|
|
510
|
-
...(role.label ? { label: role.label } : {}),
|
|
511
|
-
grants: [...role.grants],
|
|
512
|
-
},
|
|
513
|
-
]),
|
|
514
|
-
) as {
|
|
515
|
-
[K in keyof TRoles]: {
|
|
516
|
-
id: K & string;
|
|
517
|
-
label?: TRoles[K]["label"];
|
|
518
|
-
grants: Array<TRoles[K]["grants"][number] & string>;
|
|
519
|
-
};
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
468
|
// ============================================================================
|
|
524
469
|
// AuthCtx — ctx enrichment for customQuery / customMutation
|
|
525
470
|
// ============================================================================
|
|
@@ -593,7 +538,7 @@ export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
|
|
|
593
538
|
const userContext = await Fx.run(
|
|
594
539
|
Fx.match(modeDispatch, modeDispatch.mode, {
|
|
595
540
|
optional: async () => {
|
|
596
|
-
const userId = await auth.user.
|
|
541
|
+
const userId = await auth.user.id(ctx);
|
|
597
542
|
if (!userId) {
|
|
598
543
|
return null;
|
|
599
544
|
}
|
|
@@ -601,7 +546,10 @@ export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
|
|
|
601
546
|
return { userId, user };
|
|
602
547
|
},
|
|
603
548
|
required: async () => {
|
|
604
|
-
const userId = await auth.user.
|
|
549
|
+
const userId = await auth.user.id(ctx);
|
|
550
|
+
if (!userId) {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
605
553
|
const user = await auth.user.get(ctx, userId);
|
|
606
554
|
return { userId, user };
|
|
607
555
|
},
|