@robelest/convex-auth 0.0.4-preview.28 → 0.0.4-preview.29
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/bin.js +4 -1
- package/dist/component/_generated/component.d.ts +344 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/model.d.ts +25 -25
- package/dist/component/model.js +1 -0
- package/dist/component/public/factors/totp.js +12 -0
- package/dist/component/public/groups/core.js +89 -1
- package/dist/component/public/groups/members.js +39 -1
- package/dist/component/public/identity/accounts.js +22 -3
- package/dist/component/public/identity/users.js +79 -5
- package/dist/component/public/sso/audit.js +5 -2
- package/dist/component/public/sso/core.js +3 -3
- package/dist/component/public/sso/domains.js +7 -3
- package/dist/component/public/sso/scim.js +36 -1
- package/dist/component/public.js +5 -5
- package/dist/component/schema.d.ts +293 -289
- package/dist/component/schema.js +2 -1
- package/dist/core/index.d.ts +63 -27
- package/dist/core/index.js +1 -0
- package/dist/providers/credentials.d.ts +12 -0
- package/dist/providers/password.js +28 -7
- package/dist/server/auth-context.d.ts +17 -0
- package/dist/server/auth-context.js +1 -1
- package/dist/server/auth.d.ts +18 -6
- package/dist/server/auth.js +1 -0
- package/dist/server/context.js +11 -5
- package/dist/server/contract.js +44 -12
- package/dist/server/core.js +146 -149
- package/dist/server/ctxCache.js +94 -0
- package/dist/server/limits.js +39 -9
- package/dist/server/mounts.d.ts +85 -85
- package/dist/server/mutations/credentialsSignIn.js +114 -0
- package/dist/server/mutations/index.js +2 -1
- package/dist/server/mutations/refresh.js +25 -12
- package/dist/server/mutations/retrieve.js +2 -1
- package/dist/server/mutations/signin.js +27 -9
- package/dist/server/mutations/store.js +5 -0
- package/dist/server/mutations/verify.js +32 -8
- package/dist/server/runtime.d.ts +81 -47
- package/dist/server/services/group.js +23 -16
- package/dist/server/sessions.d.ts +21 -0
- package/dist/server/sessions.js +37 -27
- package/dist/server/signin.js +32 -9
- package/dist/server/sso/domain.js +1 -2
- package/dist/server/sso/oidc.js +1 -1
- package/dist/server/tokens.js +19 -3
- package/dist/server/users.js +3 -2
- package/dist/server/utils/span.js +18 -0
- package/package.json +1 -1
- package/README.md +0 -161
|
@@ -107,6 +107,94 @@ const groupGet = query({
|
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
/**
|
|
110
|
+
* Batched equivalent of {@link groupGet}. Fetches many group documents by ID
|
|
111
|
+
* in a single component round-trip. Returns each group (or `null`) in the
|
|
112
|
+
* same slot as the input ID; duplicates are tolerated but de-duplicated
|
|
113
|
+
* internally so each `ctx.db.get` runs exactly once per distinct ID.
|
|
114
|
+
*
|
|
115
|
+
* Used by app-side aggregate handlers (e.g. `groups:listMyGroups`) that
|
|
116
|
+
* previously fanned out one `groupGet` per item.
|
|
117
|
+
*
|
|
118
|
+
* @param args.groupIds - Array of group document IDs.
|
|
119
|
+
* @returns Array of group documents (or `null` entries) in input order.
|
|
120
|
+
*/
|
|
121
|
+
const groupGetMany = query({
|
|
122
|
+
args: { groupIds: v.array(v.id("Group")) },
|
|
123
|
+
returns: v.array(v.union(vGroupDoc, v.null())),
|
|
124
|
+
handler: async (ctx, { groupIds }) => {
|
|
125
|
+
if (groupIds.length === 0) return [];
|
|
126
|
+
const unique = Array.from(new Set(groupIds));
|
|
127
|
+
const docs = await Promise.all(unique.map((id) => ctx.db.get("Group", id)));
|
|
128
|
+
const byId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
|
|
129
|
+
return groupIds.map((id) => byId.get(id) ?? null);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Walk up the group hierarchy from `groupId` in a single component
|
|
134
|
+
* round-trip and return every ancestor document. Consolidates what would
|
|
135
|
+
* otherwise be D successive `groupGet` calls (one per level) when the app
|
|
136
|
+
* resolver steps parent-by-parent.
|
|
137
|
+
*
|
|
138
|
+
* Cycle detection walks up only along `parentGroupId` links; if the chain
|
|
139
|
+
* revisits a group, the traversal stops with `cycleDetected: true`. Stops
|
|
140
|
+
* early with `maxDepthReached: true` when the depth limit is hit.
|
|
141
|
+
*
|
|
142
|
+
* @param args.groupId - Starting group id. The top of the walk.
|
|
143
|
+
* @param args.maxDepth - Maximum number of ancestor levels to visit
|
|
144
|
+
* (default 32). Set to 0 to inspect only the starting group.
|
|
145
|
+
* @param args.includeSelf - When `true`, include the starting group in
|
|
146
|
+
* the returned `ancestors` array. Default `false`.
|
|
147
|
+
* @returns `{ ancestors, cycleDetected, maxDepthReached }` — ancestors
|
|
148
|
+
* are ordered from the immediate parent upward (or starting at
|
|
149
|
+
* `groupId` when `includeSelf` is set).
|
|
150
|
+
*/
|
|
151
|
+
const groupAncestors = query({
|
|
152
|
+
args: {
|
|
153
|
+
groupId: v.id("Group"),
|
|
154
|
+
maxDepth: v.optional(v.number()),
|
|
155
|
+
includeSelf: v.optional(v.boolean())
|
|
156
|
+
},
|
|
157
|
+
returns: v.object({
|
|
158
|
+
ancestors: v.array(vGroupDoc),
|
|
159
|
+
cycleDetected: v.boolean(),
|
|
160
|
+
maxDepthReached: v.boolean()
|
|
161
|
+
}),
|
|
162
|
+
handler: async (ctx, { groupId, maxDepth, includeSelf }) => {
|
|
163
|
+
const limit = Math.max(0, Math.floor(maxDepth ?? 32));
|
|
164
|
+
const visited = /* @__PURE__ */ new Set();
|
|
165
|
+
const ancestors = [];
|
|
166
|
+
let cycleDetected = false;
|
|
167
|
+
let maxDepthReached = false;
|
|
168
|
+
let current = groupId;
|
|
169
|
+
let depth = 0;
|
|
170
|
+
let first = true;
|
|
171
|
+
while (current !== void 0) {
|
|
172
|
+
if (depth > limit) {
|
|
173
|
+
maxDepthReached = true;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
if (visited.has(current)) {
|
|
177
|
+
cycleDetected = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
visited.add(current);
|
|
181
|
+
const doc = await ctx.db.get("Group", current);
|
|
182
|
+
if (doc === null) break;
|
|
183
|
+
if (first) {
|
|
184
|
+
first = false;
|
|
185
|
+
if (includeSelf === true) ancestors.push(doc);
|
|
186
|
+
} else ancestors.push(doc);
|
|
187
|
+
current = doc.parentGroupId;
|
|
188
|
+
depth += 1;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
ancestors,
|
|
192
|
+
cycleDetected,
|
|
193
|
+
maxDepthReached
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
/**
|
|
110
198
|
* List groups with optional filtering, sorting, and pagination.
|
|
111
199
|
*
|
|
112
200
|
* Returns `{ items, nextCursor }`. Empty `where` returns **all** groups.
|
|
@@ -317,5 +405,5 @@ const groupDelete = mutation({
|
|
|
317
405
|
});
|
|
318
406
|
|
|
319
407
|
//#endregion
|
|
320
|
-
export { groupCreate, groupDelete, groupGet, groupList, groupUpdate };
|
|
408
|
+
export { groupAncestors, groupCreate, groupDelete, groupGet, groupGetMany, groupList, groupUpdate };
|
|
321
409
|
//# sourceMappingURL=core.js.map
|
|
@@ -196,6 +196,44 @@ const memberGetByGroupAndUser = query({
|
|
|
196
196
|
}
|
|
197
197
|
});
|
|
198
198
|
/**
|
|
199
|
+
* Batched equivalent of {@link memberGetByGroupAndUser}. Resolves many
|
|
200
|
+
* `(groupId, userId)` pairs for the same user in a single component
|
|
201
|
+
* round-trip. Used by app-side handlers (e.g. `groups:getDashboard`) that
|
|
202
|
+
* need to inspect the current user's membership across every root group
|
|
203
|
+
* they can see.
|
|
204
|
+
*
|
|
205
|
+
* Each `groupId` is resolved using the `group_id_user_id` index; missing
|
|
206
|
+
* memberships come back as `null` in the same slot order as the input.
|
|
207
|
+
*
|
|
208
|
+
* @param args.userId - The user whose memberships to look up.
|
|
209
|
+
* @param args.groupIds - One or more groups to resolve. Order is preserved;
|
|
210
|
+
* duplicates tolerated (but de-duplicated internally so the DB only sees
|
|
211
|
+
* each pair once).
|
|
212
|
+
* @returns Array of member documents or `null` entries, in `groupIds` order.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const members = await ctx.runQuery(
|
|
217
|
+
* components.auth.groups.memberGetByGroupAndUserMany,
|
|
218
|
+
* { userId: viewerId, groupIds: rootGroupIds },
|
|
219
|
+
* );
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
const memberGetByGroupAndUserMany = query({
|
|
223
|
+
args: {
|
|
224
|
+
userId: v.id("User"),
|
|
225
|
+
groupIds: v.array(v.id("Group"))
|
|
226
|
+
},
|
|
227
|
+
returns: v.array(v.union(vGroupMemberDoc, v.null())),
|
|
228
|
+
handler: async (ctx, { userId, groupIds }) => {
|
|
229
|
+
if (groupIds.length === 0) return [];
|
|
230
|
+
const unique = Array.from(new Set(groupIds));
|
|
231
|
+
const docs = await Promise.all(unique.map((groupId) => ctx.db.query("GroupMember").withIndex("group_id_user_id", (q) => q.eq("groupId", groupId).eq("userId", userId)).unique()));
|
|
232
|
+
const byGroupId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
|
|
233
|
+
return groupIds.map((id) => byGroupId.get(id) ?? null);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
199
237
|
* Resolve a user's membership by walking the group hierarchy from the
|
|
200
238
|
* requested group up to the root. Returns the first matching membership
|
|
201
239
|
* found, enabling inherited (ancestor-level) access checks.
|
|
@@ -351,5 +389,5 @@ const memberUpdate = mutation({
|
|
|
351
389
|
});
|
|
352
390
|
|
|
353
391
|
//#endregion
|
|
354
|
-
export { memberAdd, memberGet, memberGetByGroupAndUser, memberList, memberRemove, memberResolve, memberUpdate };
|
|
392
|
+
export { memberAdd, memberGet, memberGetByGroupAndUser, memberGetByGroupAndUserMany, memberList, memberRemove, memberResolve, memberUpdate };
|
|
355
393
|
//# sourceMappingURL=members.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { vAccountDoc } from "../../model.js";
|
|
2
2
|
import { mutation, query } from "../../functions.js";
|
|
3
|
-
import { v } from "convex/values";
|
|
3
|
+
import { ConvexError, v } from "convex/values";
|
|
4
4
|
|
|
5
5
|
//#region src/component/public/identity/accounts.ts
|
|
6
6
|
/**
|
|
@@ -187,9 +187,28 @@ const accountPatch = mutation({
|
|
|
187
187
|
* ```
|
|
188
188
|
*/
|
|
189
189
|
const accountDelete = mutation({
|
|
190
|
-
args: {
|
|
190
|
+
args: {
|
|
191
|
+
accountId: v.id("Account"),
|
|
192
|
+
requireOtherAccount: v.optional(v.boolean())
|
|
193
|
+
},
|
|
191
194
|
returns: v.null(),
|
|
192
|
-
handler: async (ctx, { accountId }) => {
|
|
195
|
+
handler: async (ctx, { accountId, requireOtherAccount }) => {
|
|
196
|
+
if (requireOtherAccount === true) {
|
|
197
|
+
const doc = await ctx.db.get("Account", accountId);
|
|
198
|
+
if (doc === null) throw new ConvexError({
|
|
199
|
+
code: "ACCOUNT_NOT_FOUND",
|
|
200
|
+
message: "Account not found."
|
|
201
|
+
});
|
|
202
|
+
let otherFound = false;
|
|
203
|
+
for await (const sibling of ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", doc.userId))) if (sibling._id !== accountId) {
|
|
204
|
+
otherFound = true;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
if (!otherFound) throw new ConvexError({
|
|
208
|
+
code: "INVALID_PARAMETERS",
|
|
209
|
+
message: "The provided parameters are invalid."
|
|
210
|
+
});
|
|
211
|
+
}
|
|
193
212
|
await ctx.db.delete("Account", accountId);
|
|
194
213
|
return null;
|
|
195
214
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { vPaginated, vUserDoc } from "../../model.js";
|
|
2
2
|
import { mutation, query } from "../../functions.js";
|
|
3
|
-
import { v } from "convex/values";
|
|
3
|
+
import { ConvexError, v } from "convex/values";
|
|
4
4
|
|
|
5
5
|
//#region src/component/public/identity/users.ts
|
|
6
6
|
/**
|
|
@@ -111,6 +111,42 @@ const userGetById = query({
|
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
113
|
/**
|
|
114
|
+
* Fetch many user documents by ID in a single component round-trip.
|
|
115
|
+
*
|
|
116
|
+
* Equivalent to calling {@link userGetById} for each ID in parallel from the
|
|
117
|
+
* app side, but collapses what would be `N` cross-component RPCs into one.
|
|
118
|
+
* Returns the documents in the same order as the input IDs; missing users
|
|
119
|
+
* appear as `null`. Input is de-duplicated internally so passing the same
|
|
120
|
+
* ID twice costs exactly one `ctx.db.get`.
|
|
121
|
+
*
|
|
122
|
+
* Hot paths like `groups:getDashboard` (member summaries) and
|
|
123
|
+
* `issues:projectIssues` (assignee/creator lookups) previously fanned out
|
|
124
|
+
* N `userGetById` calls — this helper is the batched replacement.
|
|
125
|
+
*
|
|
126
|
+
* @param args.userIds - Array of user document IDs (order preserved, duplicates tolerated).
|
|
127
|
+
* @returns Array of user documents or `null` entries, in the same order as `args.userIds`.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const users = await ctx.runQuery(
|
|
132
|
+
* component.identity.users.userGetMany,
|
|
133
|
+
* { userIds: memberIds },
|
|
134
|
+
* );
|
|
135
|
+
* const byId = new Map(users.filter(u => u !== null).map(u => [u!._id, u!]));
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
const userGetMany = query({
|
|
139
|
+
args: { userIds: v.array(v.id("User")) },
|
|
140
|
+
returns: v.array(v.union(vUserDoc, v.null())),
|
|
141
|
+
handler: async (ctx, { userIds }) => {
|
|
142
|
+
if (userIds.length === 0) return [];
|
|
143
|
+
const unique = Array.from(new Set(userIds));
|
|
144
|
+
const docs = await Promise.all(unique.map((id) => ctx.db.get("User", id)));
|
|
145
|
+
const byId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
|
|
146
|
+
return userIds.map((id) => byId.get(id) ?? null);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
114
150
|
* Find a user by their verified email address.
|
|
115
151
|
*
|
|
116
152
|
* Queries the `User` table using the `email_verified` index to locate users
|
|
@@ -298,14 +334,52 @@ const userPatch = mutation({
|
|
|
298
334
|
* ```
|
|
299
335
|
*/
|
|
300
336
|
const userDelete = mutation({
|
|
301
|
-
args: {
|
|
337
|
+
args: {
|
|
338
|
+
userId: v.id("User"),
|
|
339
|
+
cascade: v.optional(v.boolean())
|
|
340
|
+
},
|
|
302
341
|
returns: v.null(),
|
|
303
|
-
handler: async (ctx, { userId }) => {
|
|
304
|
-
if (await ctx.db.get("User", userId)
|
|
342
|
+
handler: async (ctx, { userId, cascade }) => {
|
|
343
|
+
if (await ctx.db.get("User", userId) === null) return null;
|
|
344
|
+
if (cascade !== true) {
|
|
345
|
+
const [session, account, key, member, passkey, totp] = await Promise.all([
|
|
346
|
+
ctx.db.query("Session").withIndex("user_id", (q) => q.eq("userId", userId)).first(),
|
|
347
|
+
ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", userId)).first(),
|
|
348
|
+
ctx.db.query("ApiKey").withIndex("user_id", (q) => q.eq("userId", userId)).first(),
|
|
349
|
+
ctx.db.query("GroupMember").withIndex("user_id", (q) => q.eq("userId", userId)).first(),
|
|
350
|
+
ctx.db.query("Passkey").withIndex("user_id", (q) => q.eq("userId", userId)).first(),
|
|
351
|
+
ctx.db.query("TotpFactor").withIndex("user_id", (q) => q.eq("userId", userId)).first()
|
|
352
|
+
]);
|
|
353
|
+
if (session !== null || account !== null || key !== null || member !== null || passkey !== null || totp !== null) throw new ConvexError({
|
|
354
|
+
code: "INVALID_PARAMETERS",
|
|
355
|
+
message: "The provided parameters are invalid."
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (cascade === true) {
|
|
359
|
+
const [sessions, accounts, keys, members, passkeys, totps] = await Promise.all([
|
|
360
|
+
ctx.db.query("Session").withIndex("user_id", (q) => q.eq("userId", userId)).collect(),
|
|
361
|
+
ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", userId)).collect(),
|
|
362
|
+
ctx.db.query("ApiKey").withIndex("user_id", (q) => q.eq("userId", userId)).collect(),
|
|
363
|
+
ctx.db.query("GroupMember").withIndex("user_id", (q) => q.eq("userId", userId)).collect(),
|
|
364
|
+
ctx.db.query("Passkey").withIndex("user_id", (q) => q.eq("userId", userId)).collect(),
|
|
365
|
+
ctx.db.query("TotpFactor").withIndex("user_id", (q) => q.eq("userId", userId)).collect()
|
|
366
|
+
]);
|
|
367
|
+
const refreshTokens = sessions.length > 0 ? (await Promise.all(sessions.map((s) => ctx.db.query("RefreshToken").withIndex("session_id", (q) => q.eq("sessionId", s._id)).collect()))).flat() : [];
|
|
368
|
+
await Promise.all([
|
|
369
|
+
...sessions.map((s) => ctx.db.delete("Session", s._id)),
|
|
370
|
+
...refreshTokens.map((r) => ctx.db.delete("RefreshToken", r._id)),
|
|
371
|
+
...accounts.map((a) => ctx.db.delete("Account", a._id)),
|
|
372
|
+
...keys.map((k) => ctx.db.delete("ApiKey", k._id)),
|
|
373
|
+
...members.map((m) => ctx.db.delete("GroupMember", m._id)),
|
|
374
|
+
...passkeys.map((p) => ctx.db.delete("Passkey", p._id)),
|
|
375
|
+
...totps.map((t) => ctx.db.delete("TotpFactor", t._id))
|
|
376
|
+
]);
|
|
377
|
+
}
|
|
378
|
+
await ctx.db.delete("User", userId);
|
|
305
379
|
return null;
|
|
306
380
|
}
|
|
307
381
|
});
|
|
308
382
|
|
|
309
383
|
//#endregion
|
|
310
|
-
export { userDelete, userFindByVerifiedEmail, userFindByVerifiedPhone, userGetById, userInsert, userList, userPatch, userUpsert };
|
|
384
|
+
export { userDelete, userFindByVerifiedEmail, userFindByVerifiedPhone, userGetById, userGetMany, userInsert, userList, userPatch, userUpsert };
|
|
311
385
|
//# sourceMappingURL=users.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { vAuditActorType, vAuditStatus, vGroupAuditEventDoc } from "../../model.js";
|
|
2
2
|
import { mutation, query } from "../../functions.js";
|
|
3
|
-
import { v } from "convex/values";
|
|
3
|
+
import { ConvexError, v } from "convex/values";
|
|
4
4
|
|
|
5
5
|
//#region src/component/public/sso/audit.ts
|
|
6
6
|
/**
|
|
@@ -99,7 +99,10 @@ const groupAuditEventList = query({
|
|
|
99
99
|
const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);
|
|
100
100
|
if (args.connectionId !== void 0) return await ctx.db.query("GroupAuditEvent").withIndex("group_connection_id_occurred_at", (idx) => idx.eq("connectionId", args.connectionId)).order("desc").take(limit);
|
|
101
101
|
if (args.groupId !== void 0) return await ctx.db.query("GroupAuditEvent").withIndex("group_id_occurred_at", (idx) => idx.eq("groupId", args.groupId)).order("desc").take(limit);
|
|
102
|
-
|
|
102
|
+
throw new ConvexError({
|
|
103
|
+
code: "INVALID_PARAMETERS",
|
|
104
|
+
message: "groupAuditEventList requires either `connectionId` or `groupId` to scope the query. Passing neither would walk the entire audit log."
|
|
105
|
+
});
|
|
103
106
|
}
|
|
104
107
|
});
|
|
105
108
|
|
|
@@ -168,12 +168,12 @@ const groupConnectionList = query({
|
|
|
168
168
|
const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);
|
|
169
169
|
const order = args.order ?? "desc";
|
|
170
170
|
let q;
|
|
171
|
-
if (where.groupId !== void 0) q = ctx.db.query("GroupConnection").withIndex("
|
|
171
|
+
if (where.groupId !== void 0 && where.status !== void 0) q = ctx.db.query("GroupConnection").withIndex("group_id_status", (idx) => idx.eq("groupId", where.groupId).eq("status", where.status));
|
|
172
|
+
else if (where.groupId !== void 0 && where.slug !== void 0) q = ctx.db.query("GroupConnection").withIndex("group_id_slug", (idx) => idx.eq("groupId", where.groupId).eq("slug", where.slug));
|
|
173
|
+
else if (where.groupId !== void 0) q = ctx.db.query("GroupConnection").withIndex("group_id", (idx) => idx.eq("groupId", where.groupId));
|
|
172
174
|
else if (where.slug !== void 0) q = ctx.db.query("GroupConnection").withIndex("slug", (idx) => idx.eq("slug", where.slug));
|
|
173
175
|
else if (where.status !== void 0) q = ctx.db.query("GroupConnection").withIndex("status", (idx) => idx.eq("status", where.status));
|
|
174
176
|
else q = ctx.db.query("GroupConnection");
|
|
175
|
-
if (where.groupId !== void 0 && where.slug !== void 0) q = q.filter((f) => f.eq(f.field("slug"), where.slug));
|
|
176
|
-
if (where.status !== void 0 && where.groupId === void 0) {} else if (where.status !== void 0) q = q.filter((f) => f.eq(f.field("status"), where.status));
|
|
177
177
|
q = q.order(order);
|
|
178
178
|
const all = await q.collect();
|
|
179
179
|
let startIdx = 0;
|
|
@@ -83,10 +83,14 @@ const groupConnectionDomainAdd = mutation({
|
|
|
83
83
|
* ```
|
|
84
84
|
*/
|
|
85
85
|
const groupConnectionDomainList = query({
|
|
86
|
-
args: {
|
|
86
|
+
args: {
|
|
87
|
+
connectionId: v.id("GroupConnection"),
|
|
88
|
+
limit: v.optional(v.number())
|
|
89
|
+
},
|
|
87
90
|
returns: v.array(vGroupConnectionDomainDoc),
|
|
88
|
-
handler: async (ctx, { connectionId }) => {
|
|
89
|
-
|
|
91
|
+
handler: async (ctx, { connectionId, limit }) => {
|
|
92
|
+
const bounded = Math.min(Math.max(limit ?? 100, 1), 500);
|
|
93
|
+
return await ctx.db.query("GroupConnectionDomain").withIndex("connection_id", (idx) => idx.eq("connectionId", connectionId)).take(bounded);
|
|
90
94
|
}
|
|
91
95
|
});
|
|
92
96
|
/**
|
|
@@ -201,6 +201,41 @@ const groupConnectionScimIdentityGetByGroupConnectionAndUser = query({
|
|
|
201
201
|
}
|
|
202
202
|
});
|
|
203
203
|
/**
|
|
204
|
+
* Batched variant of
|
|
205
|
+
* {@link groupConnectionScimIdentityGetByGroupConnectionAndUser}. Resolves
|
|
206
|
+
* SCIM identities for many users under the same connection in a single
|
|
207
|
+
* component round-trip.
|
|
208
|
+
*
|
|
209
|
+
* Used by large SCIM syncs that previously walked the user list one at a
|
|
210
|
+
* time — a 1000-user import was 1000 lookups. With this helper it's one.
|
|
211
|
+
*
|
|
212
|
+
* @param args.connectionId - The ID of the connection to scope to.
|
|
213
|
+
* @param args.userIds - One or more user ids to look up. Duplicates are
|
|
214
|
+
* tolerated.
|
|
215
|
+
* @returns Array of `{ userId, identity }` pairs in the input order; when
|
|
216
|
+
* a user has no SCIM identity under this connection, `identity` is `null`.
|
|
217
|
+
*/
|
|
218
|
+
const groupConnectionScimIdentityGetByGroupConnectionAndUsers = query({
|
|
219
|
+
args: {
|
|
220
|
+
connectionId: v.id("GroupConnection"),
|
|
221
|
+
userIds: v.array(v.id("User"))
|
|
222
|
+
},
|
|
223
|
+
returns: v.array(v.object({
|
|
224
|
+
userId: v.id("User"),
|
|
225
|
+
identity: v.union(vGroupConnectionScimIdentityDoc, v.null())
|
|
226
|
+
})),
|
|
227
|
+
handler: async (ctx, { connectionId, userIds }) => {
|
|
228
|
+
if (userIds.length === 0) return [];
|
|
229
|
+
const unique = Array.from(new Set(userIds));
|
|
230
|
+
const docs = await Promise.all(unique.map((userId) => ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_user_id", (idx) => idx.eq("connectionId", connectionId).eq("userId", userId)).first()));
|
|
231
|
+
const byUserId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
|
|
232
|
+
return userIds.map((userId) => ({
|
|
233
|
+
userId,
|
|
234
|
+
identity: byUserId.get(userId) ?? null
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
/**
|
|
204
239
|
* Retrieve the SCIM identity that is mapped to a specific group.
|
|
205
240
|
*
|
|
206
241
|
* Looks up a SCIM identity by its `mappedGroupId` field. This is used when
|
|
@@ -340,5 +375,5 @@ const groupConnectionScimIdentityDelete = mutation({
|
|
|
340
375
|
});
|
|
341
376
|
|
|
342
377
|
//#endregion
|
|
343
|
-
export { groupConnectionScimConfigGetByGroupConnection, groupConnectionScimConfigGetByTokenHash, groupConnectionScimConfigUpsert, groupConnectionScimIdentityDelete, groupConnectionScimIdentityGet, groupConnectionScimIdentityGetByGroupConnectionAndUser, groupConnectionScimIdentityGetByMappedGroup, groupConnectionScimIdentityGetByUser, groupConnectionScimIdentityListByGroupConnection, groupConnectionScimIdentityUpsert };
|
|
378
|
+
export { groupConnectionScimConfigGetByGroupConnection, groupConnectionScimConfigGetByTokenHash, groupConnectionScimConfigUpsert, groupConnectionScimIdentityDelete, groupConnectionScimIdentityGet, groupConnectionScimIdentityGetByGroupConnectionAndUser, groupConnectionScimIdentityGetByGroupConnectionAndUsers, groupConnectionScimIdentityGetByMappedGroup, groupConnectionScimIdentityGetByUser, groupConnectionScimIdentityListByGroupConnection, groupConnectionScimIdentityUpsert };
|
|
344
379
|
//# sourceMappingURL=scim.js.map
|
package/dist/component/public.js
CHANGED
|
@@ -3,20 +3,20 @@ import { deviceAuthorize, deviceDelete, deviceGetByCodeHash, deviceGetByUserCode
|
|
|
3
3
|
import { groupAuditEventCreate, groupAuditEventList } from "./public/sso/audit.js";
|
|
4
4
|
import { groupConnectionCreate, groupConnectionDelete, groupConnectionGet, groupConnectionGetByDomain, groupConnectionList, groupConnectionUpdate } from "./public/sso/core.js";
|
|
5
5
|
import { groupConnectionDomainAdd, groupConnectionDomainDelete, groupConnectionDomainList, groupConnectionDomainVerificationDelete, groupConnectionDomainVerificationGet, groupConnectionDomainVerificationUpsert, groupConnectionDomainVerify } from "./public/sso/domains.js";
|
|
6
|
-
import { groupConnectionScimConfigGetByGroupConnection, groupConnectionScimConfigGetByTokenHash, groupConnectionScimConfigUpsert, groupConnectionScimIdentityDelete, groupConnectionScimIdentityGet, groupConnectionScimIdentityGetByGroupConnectionAndUser, groupConnectionScimIdentityGetByMappedGroup, groupConnectionScimIdentityGetByUser, groupConnectionScimIdentityListByGroupConnection, groupConnectionScimIdentityUpsert } from "./public/sso/scim.js";
|
|
6
|
+
import { groupConnectionScimConfigGetByGroupConnection, groupConnectionScimConfigGetByTokenHash, groupConnectionScimConfigUpsert, groupConnectionScimIdentityDelete, groupConnectionScimIdentityGet, groupConnectionScimIdentityGetByGroupConnectionAndUser, groupConnectionScimIdentityGetByGroupConnectionAndUsers, groupConnectionScimIdentityGetByMappedGroup, groupConnectionScimIdentityGetByUser, groupConnectionScimIdentityListByGroupConnection, groupConnectionScimIdentityUpsert } from "./public/sso/scim.js";
|
|
7
7
|
import { groupConnectionSecretDelete, groupConnectionSecretGet, groupConnectionSecretUpsert } from "./public/sso/secrets.js";
|
|
8
8
|
import { groupWebhookDeliveryEnqueue, groupWebhookDeliveryList, groupWebhookDeliveryListReady, groupWebhookDeliveryPatch, groupWebhookEndpointCreate, groupWebhookEndpointGet, groupWebhookEndpointList, groupWebhookEndpointUpdate } from "./public/sso/webhooks.js";
|
|
9
|
-
import { groupCreate, groupDelete, groupGet, groupList, groupUpdate } from "./public/groups/core.js";
|
|
9
|
+
import { groupAncestors, groupCreate, groupDelete, groupGet, groupGetMany, groupList, groupUpdate } from "./public/groups/core.js";
|
|
10
10
|
import { inviteAccept, inviteAcceptByToken, inviteCreate, inviteGet, inviteGetByTokenHash, inviteList, inviteRevoke } from "./public/groups/invites.js";
|
|
11
|
-
import { memberAdd, memberGet, memberGetByGroupAndUser, memberList, memberRemove, memberResolve, memberUpdate } from "./public/groups/members.js";
|
|
11
|
+
import { memberAdd, memberGet, memberGetByGroupAndUser, memberGetByGroupAndUserMany, memberList, memberRemove, memberResolve, memberUpdate } from "./public/groups/members.js";
|
|
12
12
|
import { keyDelete, keyGetByHashedKey, keyGetById, keyInsert, keyList, keyPatch } from "./public/security/keys.js";
|
|
13
13
|
import { passkeyDelete, passkeyGetByCredentialId, passkeyInsert, passkeyListByUserId, passkeyUpdateCounter, passkeyUpdateMeta } from "./public/factors/passkeys.js";
|
|
14
14
|
import { rateLimitCreate, rateLimitDelete, rateLimitGet, rateLimitPatch } from "./public/security/limits.js";
|
|
15
15
|
import { refreshTokenCreate, refreshTokenDeleteAll, refreshTokenExchange, refreshTokenGetActive, refreshTokenGetById, refreshTokenGetChildren, refreshTokenListBySession, refreshTokenPatch } from "./public/identity/tokens.js";
|
|
16
16
|
import { sessionCreate, sessionDelete, sessionGetById, sessionIssue, sessionList, sessionListByUser } from "./public/identity/sessions.js";
|
|
17
17
|
import { totpDelete, totpGetById, totpGetVerifiedByUserId, totpInsert, totpListByUserId, totpMarkVerified, totpUpdateLastUsed } from "./public/factors/totp.js";
|
|
18
|
-
import { userDelete, userFindByVerifiedEmail, userFindByVerifiedPhone, userGetById, userInsert, userList, userPatch, userUpsert } from "./public/identity/users.js";
|
|
18
|
+
import { userDelete, userFindByVerifiedEmail, userFindByVerifiedPhone, userGetById, userGetMany, userInsert, userList, userPatch, userUpsert } from "./public/identity/users.js";
|
|
19
19
|
import { verificationCodeCreate, verificationCodeDelete, verificationCodeGetByAccountId, verificationCodeGetByCode } from "./public/identity/codes.js";
|
|
20
20
|
import { verifierCreate, verifierDelete, verifierGetById, verifierGetBySignature, verifierPatch } from "./public/identity/verifiers.js";
|
|
21
21
|
|
|
22
|
-
export { accountDelete, accountGet, accountGetById, accountInsert, accountListByUser, accountPatch, deviceAuthorize, deviceDelete, deviceGetByCodeHash, deviceGetByUserCode, deviceInsert, deviceUpdateLastPolled, groupAuditEventCreate, groupAuditEventList, groupConnectionCreate, groupConnectionDelete, groupConnectionDomainAdd, groupConnectionDomainDelete, groupConnectionDomainList, groupConnectionDomainVerificationDelete, groupConnectionDomainVerificationGet, groupConnectionDomainVerificationUpsert, groupConnectionDomainVerify, groupConnectionGet, groupConnectionGetByDomain, groupConnectionList, groupConnectionScimConfigGetByGroupConnection, groupConnectionScimConfigGetByTokenHash, groupConnectionScimConfigUpsert, groupConnectionScimIdentityDelete, groupConnectionScimIdentityGet, groupConnectionScimIdentityGetByGroupConnectionAndUser, groupConnectionScimIdentityGetByMappedGroup, groupConnectionScimIdentityGetByUser, groupConnectionScimIdentityListByGroupConnection, groupConnectionScimIdentityUpsert, groupConnectionSecretDelete, groupConnectionSecretGet, groupConnectionSecretUpsert, groupConnectionUpdate, groupCreate, groupDelete, groupGet, groupList, groupUpdate, groupWebhookDeliveryEnqueue, groupWebhookDeliveryList, groupWebhookDeliveryListReady, groupWebhookDeliveryPatch, groupWebhookEndpointCreate, groupWebhookEndpointGet, groupWebhookEndpointList, groupWebhookEndpointUpdate, inviteAccept, inviteAcceptByToken, inviteCreate, inviteGet, inviteGetByTokenHash, inviteList, inviteRevoke, keyDelete, keyGetByHashedKey, keyGetById, keyInsert, keyList, keyPatch, memberAdd, memberGet, memberGetByGroupAndUser, memberList, memberRemove, memberResolve, memberUpdate, passkeyDelete, passkeyGetByCredentialId, passkeyInsert, passkeyListByUserId, passkeyUpdateCounter, passkeyUpdateMeta, rateLimitCreate, rateLimitDelete, rateLimitGet, rateLimitPatch, refreshTokenCreate, refreshTokenDeleteAll, refreshTokenExchange, refreshTokenGetActive, refreshTokenGetById, refreshTokenGetChildren, refreshTokenListBySession, refreshTokenPatch, sessionCreate, sessionDelete, sessionGetById, sessionIssue, sessionList, sessionListByUser, totpDelete, totpGetById, totpGetVerifiedByUserId, totpInsert, totpListByUserId, totpMarkVerified, totpUpdateLastUsed, userDelete, userFindByVerifiedEmail, userFindByVerifiedPhone, userGetById, userInsert, userList, userPatch, userUpsert, verificationCodeCreate, verificationCodeDelete, verificationCodeGetByAccountId, verificationCodeGetByCode, verifierCreate, verifierDelete, verifierGetById, verifierGetBySignature, verifierPatch };
|
|
22
|
+
export { accountDelete, accountGet, accountGetById, accountInsert, accountListByUser, accountPatch, deviceAuthorize, deviceDelete, deviceGetByCodeHash, deviceGetByUserCode, deviceInsert, deviceUpdateLastPolled, groupAncestors, groupAuditEventCreate, groupAuditEventList, groupConnectionCreate, groupConnectionDelete, groupConnectionDomainAdd, groupConnectionDomainDelete, groupConnectionDomainList, groupConnectionDomainVerificationDelete, groupConnectionDomainVerificationGet, groupConnectionDomainVerificationUpsert, groupConnectionDomainVerify, groupConnectionGet, groupConnectionGetByDomain, groupConnectionList, groupConnectionScimConfigGetByGroupConnection, groupConnectionScimConfigGetByTokenHash, groupConnectionScimConfigUpsert, groupConnectionScimIdentityDelete, groupConnectionScimIdentityGet, groupConnectionScimIdentityGetByGroupConnectionAndUser, groupConnectionScimIdentityGetByGroupConnectionAndUsers, groupConnectionScimIdentityGetByMappedGroup, groupConnectionScimIdentityGetByUser, groupConnectionScimIdentityListByGroupConnection, groupConnectionScimIdentityUpsert, groupConnectionSecretDelete, groupConnectionSecretGet, groupConnectionSecretUpsert, groupConnectionUpdate, groupCreate, groupDelete, groupGet, groupGetMany, groupList, groupUpdate, groupWebhookDeliveryEnqueue, groupWebhookDeliveryList, groupWebhookDeliveryListReady, groupWebhookDeliveryPatch, groupWebhookEndpointCreate, groupWebhookEndpointGet, groupWebhookEndpointList, groupWebhookEndpointUpdate, inviteAccept, inviteAcceptByToken, inviteCreate, inviteGet, inviteGetByTokenHash, inviteList, inviteRevoke, keyDelete, keyGetByHashedKey, keyGetById, keyInsert, keyList, keyPatch, memberAdd, memberGet, memberGetByGroupAndUser, memberGetByGroupAndUserMany, memberList, memberRemove, memberResolve, memberUpdate, passkeyDelete, passkeyGetByCredentialId, passkeyInsert, passkeyListByUserId, passkeyUpdateCounter, passkeyUpdateMeta, rateLimitCreate, rateLimitDelete, rateLimitGet, rateLimitPatch, refreshTokenCreate, refreshTokenDeleteAll, refreshTokenExchange, refreshTokenGetActive, refreshTokenGetById, refreshTokenGetChildren, refreshTokenListBySession, refreshTokenPatch, sessionCreate, sessionDelete, sessionGetById, sessionIssue, sessionList, sessionListByUser, totpDelete, totpGetById, totpGetVerifiedByUserId, totpInsert, totpListByUserId, totpMarkVerified, totpUpdateLastUsed, userDelete, userFindByVerifiedEmail, userFindByVerifiedPhone, userGetById, userGetMany, userInsert, userList, userPatch, userUpsert, verificationCodeCreate, verificationCodeDelete, verificationCodeGetByAccountId, verificationCodeGetByCode, verifierCreate, verifierDelete, verifierGetById, verifierGetBySignature, verifierPatch };
|