@robelest/convex-auth 0.0.4-preview.32 → 0.0.4-preview.34
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/component/_generated/component.d.ts +1611 -2039
- package/dist/component/account.js +3 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/factor/device.js +3 -0
- package/dist/component/factor/passkey.js +3 -0
- package/dist/component/factor/totp.js +3 -0
- package/dist/component/group/invite.js +3 -0
- package/dist/component/group/member.js +3 -0
- package/dist/component/group.js +3 -0
- package/dist/component/model.d.ts +342 -30
- package/dist/component/model.js +22 -4
- package/dist/component/modules.js +24 -21
- package/dist/component/public/factors/devices.js +37 -106
- package/dist/component/public/factors/passkeys.js +29 -149
- package/dist/component/public/factors/totp.js +32 -159
- package/dist/component/public/groups/core.js +19 -82
- package/dist/component/public/groups/invites.js +15 -104
- package/dist/component/public/groups/members.js +26 -149
- package/dist/component/public/identity/accounts.js +12 -94
- package/dist/component/public/identity/codes.js +13 -73
- package/dist/component/public/identity/sessions.js +5 -107
- package/dist/component/public/identity/tokens.js +13 -103
- package/dist/component/public/identity/users.js +188 -185
- package/dist/component/public/identity/verifiers.js +17 -80
- package/dist/component/public/security/keys.js +13 -120
- package/dist/component/public/security/limits.js +0 -43
- package/dist/component/public/sso/audit.js +0 -28
- package/dist/component/public/sso/core.js +31 -104
- package/dist/component/public/sso/domains.js +0 -71
- package/dist/component/public/sso/scim.js +63 -239
- package/dist/component/public/sso/secrets.js +0 -30
- package/dist/component/public/sso/webhooks.js +23 -128
- package/dist/component/rateLimit.js +3 -0
- package/dist/component/schema.d.ts +378 -342
- package/dist/component/schema.js +11 -1
- package/dist/component/session.js +3 -0
- package/dist/component/sso/audit.js +3 -0
- package/dist/component/sso/connection/domain/verification.js +3 -0
- package/dist/component/sso/connection/domain.js +3 -0
- package/dist/component/sso/connection/scim/config.js +3 -0
- package/dist/component/sso/connection/scim/identity.js +3 -0
- package/dist/component/sso/connection/secret.js +3 -0
- package/dist/component/sso/connection.js +3 -0
- package/dist/component/sso/webhook/delivery.js +3 -0
- package/dist/component/sso/webhook/endpoint.js +3 -0
- package/dist/component/token/pkce.js +3 -0
- package/dist/component/token/refresh.js +3 -0
- package/dist/component/token/verification.js +3 -0
- package/dist/component/user/email.js +3 -0
- package/dist/component/user/key.js +3 -0
- package/dist/component/user.js +62 -0
- package/dist/core/index.d.ts +131 -28
- package/dist/core/index.js +2 -0
- package/dist/model.js +391 -0
- package/dist/providers/credentials.d.ts +1 -1
- package/dist/providers/github.js +6 -0
- package/dist/providers/password.js +1 -2
- package/dist/server/auth.d.ts +73 -7
- package/dist/server/auth.js +4 -1
- package/dist/server/context.js +30 -3
- package/dist/server/contract.js +42 -42
- package/dist/server/core.js +224 -86
- package/dist/server/db.js +45 -37
- package/dist/server/facade.d.ts +39 -0
- package/dist/server/facade.js +16 -0
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.js +3 -1
- package/dist/server/mounts.d.ts +101 -101
- package/dist/server/mutations/credentials/signin.js +3 -7
- package/dist/server/mutations/oauth.js +9 -6
- package/dist/server/runtime.d.ts +147 -46
- package/dist/server/runtime.js +10 -8
- package/dist/server/services/group.js +9 -9
- package/dist/server/sso/domain.d.ts +1 -1
- package/dist/server/sso/domain.js +40 -40
- package/dist/server/sso/http.js +18 -18
- package/dist/server/sso/oidc.js +1 -1
- package/dist/server/sso/policies.js +3 -3
- package/dist/server/sso/policy.js +12 -4
- package/dist/server/sso/provision.js +9 -9
- package/dist/server/sso/validators.js +2 -2
- package/dist/server/sso/webhook.js +8 -8
- package/dist/server/types.d.ts +185 -124
- package/dist/server/types.js +29 -24
- package/dist/server/users.js +49 -2
- package/dist/server/validators.d.ts +745 -0
- package/dist/server/validators.js +60 -0
- package/package.json +1 -1
- package/dist/component/public.js +0 -22
package/dist/server/core.js
CHANGED
|
@@ -44,14 +44,14 @@ function createCoreDomains(deps) {
|
|
|
44
44
|
return Array.from(grants).sort();
|
|
45
45
|
};
|
|
46
46
|
async function userGet(ctx, input) {
|
|
47
|
-
if (typeof input === "string") return await cached(ctx, `user:${input}`, () => ctx.runQuery(config.component.
|
|
47
|
+
if (typeof input === "string") return await cached(ctx, `user:${input}`, () => ctx.runQuery(config.component.user.get, { id: input }));
|
|
48
48
|
const userIds = input;
|
|
49
49
|
if (userIds.length === 0) return [];
|
|
50
50
|
const unique = Array.from(new Set(userIds));
|
|
51
51
|
const toFetch = [];
|
|
52
52
|
for (const id of unique) if (!ctxCacheHas(ctx, `user:${id}`)) toFetch.push(id);
|
|
53
53
|
if (toFetch.length > 0) {
|
|
54
|
-
const sharedFetch = ctx.runQuery(config.component.
|
|
54
|
+
const sharedFetch = ctx.runQuery(config.component.user.get, { ids: toFetch });
|
|
55
55
|
for (let i = 0; i < toFetch.length; i += 1) {
|
|
56
56
|
const id = toFetch[i];
|
|
57
57
|
const indexInBatch = i;
|
|
@@ -60,56 +60,86 @@ function createCoreDomains(deps) {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
return await Promise.all(userIds.map((id) => cached(ctx, `user:${id}`, () => ctx.runQuery(config.component.
|
|
63
|
+
return await Promise.all(userIds.map((id) => cached(ctx, `user:${id}`, () => ctx.runQuery(config.component.user.get, { id }))));
|
|
64
64
|
}
|
|
65
65
|
const user = {
|
|
66
66
|
get: userGet,
|
|
67
67
|
list: async (ctx, opts = {}) => {
|
|
68
|
-
return await ctx.runQuery(config.component.
|
|
68
|
+
return await ctx.runQuery(config.component.user.list, opts);
|
|
69
69
|
},
|
|
70
70
|
viewer: async (ctx) => {
|
|
71
71
|
const userId = await getSessionUserId(ctx);
|
|
72
72
|
if (userId === null) return null;
|
|
73
73
|
return await user.get(ctx, userId);
|
|
74
74
|
},
|
|
75
|
+
email: (() => {
|
|
76
|
+
async function primary(ctx, emailOrOpts, maybeOpts) {
|
|
77
|
+
const setting = typeof emailOrOpts === "string";
|
|
78
|
+
const userId = (setting ? maybeOpts : emailOrOpts)?.userId ?? await getSessionUserId(ctx);
|
|
79
|
+
if (userId === null || userId === void 0) {
|
|
80
|
+
if (setting) throw new ConvexError({
|
|
81
|
+
code: "NOT_SIGNED_IN",
|
|
82
|
+
message: "Authentication required."
|
|
83
|
+
});
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (setting) {
|
|
87
|
+
await ctx.runMutation(config.component.user.email.setPrimary, {
|
|
88
|
+
userId,
|
|
89
|
+
email: emailOrOpts.toLowerCase()
|
|
90
|
+
});
|
|
91
|
+
return { email: emailOrOpts.toLowerCase() };
|
|
92
|
+
}
|
|
93
|
+
return (await ctx.runQuery(config.component.user.email.list, { userId })).find((r) => r.isPrimary) ?? null;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
list: async (ctx, opts) => {
|
|
97
|
+
const userId = opts?.userId ?? await getSessionUserId(ctx);
|
|
98
|
+
if (userId === null || userId === void 0) return [];
|
|
99
|
+
return await ctx.runQuery(config.component.user.email.list, { userId });
|
|
100
|
+
},
|
|
101
|
+
add: async (ctx, email, opts) => {
|
|
102
|
+
const userId = opts?.userId ?? await getSessionUserId(ctx);
|
|
103
|
+
if (userId === null || userId === void 0) throw new ConvexError({
|
|
104
|
+
code: "NOT_SIGNED_IN",
|
|
105
|
+
message: "Authentication required."
|
|
106
|
+
});
|
|
107
|
+
const addr = email.toLowerCase();
|
|
108
|
+
await ctx.runMutation(config.component.user.email.upsert, {
|
|
109
|
+
userId,
|
|
110
|
+
email: addr,
|
|
111
|
+
verified: false,
|
|
112
|
+
isPrimary: false,
|
|
113
|
+
source: "password"
|
|
114
|
+
});
|
|
115
|
+
return { email: addr };
|
|
116
|
+
},
|
|
117
|
+
remove: async (ctx, email, opts) => {
|
|
118
|
+
const userId = opts?.userId ?? await getSessionUserId(ctx);
|
|
119
|
+
if (userId === null || userId === void 0) throw new ConvexError({
|
|
120
|
+
code: "NOT_SIGNED_IN",
|
|
121
|
+
message: "Authentication required."
|
|
122
|
+
});
|
|
123
|
+
const addr = email.toLowerCase();
|
|
124
|
+
await ctx.runMutation(config.component.user.email.delete, {
|
|
125
|
+
userId,
|
|
126
|
+
email: addr
|
|
127
|
+
});
|
|
128
|
+
return { email: addr };
|
|
129
|
+
},
|
|
130
|
+
primary
|
|
131
|
+
};
|
|
132
|
+
})(),
|
|
75
133
|
update: async (ctx, userId, data) => {
|
|
76
|
-
await ctx.runMutation(config.component.
|
|
134
|
+
await ctx.runMutation(config.component.user.update, {
|
|
77
135
|
userId,
|
|
78
136
|
data
|
|
79
137
|
});
|
|
80
138
|
invalidateCtxCache(ctx, `user:${userId}`);
|
|
81
139
|
return { userId };
|
|
82
140
|
},
|
|
83
|
-
setActiveGroup: async (ctx, opts) => {
|
|
84
|
-
const doc = await user.get(ctx, opts.userId);
|
|
85
|
-
const existingExtend = doc !== null && doc.extend !== null && typeof doc.extend === "object" && !Array.isArray(doc.extend) ? { ...doc.extend } : {};
|
|
86
|
-
if (opts.groupId === null) {
|
|
87
|
-
const { lastActiveGroup: _omit, ...rest } = existingExtend;
|
|
88
|
-
await user.update(ctx, opts.userId, { extend: rest });
|
|
89
|
-
return {
|
|
90
|
-
userId: opts.userId,
|
|
91
|
-
groupId: null
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
await user.update(ctx, opts.userId, { extend: {
|
|
95
|
-
...existingExtend,
|
|
96
|
-
lastActiveGroup: opts.groupId
|
|
97
|
-
} });
|
|
98
|
-
return {
|
|
99
|
-
userId: opts.userId,
|
|
100
|
-
groupId: opts.groupId
|
|
101
|
-
};
|
|
102
|
-
},
|
|
103
|
-
getActiveGroup: async (ctx, opts) => {
|
|
104
|
-
const doc = await user.get(ctx, opts.userId);
|
|
105
|
-
if (doc !== null && doc.extend !== null && typeof doc.extend === "object" && !Array.isArray(doc.extend)) {
|
|
106
|
-
const val = doc.extend.lastActiveGroup;
|
|
107
|
-
if (typeof val === "string") return val;
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
},
|
|
111
141
|
delete: async (ctx, userId, opts) => {
|
|
112
|
-
await ctx.runMutation(config.component.
|
|
142
|
+
await ctx.runMutation(config.component.user.delete, {
|
|
113
143
|
userId,
|
|
114
144
|
cascade: opts?.cascade !== false
|
|
115
145
|
});
|
|
@@ -126,10 +156,10 @@ function createCoreDomains(deps) {
|
|
|
126
156
|
};
|
|
127
157
|
},
|
|
128
158
|
get: async (ctx, sessionId) => {
|
|
129
|
-
return await cached(ctx, `session:${sessionId}`, () => ctx.runQuery(config.component.
|
|
159
|
+
return await cached(ctx, `session:${sessionId}`, () => ctx.runQuery(config.component.session.get, { sessionId }));
|
|
130
160
|
},
|
|
131
161
|
list: async (ctx, opts) => {
|
|
132
|
-
return await ctx.runQuery(config.component.
|
|
162
|
+
return await ctx.runQuery(config.component.session.list, { userId: opts.userId });
|
|
133
163
|
}
|
|
134
164
|
};
|
|
135
165
|
const account = {
|
|
@@ -146,31 +176,31 @@ function createCoreDomains(deps) {
|
|
|
146
176
|
return { accountId: args.account.id };
|
|
147
177
|
},
|
|
148
178
|
delete: async (ctx, accountId) => {
|
|
149
|
-
await ctx.runMutation(config.component.
|
|
179
|
+
await ctx.runMutation(config.component.account.delete, {
|
|
150
180
|
accountId,
|
|
151
181
|
requireOtherAccount: true
|
|
152
182
|
});
|
|
153
183
|
return { accountId };
|
|
154
184
|
},
|
|
155
185
|
listPasskeys: async (ctx, opts) => {
|
|
156
|
-
return await ctx.runQuery(config.component.
|
|
186
|
+
return await ctx.runQuery(config.component.factor.passkey.list, opts);
|
|
157
187
|
},
|
|
158
188
|
renamePasskey: async (ctx, passkeyId, name) => {
|
|
159
|
-
await ctx.runMutation(config.component.
|
|
189
|
+
await ctx.runMutation(config.component.factor.passkey.update, {
|
|
160
190
|
passkeyId,
|
|
161
191
|
data: { name }
|
|
162
192
|
});
|
|
163
193
|
return { passkeyId };
|
|
164
194
|
},
|
|
165
195
|
deletePasskey: async (ctx, passkeyId) => {
|
|
166
|
-
await ctx.runMutation(config.component.
|
|
196
|
+
await ctx.runMutation(config.component.factor.passkey.delete, { passkeyId });
|
|
167
197
|
return { passkeyId };
|
|
168
198
|
},
|
|
169
199
|
listTotps: async (ctx, opts) => {
|
|
170
|
-
return await ctx.runQuery(config.component.
|
|
200
|
+
return await ctx.runQuery(config.component.factor.totp.list, opts);
|
|
171
201
|
},
|
|
172
202
|
deleteTotp: async (ctx, totpId) => {
|
|
173
|
-
await ctx.runMutation(config.component.
|
|
203
|
+
await ctx.runMutation(config.component.factor.totp.delete, { totpId });
|
|
174
204
|
return { totpId };
|
|
175
205
|
}
|
|
176
206
|
};
|
|
@@ -178,14 +208,14 @@ function createCoreDomains(deps) {
|
|
|
178
208
|
return deps.signInForProvider(ctx, providerConfig, args);
|
|
179
209
|
} : void 0 };
|
|
180
210
|
async function groupGet(ctx, input) {
|
|
181
|
-
if (typeof input === "string") return await cached(ctx, `group:${input}`, () => ctx.runQuery(config.component.
|
|
211
|
+
if (typeof input === "string") return await cached(ctx, `group:${input}`, () => ctx.runQuery(config.component.group.get, { id: input }));
|
|
182
212
|
const groupIds = input;
|
|
183
213
|
if (groupIds.length === 0) return [];
|
|
184
214
|
const unique = Array.from(new Set(groupIds));
|
|
185
215
|
const toFetch = [];
|
|
186
216
|
for (const id of unique) if (!ctxCacheHas(ctx, `group:${id}`)) toFetch.push(id);
|
|
187
217
|
if (toFetch.length > 0) {
|
|
188
|
-
const sharedFetch = ctx.runQuery(config.component.
|
|
218
|
+
const sharedFetch = ctx.runQuery(config.component.group.get, { ids: toFetch });
|
|
189
219
|
for (let i = 0; i < toFetch.length; i += 1) {
|
|
190
220
|
const id = toFetch[i];
|
|
191
221
|
const indexInBatch = i;
|
|
@@ -194,15 +224,50 @@ function createCoreDomains(deps) {
|
|
|
194
224
|
});
|
|
195
225
|
}
|
|
196
226
|
}
|
|
197
|
-
return await Promise.all(groupIds.map((id) => cached(ctx, `group:${id}`, () => ctx.runQuery(config.component.
|
|
227
|
+
return await Promise.all(groupIds.map((id) => cached(ctx, `group:${id}`, () => ctx.runQuery(config.component.group.get, { id }))));
|
|
228
|
+
}
|
|
229
|
+
async function groupGetEx(ctx, input, opts) {
|
|
230
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input) && "slug" in input) {
|
|
231
|
+
const { items } = await group.list(ctx, {
|
|
232
|
+
where: { slug: input.slug },
|
|
233
|
+
limit: 1
|
|
234
|
+
});
|
|
235
|
+
return items[0] ?? null;
|
|
236
|
+
}
|
|
237
|
+
if (opts?.tree === true && typeof input === "string") {
|
|
238
|
+
const current = await groupGet(ctx, input);
|
|
239
|
+
if (current === null) return null;
|
|
240
|
+
const parentId = typeof current.parentGroupId === "string" ? current.parentGroupId : null;
|
|
241
|
+
const [parent, childrenPage] = await Promise.all([parentId !== null ? groupGet(ctx, parentId) : Promise.resolve(null), group.list(ctx, {
|
|
242
|
+
where: { parentGroupId: input },
|
|
243
|
+
limit: 100
|
|
244
|
+
})]);
|
|
245
|
+
const ancestors = [];
|
|
246
|
+
let walk = parentId;
|
|
247
|
+
const seen = new Set([input]);
|
|
248
|
+
while (walk !== null && !seen.has(walk)) {
|
|
249
|
+
seen.add(walk);
|
|
250
|
+
const ancestor = await groupGet(ctx, walk);
|
|
251
|
+
if (ancestor === null) break;
|
|
252
|
+
ancestors.push(ancestor);
|
|
253
|
+
walk = typeof ancestor.parentGroupId === "string" ? ancestor.parentGroupId : null;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
current,
|
|
257
|
+
parent,
|
|
258
|
+
children: childrenPage.items,
|
|
259
|
+
ancestors
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return groupGet(ctx, input);
|
|
198
263
|
}
|
|
199
264
|
const group = {
|
|
200
265
|
create: async (ctx, data) => {
|
|
201
|
-
return { groupId: await ctx.runMutation(config.component.
|
|
266
|
+
return { groupId: await ctx.runMutation(config.component.group.create, data) };
|
|
202
267
|
},
|
|
203
|
-
get:
|
|
268
|
+
get: groupGetEx,
|
|
204
269
|
list: async (ctx, opts) => {
|
|
205
|
-
return await ctx.runQuery(config.component.
|
|
270
|
+
return await ctx.runQuery(config.component.group.list, {
|
|
206
271
|
where: opts?.where,
|
|
207
272
|
limit: opts?.limit,
|
|
208
273
|
cursor: opts?.cursor,
|
|
@@ -211,7 +276,7 @@ function createCoreDomains(deps) {
|
|
|
211
276
|
});
|
|
212
277
|
},
|
|
213
278
|
update: async (ctx, groupId, data) => {
|
|
214
|
-
await ctx.runMutation(config.component.
|
|
279
|
+
await ctx.runMutation(config.component.group.update, {
|
|
215
280
|
groupId,
|
|
216
281
|
data
|
|
217
282
|
});
|
|
@@ -219,15 +284,14 @@ function createCoreDomains(deps) {
|
|
|
219
284
|
return { groupId };
|
|
220
285
|
},
|
|
221
286
|
delete: async (ctx, groupId) => {
|
|
222
|
-
await ctx.runMutation(config.component.
|
|
287
|
+
await ctx.runMutation(config.component.group.delete, { groupId });
|
|
223
288
|
invalidateCtxCache(ctx, `group:${groupId}`);
|
|
224
289
|
invalidateCtxCache(ctx, "member");
|
|
225
290
|
invalidateCtxCache(ctx, "member-inspect");
|
|
226
291
|
return { groupId };
|
|
227
292
|
},
|
|
228
293
|
ancestors: async (ctx, opts) => {
|
|
229
|
-
const
|
|
230
|
-
const result = await ctx.runQuery(ancestorsRef, {
|
|
294
|
+
const result = await ctx.runQuery(config.component.group.ancestors, {
|
|
231
295
|
groupId: opts.groupId,
|
|
232
296
|
maxDepth: opts.maxDepth,
|
|
233
297
|
includeSelf: opts.includeSelf
|
|
@@ -247,7 +311,7 @@ function createCoreDomains(deps) {
|
|
|
247
311
|
const toFetch = [];
|
|
248
312
|
for (const groupId of unique) if (!ctxCacheHas(ctx, `member-inspect:${userId}:${groupId}:n`)) toFetch.push(groupId);
|
|
249
313
|
if (toFetch.length > 0) {
|
|
250
|
-
const sharedFetch = ctx.runQuery(config.component.
|
|
314
|
+
const sharedFetch = ctx.runQuery(config.component.group.member.get, {
|
|
251
315
|
userId,
|
|
252
316
|
groupIds: toFetch
|
|
253
317
|
});
|
|
@@ -260,7 +324,7 @@ function createCoreDomains(deps) {
|
|
|
260
324
|
}
|
|
261
325
|
}
|
|
262
326
|
return await Promise.all(groupIds.map(async (groupId) => {
|
|
263
|
-
const membership$1 = await cached(ctx, `member-inspect:${userId}:${groupId}:n`, () => ctx.runQuery(config.component.
|
|
327
|
+
const membership$1 = await cached(ctx, `member-inspect:${userId}:${groupId}:n`, () => ctx.runQuery(config.component.group.member.get, {
|
|
264
328
|
userId,
|
|
265
329
|
groupId
|
|
266
330
|
}));
|
|
@@ -281,15 +345,13 @@ function createCoreDomains(deps) {
|
|
|
281
345
|
const maxDepth = useAncestry ? Math.max(0, Math.floor(opts.maxDepth ?? 32)) : 0;
|
|
282
346
|
const cacheKey = `member-inspect:${opts.userId}:${opts.groupId}:${useAncestry ? `a${maxDepth}` : "n"}`;
|
|
283
347
|
let membership = null;
|
|
284
|
-
if (useAncestry) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}))).membership;
|
|
292
|
-
} else membership = await cached(ctx, cacheKey, () => ctx.runQuery(config.component.public.memberGetByGroupAndUser, {
|
|
348
|
+
if (useAncestry) membership = (await cached(ctx, cacheKey, () => ctx.runQuery(config.component.group.member.resolve, {
|
|
349
|
+
userId: opts.userId,
|
|
350
|
+
groupId: opts.groupId,
|
|
351
|
+
maxDepth,
|
|
352
|
+
ancestry: true
|
|
353
|
+
}))).membership;
|
|
354
|
+
else membership = await cached(ctx, cacheKey, () => ctx.runQuery(config.component.group.member.get, {
|
|
293
355
|
userId: opts.userId,
|
|
294
356
|
groupId: opts.groupId
|
|
295
357
|
}));
|
|
@@ -309,7 +371,7 @@ function createCoreDomains(deps) {
|
|
|
309
371
|
const member = {
|
|
310
372
|
create: async (ctx, data) => {
|
|
311
373
|
const roleIds = normalizeRoleIds(data.roleIds);
|
|
312
|
-
const memberId = await ctx.runMutation(config.component.
|
|
374
|
+
const memberId = await ctx.runMutation(config.component.group.member.create, {
|
|
313
375
|
...data,
|
|
314
376
|
roleIds
|
|
315
377
|
});
|
|
@@ -317,19 +379,37 @@ function createCoreDomains(deps) {
|
|
|
317
379
|
return { memberId };
|
|
318
380
|
},
|
|
319
381
|
get: async (ctx, memberId) => {
|
|
320
|
-
return await cached(ctx, `member:${memberId}`, () => ctx.runQuery(config.component.
|
|
382
|
+
return await cached(ctx, `member:${memberId}`, () => ctx.runQuery(config.component.group.member.get, { id: memberId }));
|
|
321
383
|
},
|
|
322
384
|
list: async (ctx, opts) => {
|
|
323
|
-
|
|
385
|
+
const page = await ctx.runQuery(config.component.group.member.list, {
|
|
324
386
|
where: opts?.where,
|
|
325
387
|
limit: opts?.limit,
|
|
326
388
|
cursor: opts?.cursor,
|
|
327
389
|
orderBy: opts?.orderBy,
|
|
328
390
|
order: opts?.order
|
|
329
391
|
});
|
|
392
|
+
if (opts?.withGroup !== true && opts?.withGrants !== true) return page;
|
|
393
|
+
const groupDocs = opts?.withGroup ? await group.get(ctx, page.items.map((m) => m.groupId)) : null;
|
|
394
|
+
return {
|
|
395
|
+
items: await Promise.all(page.items.map(async (m, i) => {
|
|
396
|
+
let enriched = { ...m };
|
|
397
|
+
if (groupDocs !== null) enriched.group = groupDocs[i] ?? null;
|
|
398
|
+
if (opts?.withGrants === true) {
|
|
399
|
+
const resolved = await memberInspect(ctx, {
|
|
400
|
+
userId: m.userId,
|
|
401
|
+
groupId: m.groupId
|
|
402
|
+
});
|
|
403
|
+
enriched.roleIds = resolved.roleIds;
|
|
404
|
+
enriched.grants = resolved.grants;
|
|
405
|
+
}
|
|
406
|
+
return enriched;
|
|
407
|
+
})),
|
|
408
|
+
nextCursor: page.nextCursor
|
|
409
|
+
};
|
|
330
410
|
},
|
|
331
411
|
delete: async (ctx, memberId) => {
|
|
332
|
-
await ctx.runMutation(config.component.
|
|
412
|
+
await ctx.runMutation(config.component.group.member.delete, { memberId });
|
|
333
413
|
invalidateCtxCache(ctx, "member");
|
|
334
414
|
invalidateCtxCache(ctx, "member-inspect");
|
|
335
415
|
return { memberId };
|
|
@@ -337,7 +417,7 @@ function createCoreDomains(deps) {
|
|
|
337
417
|
update: async (ctx, memberId, data) => {
|
|
338
418
|
const nextData = { ...data };
|
|
339
419
|
if ("roleIds" in nextData) nextData.roleIds = normalizeRoleIds(Array.isArray(nextData.roleIds) ? nextData.roleIds : void 0);
|
|
340
|
-
await ctx.runMutation(config.component.
|
|
420
|
+
await ctx.runMutation(config.component.group.member.update, {
|
|
341
421
|
memberId,
|
|
342
422
|
data: nextData
|
|
343
423
|
});
|
|
@@ -376,13 +456,70 @@ function createCoreDomains(deps) {
|
|
|
376
456
|
return result;
|
|
377
457
|
}
|
|
378
458
|
};
|
|
459
|
+
const readLastActiveGroup = (doc) => {
|
|
460
|
+
const val = doc?.lastActiveGroup;
|
|
461
|
+
return typeof val === "string" ? val : null;
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* The current user's active group — the workspace selection persisted
|
|
465
|
+
* natively on `User.lastActiveGroup`. Reuses the existing `get/set/clear`
|
|
466
|
+
* vocabulary instead of bespoke `setActiveGroup`/`getActiveGroup`.
|
|
467
|
+
*/
|
|
468
|
+
const active = {
|
|
469
|
+
get: async (ctx, opts) => {
|
|
470
|
+
const userId = opts?.userId ?? await getSessionUserId(ctx);
|
|
471
|
+
if (userId === null || userId === void 0) return null;
|
|
472
|
+
const [userDoc, { items: memberships }] = await Promise.all([user.get(ctx, userId), member.list(ctx, {
|
|
473
|
+
where: { userId },
|
|
474
|
+
limit: 100
|
|
475
|
+
})]);
|
|
476
|
+
if (memberships.length === 0) return null;
|
|
477
|
+
const stored = readLastActiveGroup(userDoc);
|
|
478
|
+
const chosen = memberships.find((m) => m.groupId === stored) ?? memberships[0];
|
|
479
|
+
const groupDoc = await group.get(ctx, chosen.groupId);
|
|
480
|
+
return {
|
|
481
|
+
groupId: chosen.groupId,
|
|
482
|
+
group: groupDoc,
|
|
483
|
+
membership: chosen
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
set: async (ctx, groupId, opts) => {
|
|
487
|
+
const userId = opts?.userId ?? await getSessionUserId(ctx);
|
|
488
|
+
if (userId === null || userId === void 0) throw new ConvexError({
|
|
489
|
+
code: "NOT_SIGNED_IN",
|
|
490
|
+
message: "Authentication required."
|
|
491
|
+
});
|
|
492
|
+
const { items } = await member.list(ctx, {
|
|
493
|
+
where: {
|
|
494
|
+
userId,
|
|
495
|
+
groupId
|
|
496
|
+
},
|
|
497
|
+
limit: 1
|
|
498
|
+
});
|
|
499
|
+
if (items.length === 0) throw new ConvexError({
|
|
500
|
+
code: "NOT_A_MEMBER",
|
|
501
|
+
message: "User is not a member of this group."
|
|
502
|
+
});
|
|
503
|
+
await user.update(ctx, userId, { lastActiveGroup: groupId });
|
|
504
|
+
return { groupId };
|
|
505
|
+
},
|
|
506
|
+
clear: async (ctx, opts) => {
|
|
507
|
+
const userId = opts?.userId ?? await getSessionUserId(ctx);
|
|
508
|
+
if (userId === null || userId === void 0) throw new ConvexError({
|
|
509
|
+
code: "NOT_SIGNED_IN",
|
|
510
|
+
message: "Authentication required."
|
|
511
|
+
});
|
|
512
|
+
await user.update(ctx, userId, { lastActiveGroup: void 0 });
|
|
513
|
+
return { groupId: null };
|
|
514
|
+
}
|
|
515
|
+
};
|
|
379
516
|
const invite = {
|
|
380
517
|
create: async (ctx, data) => {
|
|
381
518
|
const roleIds = normalizeRoleIds(data.roleIds);
|
|
382
519
|
const token = generateRandomString(inviteTokenLength, inviteTokenAlphabet);
|
|
383
520
|
const tokenHash = await sha256(token);
|
|
384
521
|
return {
|
|
385
|
-
inviteId: await ctx.runMutation(config.component.
|
|
522
|
+
inviteId: await ctx.runMutation(config.component.group.invite.create, {
|
|
386
523
|
...data,
|
|
387
524
|
roleIds,
|
|
388
525
|
tokenHash,
|
|
@@ -392,23 +529,23 @@ function createCoreDomains(deps) {
|
|
|
392
529
|
};
|
|
393
530
|
},
|
|
394
531
|
get: async (ctx, inviteId) => {
|
|
395
|
-
return await ctx.runQuery(config.component.
|
|
532
|
+
return await ctx.runQuery(config.component.group.invite.get, { id: inviteId });
|
|
396
533
|
},
|
|
397
534
|
token: {
|
|
398
535
|
get: async (ctx, token) => {
|
|
399
536
|
const tokenHash = await sha256(token);
|
|
400
|
-
return await ctx.runQuery(config.component.
|
|
537
|
+
return await ctx.runQuery(config.component.group.invite.get, { tokenHash });
|
|
401
538
|
},
|
|
402
539
|
accept: async (ctx, args) => {
|
|
403
540
|
const tokenHash = await sha256(args.token);
|
|
404
|
-
return { ...await ctx.runMutation(config.component.
|
|
541
|
+
return { ...await ctx.runMutation(config.component.group.invite.redeem, {
|
|
405
542
|
tokenHash,
|
|
406
543
|
acceptedByUserId: args.acceptedByUserId
|
|
407
544
|
}) };
|
|
408
545
|
}
|
|
409
546
|
},
|
|
410
547
|
list: async (ctx, opts) => {
|
|
411
|
-
return await ctx.runQuery(config.component.
|
|
548
|
+
return await ctx.runQuery(config.component.group.invite.list, {
|
|
412
549
|
where: opts?.where,
|
|
413
550
|
limit: opts?.limit,
|
|
414
551
|
cursor: opts?.cursor,
|
|
@@ -417,7 +554,7 @@ function createCoreDomains(deps) {
|
|
|
417
554
|
});
|
|
418
555
|
},
|
|
419
556
|
accept: async (ctx, inviteId, acceptedByUserId) => {
|
|
420
|
-
await ctx.runMutation(config.component.
|
|
557
|
+
await ctx.runMutation(config.component.group.invite.accept, {
|
|
421
558
|
inviteId,
|
|
422
559
|
...acceptedByUserId ? { acceptedByUserId } : {}
|
|
423
560
|
});
|
|
@@ -427,7 +564,7 @@ function createCoreDomains(deps) {
|
|
|
427
564
|
};
|
|
428
565
|
},
|
|
429
566
|
revoke: async (ctx, inviteId) => {
|
|
430
|
-
await ctx.runMutation(config.component.
|
|
567
|
+
await ctx.runMutation(config.component.group.invite.revoke, { inviteId });
|
|
431
568
|
return { inviteId };
|
|
432
569
|
}
|
|
433
570
|
};
|
|
@@ -435,7 +572,7 @@ function createCoreDomains(deps) {
|
|
|
435
572
|
create: async (ctx, opts) => {
|
|
436
573
|
const { raw, hashedKey, displayPrefix } = await generateApiKey("sk_");
|
|
437
574
|
return {
|
|
438
|
-
keyId: await ctx.runMutation(config.component.
|
|
575
|
+
keyId: await ctx.runMutation(config.component.user.key.create, {
|
|
439
576
|
userId: opts.userId,
|
|
440
577
|
prefix: displayPrefix,
|
|
441
578
|
hashedKey,
|
|
@@ -450,7 +587,7 @@ function createCoreDomains(deps) {
|
|
|
450
587
|
},
|
|
451
588
|
verify: async (ctx, rawKey) => {
|
|
452
589
|
const hashedKey = await hashApiKey(rawKey);
|
|
453
|
-
const doc = await ctx.runQuery(config.component.
|
|
590
|
+
const doc = await ctx.runQuery(config.component.user.key.get, { hashedKey });
|
|
454
591
|
if (!doc) throw new ConvexError({
|
|
455
592
|
code: "INVALID_API_KEY",
|
|
456
593
|
message: "Invalid API key."
|
|
@@ -473,7 +610,7 @@ function createCoreDomains(deps) {
|
|
|
473
610
|
});
|
|
474
611
|
patchData.rateLimitState = newState;
|
|
475
612
|
}
|
|
476
|
-
await ctx.runMutation(config.component.
|
|
613
|
+
await ctx.runMutation(config.component.user.key.update, {
|
|
477
614
|
keyId: k._id,
|
|
478
615
|
data: patchData
|
|
479
616
|
});
|
|
@@ -484,7 +621,7 @@ function createCoreDomains(deps) {
|
|
|
484
621
|
};
|
|
485
622
|
},
|
|
486
623
|
list: async (ctx, opts) => {
|
|
487
|
-
return await ctx.runQuery(config.component.
|
|
624
|
+
return await ctx.runQuery(config.component.user.key.list, {
|
|
488
625
|
where: opts?.where,
|
|
489
626
|
limit: opts?.limit,
|
|
490
627
|
cursor: opts?.cursor,
|
|
@@ -493,28 +630,28 @@ function createCoreDomains(deps) {
|
|
|
493
630
|
});
|
|
494
631
|
},
|
|
495
632
|
get: async (ctx, keyId) => {
|
|
496
|
-
return await ctx.runQuery(config.component.
|
|
633
|
+
return await ctx.runQuery(config.component.user.key.get, { id: keyId }) ?? null;
|
|
497
634
|
},
|
|
498
635
|
update: async (ctx, keyId, data) => {
|
|
499
|
-
await ctx.runMutation(config.component.
|
|
636
|
+
await ctx.runMutation(config.component.user.key.update, {
|
|
500
637
|
keyId,
|
|
501
638
|
data
|
|
502
639
|
});
|
|
503
640
|
return { keyId };
|
|
504
641
|
},
|
|
505
642
|
revoke: async (ctx, keyId) => {
|
|
506
|
-
await ctx.runMutation(config.component.
|
|
643
|
+
await ctx.runMutation(config.component.user.key.update, {
|
|
507
644
|
keyId,
|
|
508
645
|
data: { revoked: true }
|
|
509
646
|
});
|
|
510
647
|
return { keyId };
|
|
511
648
|
},
|
|
512
649
|
delete: async (ctx, keyId) => {
|
|
513
|
-
await ctx.runMutation(config.component.
|
|
650
|
+
await ctx.runMutation(config.component.user.key.delete, { keyId });
|
|
514
651
|
return { keyId };
|
|
515
652
|
},
|
|
516
653
|
rotate: async (ctx, keyId, opts) => {
|
|
517
|
-
const existing = await ctx.runQuery(config.component.
|
|
654
|
+
const existing = await ctx.runQuery(config.component.user.key.get, { id: keyId });
|
|
518
655
|
if (!existing) throw new ConvexError({
|
|
519
656
|
code: "INVALID_PARAMETERS",
|
|
520
657
|
message: "The provided parameters are invalid."
|
|
@@ -524,7 +661,7 @@ function createCoreDomains(deps) {
|
|
|
524
661
|
code: "API_KEY_REVOKED",
|
|
525
662
|
message: "This API key has been revoked."
|
|
526
663
|
});
|
|
527
|
-
await ctx.runMutation(config.component.
|
|
664
|
+
await ctx.runMutation(config.component.user.key.update, {
|
|
528
665
|
keyId,
|
|
529
666
|
data: { revoked: true }
|
|
530
667
|
});
|
|
@@ -546,7 +683,8 @@ function createCoreDomains(deps) {
|
|
|
546
683
|
group,
|
|
547
684
|
member,
|
|
548
685
|
invite,
|
|
549
|
-
key
|
|
686
|
+
key,
|
|
687
|
+
active
|
|
550
688
|
};
|
|
551
689
|
}
|
|
552
690
|
|