@robelest/convex-auth 0.0.4-preview.21 → 0.0.4-preview.23
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 +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +36 -39
- package/dist/client/index.js.map +1 -1
- package/dist/component/client/index.d.ts +1 -2
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/model.d.ts +5 -5
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.js.map +1 -1
- package/dist/component/public/enterprise/core.d.ts.map +1 -1
- package/dist/component/public/enterprise/core.js.map +1 -1
- package/dist/component/public/enterprise/domains.d.ts.map +1 -1
- package/dist/component/public/enterprise/domains.js.map +1 -1
- package/dist/component/public/enterprise/scim.d.ts.map +1 -1
- package/dist/component/public/enterprise/scim.js.map +1 -1
- package/dist/component/public/enterprise/secrets.d.ts.map +1 -1
- package/dist/component/public/enterprise/secrets.js.map +1 -1
- package/dist/component/public/enterprise/webhooks.d.ts.map +1 -1
- package/dist/component/public/enterprise/webhooks.js.map +1 -1
- package/dist/component/public/factors/devices.d.ts.map +1 -1
- package/dist/component/public/factors/devices.js.map +1 -1
- package/dist/component/public/factors/passkeys.d.ts.map +1 -1
- package/dist/component/public/factors/passkeys.js.map +1 -1
- package/dist/component/public/factors/totp.d.ts.map +1 -1
- package/dist/component/public/factors/totp.js.map +1 -1
- package/dist/component/public/groups/core.js.map +1 -1
- package/dist/component/public/groups/invites.d.ts.map +1 -1
- package/dist/component/public/groups/invites.js.map +1 -1
- package/dist/component/public/groups/members.d.ts.map +1 -1
- package/dist/component/public/groups/members.js.map +1 -1
- package/dist/component/public/identity/accounts.d.ts.map +1 -1
- package/dist/component/public/identity/accounts.js.map +1 -1
- package/dist/component/public/identity/codes.d.ts.map +1 -1
- package/dist/component/public/identity/codes.js.map +1 -1
- package/dist/component/public/identity/sessions.d.ts.map +1 -1
- package/dist/component/public/identity/sessions.js.map +1 -1
- package/dist/component/public/identity/tokens.d.ts.map +1 -1
- package/dist/component/public/identity/tokens.js.map +1 -1
- package/dist/component/public/identity/users.d.ts.map +1 -1
- package/dist/component/public/identity/users.js.map +1 -1
- package/dist/component/public/identity/verifiers.d.ts.map +1 -1
- package/dist/component/public/identity/verifiers.js.map +1 -1
- package/dist/component/public/security/keys.d.ts.map +1 -1
- package/dist/component/public/security/keys.js.map +1 -1
- package/dist/component/public/security/limits.d.ts.map +1 -1
- package/dist/component/public/security/limits.js.map +1 -1
- package/dist/component/schema.d.ts +39 -39
- package/dist/component/server/auth.d.ts +95 -52
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +63 -43
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/core.js +116 -235
- package/dist/component/server/core.js.map +1 -1
- package/dist/component/server/crypto.js +25 -7
- package/dist/component/server/crypto.js.map +1 -1
- package/dist/component/server/device.js +58 -15
- package/dist/component/server/device.js.map +1 -1
- package/dist/component/server/enterprise/domain.js +148 -59
- package/dist/component/server/enterprise/domain.js.map +1 -1
- package/dist/component/server/enterprise/http.js +36 -15
- package/dist/component/server/enterprise/http.js.map +1 -1
- package/dist/component/server/enterprise/oidc.js +1 -1
- package/dist/component/server/http.js +26 -21
- package/dist/component/server/http.js.map +1 -1
- package/dist/component/server/identity.js +5 -2
- package/dist/component/server/identity.js.map +1 -1
- package/dist/component/server/limits.js +21 -30
- package/dist/component/server/limits.js.map +1 -1
- package/dist/component/server/mutations/account.js +12 -10
- package/dist/component/server/mutations/account.js.map +1 -1
- package/dist/component/server/mutations/code.js +5 -2
- package/dist/component/server/mutations/code.js.map +1 -1
- package/dist/component/server/mutations/invalidate.js +1 -1
- package/dist/component/server/mutations/invalidate.js.map +1 -1
- package/dist/component/server/mutations/oauth.js +10 -4
- package/dist/component/server/mutations/oauth.js.map +1 -1
- package/dist/component/server/mutations/refresh.js +2 -2
- package/dist/component/server/mutations/refresh.js.map +1 -1
- package/dist/component/server/mutations/register.js +46 -42
- package/dist/component/server/mutations/register.js.map +1 -1
- package/dist/component/server/mutations/retrieve.js +21 -25
- package/dist/component/server/mutations/retrieve.js.map +1 -1
- package/dist/component/server/mutations/signature.js +10 -4
- package/dist/component/server/mutations/signature.js.map +1 -1
- package/dist/component/server/mutations/signout.js.map +1 -1
- package/dist/component/server/mutations/store.js +9 -24
- package/dist/component/server/mutations/store.js.map +1 -1
- package/dist/component/server/mutations/verifier.js.map +1 -1
- package/dist/component/server/mutations/verify.js +1 -1
- package/dist/component/server/mutations/verify.js.map +1 -1
- package/dist/component/server/oauth.js +53 -16
- package/dist/component/server/oauth.js.map +1 -1
- package/dist/component/server/passkey.js +115 -31
- package/dist/component/server/passkey.js.map +1 -1
- package/dist/component/server/redirects.js +9 -3
- package/dist/component/server/redirects.js.map +1 -1
- package/dist/component/server/refresh.js +10 -7
- package/dist/component/server/refresh.js.map +1 -1
- package/dist/component/server/runtime.d.ts +3 -3
- package/dist/component/server/runtime.d.ts.map +1 -1
- package/dist/component/server/runtime.js +62 -20
- package/dist/component/server/runtime.js.map +1 -1
- package/dist/component/server/signin.js +34 -10
- package/dist/component/server/signin.js.map +1 -1
- package/dist/component/server/totp.js +79 -19
- package/dist/component/server/totp.js.map +1 -1
- package/dist/component/server/types.d.ts +12 -20
- package/dist/component/server/types.d.ts.map +1 -1
- package/dist/component/server/types.js.map +1 -1
- package/dist/component/server/users.js +6 -3
- package/dist/component/server/users.js.map +1 -1
- package/dist/component/server/utils.js +10 -4
- package/dist/component/server/utils.js.map +1 -1
- package/dist/core/types.d.ts +14 -22
- package/dist/core/types.d.ts.map +1 -1
- package/dist/factors/device.js +8 -9
- package/dist/factors/device.js.map +1 -1
- package/dist/factors/passkey.js +18 -21
- package/dist/factors/passkey.js.map +1 -1
- package/dist/providers/password.js +66 -81
- package/dist/providers/password.js.map +1 -1
- package/dist/runtime/invite.js +2 -8
- package/dist/runtime/invite.js.map +1 -1
- package/dist/server/auth.d.ts +95 -52
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +63 -43
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core.d.ts +71 -159
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +116 -235
- package/dist/server/core.js.map +1 -1
- package/dist/server/crypto.d.ts.map +1 -1
- package/dist/server/crypto.js +25 -7
- package/dist/server/crypto.js.map +1 -1
- package/dist/server/device.js +58 -15
- package/dist/server/device.js.map +1 -1
- package/dist/server/enterprise/domain.d.ts +0 -8
- package/dist/server/enterprise/domain.d.ts.map +1 -1
- package/dist/server/enterprise/domain.js +148 -59
- package/dist/server/enterprise/domain.js.map +1 -1
- package/dist/server/enterprise/http.d.ts.map +1 -1
- package/dist/server/enterprise/http.js +35 -14
- package/dist/server/enterprise/http.js.map +1 -1
- package/dist/server/http.d.ts +2 -2
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +25 -20
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity.js +5 -2
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/limits.js +21 -30
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mounts.d.ts +26 -64
- package/dist/server/mounts.d.ts.map +1 -1
- package/dist/server/mounts.js +45 -106
- package/dist/server/mounts.js.map +1 -1
- package/dist/server/mutations/account.d.ts +8 -9
- package/dist/server/mutations/account.d.ts.map +1 -1
- package/dist/server/mutations/account.js +11 -9
- package/dist/server/mutations/account.js.map +1 -1
- package/dist/server/mutations/code.d.ts +13 -13
- package/dist/server/mutations/code.d.ts.map +1 -1
- package/dist/server/mutations/code.js +5 -2
- package/dist/server/mutations/code.js.map +1 -1
- package/dist/server/mutations/invalidate.d.ts +4 -4
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.d.ts +12 -10
- package/dist/server/mutations/oauth.d.ts.map +1 -1
- package/dist/server/mutations/oauth.js +9 -3
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/refresh.d.ts +3 -3
- package/dist/server/mutations/refresh.d.ts.map +1 -1
- package/dist/server/mutations/refresh.js +1 -1
- package/dist/server/mutations/refresh.js.map +1 -1
- package/dist/server/mutations/register.d.ts +11 -11
- package/dist/server/mutations/register.d.ts.map +1 -1
- package/dist/server/mutations/register.js +45 -41
- package/dist/server/mutations/register.js.map +1 -1
- package/dist/server/mutations/retrieve.d.ts +6 -6
- package/dist/server/mutations/retrieve.d.ts.map +1 -1
- package/dist/server/mutations/retrieve.js +20 -24
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signature.d.ts +6 -7
- package/dist/server/mutations/signature.d.ts.map +1 -1
- package/dist/server/mutations/signature.js +9 -3
- package/dist/server/mutations/signature.js.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/signout.js.map +1 -1
- package/dist/server/mutations/store.d.ts +97 -97
- package/dist/server/mutations/store.d.ts.map +1 -1
- package/dist/server/mutations/store.js +8 -23
- package/dist/server/mutations/store.js.map +1 -1
- package/dist/server/mutations/verifier.js.map +1 -1
- package/dist/server/mutations/verify.d.ts +10 -10
- package/dist/server/mutations/verify.d.ts.map +1 -1
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth.js +53 -16
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/passkey.d.ts +2 -2
- package/dist/server/passkey.d.ts.map +1 -1
- package/dist/server/passkey.js +114 -30
- package/dist/server/passkey.js.map +1 -1
- package/dist/server/redirects.js +9 -3
- package/dist/server/redirects.js.map +1 -1
- package/dist/server/refresh.js +10 -7
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/runtime.d.ts +14 -14
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +61 -19
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/signin.js +34 -10
- package/dist/server/signin.js.map +1 -1
- package/dist/server/ssr.d.ts.map +1 -1
- package/dist/server/ssr.js +175 -184
- package/dist/server/ssr.js.map +1 -1
- package/dist/server/totp.js +78 -18
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +13 -21
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/server/users.js +6 -3
- package/dist/server/users.js.map +1 -1
- package/dist/server/utils.js +10 -4
- package/dist/server/utils.js.map +1 -1
- package/package.json +2 -6
- package/src/authorization/index.ts +1 -1
- package/src/cli/index.ts +1 -1
- package/src/client/core/types.ts +14 -14
- package/src/client/factors/device.ts +10 -12
- package/src/client/factors/passkey.ts +23 -26
- package/src/client/index.ts +54 -64
- package/src/client/runtime/invite.ts +5 -7
- package/src/component/index.ts +1 -0
- package/src/component/public/enterprise/audit.ts +6 -1
- package/src/component/public/enterprise/core.ts +1 -0
- package/src/component/public/enterprise/domains.ts +5 -1
- package/src/component/public/enterprise/scim.ts +1 -0
- package/src/component/public/enterprise/secrets.ts +1 -0
- package/src/component/public/enterprise/webhooks.ts +1 -0
- package/src/component/public/factors/devices.ts +1 -0
- package/src/component/public/factors/passkeys.ts +1 -0
- package/src/component/public/factors/totp.ts +1 -0
- package/src/component/public/groups/core.ts +1 -1
- package/src/component/public/groups/invites.ts +7 -1
- package/src/component/public/groups/members.ts +1 -0
- package/src/component/public/identity/accounts.ts +1 -0
- package/src/component/public/identity/codes.ts +1 -0
- package/src/component/public/identity/sessions.ts +1 -0
- package/src/component/public/identity/tokens.ts +1 -0
- package/src/component/public/identity/users.ts +1 -0
- package/src/component/public/identity/verifiers.ts +1 -0
- package/src/component/public/security/keys.ts +1 -0
- package/src/component/public/security/limits.ts +1 -0
- package/src/providers/password.ts +89 -110
- package/src/server/auth.ts +177 -111
- package/src/server/core.ts +197 -233
- package/src/server/crypto.ts +31 -29
- package/src/server/device.ts +65 -32
- package/src/server/enterprise/domain.ts +158 -170
- package/src/server/enterprise/http.ts +46 -39
- package/src/server/http.ts +36 -30
- package/src/server/identity.ts +5 -5
- package/src/server/index.ts +2 -0
- package/src/server/limits.ts +53 -80
- package/src/server/mounts.ts +47 -74
- package/src/server/mutations/account.ts +22 -36
- package/src/server/mutations/code.ts +6 -6
- package/src/server/mutations/invalidate.ts +1 -1
- package/src/server/mutations/oauth.ts +14 -8
- package/src/server/mutations/refresh.ts +5 -4
- package/src/server/mutations/register.ts +87 -132
- package/src/server/mutations/retrieve.ts +44 -44
- package/src/server/mutations/signature.ts +13 -6
- package/src/server/mutations/signout.ts +1 -1
- package/src/server/mutations/store.ts +16 -31
- package/src/server/mutations/verifier.ts +1 -1
- package/src/server/mutations/verify.ts +3 -5
- package/src/server/oauth.ts +60 -69
- package/src/server/passkey.ts +567 -517
- package/src/server/redirects.ts +10 -6
- package/src/server/refresh.ts +14 -18
- package/src/server/runtime.ts +70 -55
- package/src/server/signin.ts +44 -37
- package/src/server/ssr.ts +390 -407
- package/src/server/totp.ts +85 -35
- package/src/server/types.ts +19 -22
- package/src/server/users.ts +7 -6
- package/src/server/utils.ts +10 -12
- package/dist/component/server/authError.js +0 -34
- package/dist/component/server/authError.js.map +0 -1
- package/dist/component/server/errors.d.ts +0 -1
- package/dist/component/server/errors.js +0 -137
- package/dist/component/server/errors.js.map +0 -1
- package/dist/server/authError.d.ts +0 -46
- package/dist/server/authError.d.ts.map +0 -1
- package/dist/server/authError.js +0 -34
- package/dist/server/authError.js.map +0 -1
- package/dist/server/errors.d.ts +0 -177
- package/dist/server/errors.d.ts.map +0 -1
- package/dist/server/errors.js +0 -212
- package/dist/server/errors.js.map +0 -1
- package/src/server/authError.ts +0 -44
- package/src/server/errors.ts +0 -290
package/src/server/core.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { Cv } from "@robelest/fx/convex";
|
|
1
2
|
import { Auth, GenericActionCtx, GenericDataModel } from "convex/server";
|
|
2
3
|
import { GenericId } from "convex/values";
|
|
3
4
|
|
|
5
|
+
import { materializeProvider } from "./config";
|
|
4
6
|
import {
|
|
5
7
|
buildScopeChecker,
|
|
6
8
|
checkKeyRateLimit,
|
|
7
9
|
generateApiKey,
|
|
8
10
|
hashApiKey,
|
|
9
11
|
} from "./keys";
|
|
10
|
-
import { materializeProvider } from "./config";
|
|
11
12
|
import { signInImpl } from "./signin";
|
|
12
13
|
import type {
|
|
13
14
|
AuthProviderConfig,
|
|
@@ -17,11 +18,7 @@ import type {
|
|
|
17
18
|
UserOrderBy,
|
|
18
19
|
UserWhere,
|
|
19
20
|
} from "./types";
|
|
20
|
-
import {
|
|
21
|
-
generateRandomString,
|
|
22
|
-
sha256,
|
|
23
|
-
TOKEN_SUB_CLAIM_DIVIDER,
|
|
24
|
-
} from "./utils";
|
|
21
|
+
import { generateRandomString, sha256, TOKEN_SUB_CLAIM_DIVIDER } from "./utils";
|
|
25
22
|
|
|
26
23
|
type ComponentCtx = Pick<
|
|
27
24
|
GenericActionCtx<GenericDataModel>,
|
|
@@ -104,17 +101,17 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
104
101
|
return roleDefinitions[roleId] ?? null;
|
|
105
102
|
};
|
|
106
103
|
|
|
107
|
-
const normalizeRoleIds = (
|
|
108
|
-
roleIds?: string[],
|
|
109
|
-
):
|
|
110
|
-
| { ok: true; roleIds: string[] }
|
|
111
|
-
| { ok: false; invalidRoleIds: string[] } => {
|
|
104
|
+
const normalizeRoleIds = (roleIds?: string[]): string[] => {
|
|
112
105
|
const normalized = Array.from(new Set(roleIds ?? []));
|
|
113
106
|
const invalid = normalized.filter((id) => getRoleDefinition(id) === null);
|
|
114
107
|
if (invalid.length > 0) {
|
|
115
|
-
|
|
108
|
+
throw Cv.error({
|
|
109
|
+
code: "INVALID_ROLE_IDS",
|
|
110
|
+
message: "One or more role IDs are invalid.",
|
|
111
|
+
invalidRoleIds: invalid,
|
|
112
|
+
});
|
|
116
113
|
}
|
|
117
|
-
return
|
|
114
|
+
return normalized;
|
|
118
115
|
};
|
|
119
116
|
|
|
120
117
|
const listAllKeysByUser = async (ctx: ComponentCtx, userId: string) => {
|
|
@@ -225,14 +222,15 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
225
222
|
const authHeader = request.headers.get("Authorization");
|
|
226
223
|
if (authHeader?.startsWith("Bearer sk_")) {
|
|
227
224
|
const rawKey = authHeader.slice(7);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
225
|
+
try {
|
|
226
|
+
const result = await getAuth().key.verify(
|
|
227
|
+
ctx as ComponentCtx,
|
|
228
|
+
rawKey,
|
|
229
|
+
);
|
|
233
230
|
return result.userId;
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
234
233
|
}
|
|
235
|
-
return null;
|
|
236
234
|
}
|
|
237
235
|
}
|
|
238
236
|
return null;
|
|
@@ -325,7 +323,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
325
323
|
* @param ctx - Convex mutation context.
|
|
326
324
|
* @param userId - The user's document ID.
|
|
327
325
|
* @param data - Fields to merge into the user document.
|
|
328
|
-
* @returns `{
|
|
326
|
+
* @returns `{ userId }`.
|
|
329
327
|
*
|
|
330
328
|
* @example
|
|
331
329
|
* ```ts
|
|
@@ -344,7 +342,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
344
342
|
userId,
|
|
345
343
|
data,
|
|
346
344
|
});
|
|
347
|
-
return {
|
|
345
|
+
return { userId };
|
|
348
346
|
},
|
|
349
347
|
/**
|
|
350
348
|
* Set the user's active group. Stored in `user.extend.lastActiveGroup`.
|
|
@@ -354,7 +352,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
354
352
|
* @param ctx - Convex mutation context.
|
|
355
353
|
* @param opts.userId - The user's document ID.
|
|
356
354
|
* @param opts.groupId - Group ID to set as active, or `null` to clear.
|
|
357
|
-
* @returns `{
|
|
355
|
+
* @returns `{ userId, groupId }` confirming the active group was set (or cleared).
|
|
358
356
|
*
|
|
359
357
|
* @example
|
|
360
358
|
* ```ts
|
|
@@ -380,12 +378,12 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
380
378
|
if (opts.groupId === null) {
|
|
381
379
|
const { lastActiveGroup: _omit, ...rest } = existingExtend;
|
|
382
380
|
await user.update(ctx, opts.userId, { extend: rest });
|
|
383
|
-
return {
|
|
381
|
+
return { userId: opts.userId, groupId: null };
|
|
384
382
|
}
|
|
385
383
|
await user.update(ctx, opts.userId, {
|
|
386
384
|
extend: { ...existingExtend, lastActiveGroup: opts.groupId },
|
|
387
385
|
});
|
|
388
|
-
return {
|
|
386
|
+
return { userId: opts.userId, groupId: opts.groupId };
|
|
389
387
|
},
|
|
390
388
|
/**
|
|
391
389
|
* Read the user's active group ID from `user.extend.lastActiveGroup`.
|
|
@@ -429,7 +427,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
429
427
|
* @param ctx - Convex mutation context.
|
|
430
428
|
* @param userId - The user's document ID.
|
|
431
429
|
* @param opts.cascade - Whether to delete related records (default `true`).
|
|
432
|
-
* @returns `{
|
|
430
|
+
* @returns `{ userId }`.
|
|
431
|
+
* @throws `INVALID_PARAMETERS` if `cascade` is `false` but the user has linked data.
|
|
433
432
|
*/
|
|
434
433
|
delete: async (
|
|
435
434
|
ctx: ComponentCtx,
|
|
@@ -462,7 +461,10 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
462
461
|
passkeys.length +
|
|
463
462
|
totps.length;
|
|
464
463
|
if (!cascade && totalLinked > 0) {
|
|
465
|
-
|
|
464
|
+
throw Cv.error({
|
|
465
|
+
code: "INVALID_PARAMETERS",
|
|
466
|
+
message: "The provided parameters are invalid.",
|
|
467
|
+
});
|
|
466
468
|
}
|
|
467
469
|
const deletions: Promise<unknown>[] = [];
|
|
468
470
|
for (const s of sessions)
|
|
@@ -501,7 +503,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
501
503
|
);
|
|
502
504
|
await Promise.all(deletions);
|
|
503
505
|
await ctx.runMutation(config.component.public.userDelete, { userId });
|
|
504
|
-
return {
|
|
506
|
+
return { userId };
|
|
505
507
|
},
|
|
506
508
|
};
|
|
507
509
|
|
|
@@ -547,7 +549,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
547
549
|
* @param ctx - Convex action context.
|
|
548
550
|
* @param args.userId - The user whose sessions should be invalidated.
|
|
549
551
|
* @param args.except - Optional array of session IDs to keep valid.
|
|
550
|
-
* @returns `{
|
|
552
|
+
* @returns `{ userId, except }` confirming the operation.
|
|
551
553
|
*
|
|
552
554
|
* @example Sign out everywhere except the current session
|
|
553
555
|
* ```ts
|
|
@@ -564,7 +566,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
564
566
|
) => {
|
|
565
567
|
await callInvalidateSessions(ctx, args);
|
|
566
568
|
return {
|
|
567
|
-
ok: true as const,
|
|
568
569
|
userId: args.userId,
|
|
569
570
|
except: args.except ?? [],
|
|
570
571
|
};
|
|
@@ -635,7 +636,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
635
636
|
* @param args.profile - Profile data used to create or update the user document.
|
|
636
637
|
* @param args.shouldLinkViaEmail - If `true`, link to an existing user by email match.
|
|
637
638
|
* @param args.shouldLinkViaPhone - If `true`, link to an existing user by phone match.
|
|
638
|
-
* @returns
|
|
639
|
+
* @returns The created account and user information.
|
|
639
640
|
*
|
|
640
641
|
* @example
|
|
641
642
|
* ```ts
|
|
@@ -651,7 +652,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
651
652
|
args: CreateAccountArgs,
|
|
652
653
|
) => {
|
|
653
654
|
const created = await callCreateAccountFromCredentials(ctx, args);
|
|
654
|
-
return {
|
|
655
|
+
return { ...created };
|
|
655
656
|
},
|
|
656
657
|
/**
|
|
657
658
|
* Retrieve an auth account by provider and credentials.
|
|
@@ -700,7 +701,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
700
701
|
* @param args.provider - The provider ID (e.g. `"password"`).
|
|
701
702
|
* @param args.account.id - Provider-specific account identifier.
|
|
702
703
|
* @param args.account.secret - The new credential secret to store.
|
|
703
|
-
* @returns `{
|
|
704
|
+
* @returns `{ accountId }` confirming the update.
|
|
704
705
|
*
|
|
705
706
|
* @example Password reset
|
|
706
707
|
* ```ts
|
|
@@ -715,7 +716,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
715
716
|
args: UpdateAccountCredentialsArgs,
|
|
716
717
|
) => {
|
|
717
718
|
await callModifyAccount(ctx, args);
|
|
718
|
-
return {
|
|
719
|
+
return { accountId: args.account.id };
|
|
719
720
|
},
|
|
720
721
|
/**
|
|
721
722
|
* Delete an auth account by ID.
|
|
@@ -727,16 +728,13 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
727
728
|
*
|
|
728
729
|
* @param ctx - Convex mutation context.
|
|
729
730
|
* @param accountId - The account's document ID.
|
|
730
|
-
* @returns `{
|
|
731
|
-
*
|
|
732
|
-
*
|
|
731
|
+
* @returns `{ accountId }` on success.
|
|
732
|
+
* @throws `ACCOUNT_NOT_FOUND` if the account does not exist.
|
|
733
|
+
* @throws `INVALID_PARAMETERS` if it is the user's last account.
|
|
733
734
|
*
|
|
734
735
|
* @example
|
|
735
736
|
* ```ts
|
|
736
|
-
*
|
|
737
|
-
* if (!result.ok) {
|
|
738
|
-
* console.error("Cannot delete account:", result.code);
|
|
739
|
-
* }
|
|
737
|
+
* await auth.account.delete(ctx, accountId);
|
|
740
738
|
* ```
|
|
741
739
|
*/
|
|
742
740
|
delete: async (ctx: ComponentCtx, accountId: string) => {
|
|
@@ -744,19 +742,25 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
744
742
|
accountId,
|
|
745
743
|
});
|
|
746
744
|
if (doc === null) {
|
|
747
|
-
|
|
745
|
+
throw Cv.error({
|
|
746
|
+
code: "ACCOUNT_NOT_FOUND",
|
|
747
|
+
message: "Account not found.",
|
|
748
|
+
});
|
|
748
749
|
}
|
|
749
750
|
const allAccounts = (await ctx.runQuery(
|
|
750
751
|
config.component.public.accountListByUser,
|
|
751
752
|
{ userId: (doc as any).userId },
|
|
752
753
|
)) as Array<{ _id: string }>;
|
|
753
754
|
if (allAccounts.length <= 1) {
|
|
754
|
-
|
|
755
|
+
throw Cv.error({
|
|
756
|
+
code: "INVALID_PARAMETERS",
|
|
757
|
+
message: "The provided parameters are invalid.",
|
|
758
|
+
});
|
|
755
759
|
}
|
|
756
760
|
await ctx.runMutation(config.component.public.accountDelete, {
|
|
757
761
|
accountId,
|
|
758
762
|
});
|
|
759
|
-
return {
|
|
763
|
+
return { accountId };
|
|
760
764
|
},
|
|
761
765
|
/**
|
|
762
766
|
* List all passkey credentials registered for a user.
|
|
@@ -794,7 +798,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
794
798
|
* @param ctx - Convex mutation context.
|
|
795
799
|
* @param passkeyId - The passkey credential's document ID.
|
|
796
800
|
* @param name - The new display name for the passkey.
|
|
797
|
-
* @returns `{
|
|
801
|
+
* @returns `{ passkeyId }` confirming the rename.
|
|
798
802
|
*
|
|
799
803
|
* @example
|
|
800
804
|
* ```ts
|
|
@@ -810,7 +814,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
810
814
|
passkeyId,
|
|
811
815
|
data: { name },
|
|
812
816
|
});
|
|
813
|
-
return {
|
|
817
|
+
return { passkeyId };
|
|
814
818
|
},
|
|
815
819
|
/**
|
|
816
820
|
* Delete a passkey credential.
|
|
@@ -821,7 +825,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
821
825
|
*
|
|
822
826
|
* @param ctx - Convex mutation context.
|
|
823
827
|
* @param passkeyId - The passkey credential's document ID.
|
|
824
|
-
* @returns `{
|
|
828
|
+
* @returns `{ passkeyId }` confirming the deletion.
|
|
825
829
|
*
|
|
826
830
|
* @example
|
|
827
831
|
* ```ts
|
|
@@ -832,7 +836,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
832
836
|
await ctx.runMutation(config.component.public.passkeyDelete, {
|
|
833
837
|
passkeyId,
|
|
834
838
|
});
|
|
835
|
-
return {
|
|
839
|
+
return { passkeyId };
|
|
836
840
|
},
|
|
837
841
|
/**
|
|
838
842
|
* List all TOTP (time-based one-time password) factors for a user.
|
|
@@ -863,7 +867,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
863
867
|
*
|
|
864
868
|
* @param ctx - Convex mutation context.
|
|
865
869
|
* @param totpId - The TOTP factor's document ID.
|
|
866
|
-
* @returns `{
|
|
870
|
+
* @returns `{ totpId }` confirming the deletion.
|
|
867
871
|
*
|
|
868
872
|
* @example
|
|
869
873
|
* ```ts
|
|
@@ -872,7 +876,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
872
876
|
*/
|
|
873
877
|
deleteTotp: async (ctx: ComponentCtx, totpId: string) => {
|
|
874
878
|
await ctx.runMutation(config.component.public.totpDelete, { totpId });
|
|
875
|
-
return {
|
|
879
|
+
return { totpId };
|
|
876
880
|
},
|
|
877
881
|
};
|
|
878
882
|
|
|
@@ -954,7 +958,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
954
958
|
* @param data.parentGroupId - Nest under this group. Omit for a root group.
|
|
955
959
|
* @param data.tags - Faceted classification tags (normalized at write time).
|
|
956
960
|
* @param data.extend - Arbitrary app-specific metadata.
|
|
957
|
-
* @returns `{
|
|
961
|
+
* @returns `{ groupId }`.
|
|
958
962
|
*
|
|
959
963
|
* @example Root group
|
|
960
964
|
* ```ts
|
|
@@ -980,12 +984,12 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
980
984
|
tags?: Array<{ key: string; value: string }>;
|
|
981
985
|
extend?: Record<string, unknown>;
|
|
982
986
|
},
|
|
983
|
-
): Promise<{
|
|
987
|
+
): Promise<{ groupId: string }> => {
|
|
984
988
|
const groupId = (await ctx.runMutation(
|
|
985
989
|
config.component.public.groupCreate,
|
|
986
990
|
data,
|
|
987
991
|
)) as string;
|
|
988
|
-
return {
|
|
992
|
+
return { groupId };
|
|
989
993
|
},
|
|
990
994
|
/**
|
|
991
995
|
* Fetch a group document by ID.
|
|
@@ -1074,7 +1078,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1074
1078
|
* @param ctx - Convex mutation context.
|
|
1075
1079
|
* @param groupId - The group's document ID.
|
|
1076
1080
|
* @param data - Fields to merge (e.g. `name`, `slug`, `tags`, `parentGroupId`).
|
|
1077
|
-
* @returns `{
|
|
1081
|
+
* @returns `{ groupId }`.
|
|
1078
1082
|
*
|
|
1079
1083
|
* @example
|
|
1080
1084
|
* ```ts
|
|
@@ -1093,7 +1097,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1093
1097
|
groupId,
|
|
1094
1098
|
data,
|
|
1095
1099
|
});
|
|
1096
|
-
return {
|
|
1100
|
+
return { groupId };
|
|
1097
1101
|
},
|
|
1098
1102
|
/**
|
|
1099
1103
|
* Delete a group and recursively cascade to all descendant groups,
|
|
@@ -1101,7 +1105,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1101
1105
|
*
|
|
1102
1106
|
* @param ctx - Convex mutation context.
|
|
1103
1107
|
* @param groupId - The group's document ID.
|
|
1104
|
-
* @returns `{
|
|
1108
|
+
* @returns `{ groupId }`.
|
|
1105
1109
|
*
|
|
1106
1110
|
* @example
|
|
1107
1111
|
* ```ts
|
|
@@ -1110,7 +1114,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1110
1114
|
*/
|
|
1111
1115
|
delete: async (ctx: ComponentCtx, groupId: string) => {
|
|
1112
1116
|
await ctx.runMutation(config.component.public.groupDelete, { groupId });
|
|
1113
|
-
return {
|
|
1117
|
+
return { groupId };
|
|
1114
1118
|
},
|
|
1115
1119
|
/**
|
|
1116
1120
|
* Walk up the group hierarchy from `groupId` and return all ancestor
|
|
@@ -1176,7 +1180,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1176
1180
|
* Add a user to a group with optional role IDs.
|
|
1177
1181
|
*
|
|
1178
1182
|
* Role IDs are validated against the roles defined in `defineRoles()` —
|
|
1179
|
-
* invalid IDs
|
|
1183
|
+
* invalid IDs throw `INVALID_ROLE_IDS`.
|
|
1180
1184
|
* Throws `DUPLICATE_MEMBERSHIP` if the user is already a member.
|
|
1181
1185
|
*
|
|
1182
1186
|
* @param ctx - Convex mutation context.
|
|
@@ -1185,7 +1189,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1185
1189
|
* @param data.roleIds - Role IDs from `defineRoles()` (optional).
|
|
1186
1190
|
* @param data.status - Membership status string (optional, app-defined).
|
|
1187
1191
|
* @param data.extend - Arbitrary app-specific metadata.
|
|
1188
|
-
* @returns `{
|
|
1192
|
+
* @returns `{ memberId }`.
|
|
1193
|
+
* @throws `INVALID_ROLE_IDS` if any supplied role IDs are not defined.
|
|
1189
1194
|
*
|
|
1190
1195
|
* @example
|
|
1191
1196
|
* ```ts
|
|
@@ -1206,18 +1211,12 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1206
1211
|
extend?: Record<string, unknown>;
|
|
1207
1212
|
},
|
|
1208
1213
|
) => {
|
|
1209
|
-
const
|
|
1210
|
-
if (!normalized.ok)
|
|
1211
|
-
return {
|
|
1212
|
-
ok: false as const,
|
|
1213
|
-
code: "INVALID_ROLE_IDS" as const,
|
|
1214
|
-
invalidRoleIds: normalized.invalidRoleIds,
|
|
1215
|
-
};
|
|
1214
|
+
const roleIds = normalizeRoleIds(data.roleIds);
|
|
1216
1215
|
const memberId = (await ctx.runMutation(
|
|
1217
1216
|
config.component.public.memberAdd,
|
|
1218
|
-
{ ...data, roleIds
|
|
1217
|
+
{ ...data, roleIds },
|
|
1219
1218
|
)) as string;
|
|
1220
|
-
return {
|
|
1219
|
+
return { memberId };
|
|
1221
1220
|
},
|
|
1222
1221
|
/**
|
|
1223
1222
|
* Fetch a membership document by its document ID.
|
|
@@ -1291,7 +1290,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1291
1290
|
*
|
|
1292
1291
|
* @param ctx - Convex mutation context.
|
|
1293
1292
|
* @param memberId - The membership document ID.
|
|
1294
|
-
* @returns `{
|
|
1293
|
+
* @returns `{ memberId }`.
|
|
1295
1294
|
*
|
|
1296
1295
|
* @example
|
|
1297
1296
|
* ```ts
|
|
@@ -1300,7 +1299,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1300
1299
|
*/
|
|
1301
1300
|
delete: async (ctx: ComponentCtx, memberId: string) => {
|
|
1302
1301
|
await ctx.runMutation(config.component.public.memberRemove, { memberId });
|
|
1303
|
-
return {
|
|
1302
|
+
return { memberId };
|
|
1304
1303
|
},
|
|
1305
1304
|
/**
|
|
1306
1305
|
* Patch a membership's `roleIds`, `status`, or `extend` fields.
|
|
@@ -1309,7 +1308,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1309
1308
|
* @param ctx - Convex mutation context.
|
|
1310
1309
|
* @param memberId - The membership document ID.
|
|
1311
1310
|
* @param data - Fields to merge. `roleIds` are validated.
|
|
1312
|
-
* @returns `{
|
|
1311
|
+
* @returns `{ memberId }`.
|
|
1312
|
+
* @throws `INVALID_ROLE_IDS` if any supplied role IDs are not defined.
|
|
1313
1313
|
*
|
|
1314
1314
|
* @example
|
|
1315
1315
|
* ```ts
|
|
@@ -1326,24 +1326,17 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1326
1326
|
) => {
|
|
1327
1327
|
const nextData = { ...data };
|
|
1328
1328
|
if ("roleIds" in nextData) {
|
|
1329
|
-
|
|
1329
|
+
nextData.roleIds = normalizeRoleIds(
|
|
1330
1330
|
Array.isArray(nextData.roleIds)
|
|
1331
1331
|
? (nextData.roleIds as string[])
|
|
1332
1332
|
: undefined,
|
|
1333
1333
|
);
|
|
1334
|
-
if (!normalized.ok)
|
|
1335
|
-
return {
|
|
1336
|
-
ok: false as const,
|
|
1337
|
-
code: "INVALID_ROLE_IDS" as const,
|
|
1338
|
-
invalidRoleIds: normalized.invalidRoleIds,
|
|
1339
|
-
};
|
|
1340
|
-
nextData.roleIds = normalized.roleIds;
|
|
1341
1334
|
}
|
|
1342
1335
|
await ctx.runMutation(config.component.public.memberUpdate, {
|
|
1343
1336
|
memberId,
|
|
1344
1337
|
data: nextData,
|
|
1345
1338
|
});
|
|
1346
|
-
return {
|
|
1339
|
+
return { memberId };
|
|
1347
1340
|
},
|
|
1348
1341
|
/**
|
|
1349
1342
|
* Resolve a user's membership in a group, optionally walking the
|
|
@@ -1364,72 +1357,42 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1364
1357
|
* @param opts.userId - The user's document ID.
|
|
1365
1358
|
* @param opts.groupId - The group to check membership in.
|
|
1366
1359
|
* @param opts.ancestry - Walk the hierarchy (default `false`).
|
|
1367
|
-
* @param opts.grants - Grant strings to check (optional).
|
|
1368
|
-
* @param opts.roleIds - Role IDs to filter by (optional).
|
|
1369
1360
|
* @param opts.maxDepth - Max hierarchy levels (default 32, only with ancestry).
|
|
1370
|
-
* @returns `{
|
|
1371
|
-
* `ok` is `true` when membership exists and all requested grants are satisfied.
|
|
1361
|
+
* @returns `{ membership, roleIds, grants }`.
|
|
1372
1362
|
*
|
|
1373
1363
|
* @example Direct lookup
|
|
1374
1364
|
* ```ts
|
|
1375
|
-
* const result = await auth.member.
|
|
1376
|
-
* if (!result.membership) return
|
|
1365
|
+
* const result = await auth.member.inspect(ctx, { userId, groupId });
|
|
1366
|
+
* if (!result.membership) return null;
|
|
1377
1367
|
* ```
|
|
1378
1368
|
*
|
|
1379
|
-
* @example Check grants
|
|
1369
|
+
* @example Check grants after inspection
|
|
1380
1370
|
* ```ts
|
|
1381
|
-
* const result = await auth.member.
|
|
1382
|
-
* userId, groupId,
|
|
1371
|
+
* const result = await auth.member.inspect(ctx, {
|
|
1372
|
+
* userId, groupId,
|
|
1383
1373
|
* });
|
|
1384
|
-
*
|
|
1374
|
+
* const canCreate = result.grants.includes("issues.create");
|
|
1385
1375
|
* ```
|
|
1386
1376
|
*
|
|
1387
1377
|
* @example Walk hierarchy + check grants
|
|
1388
1378
|
* ```ts
|
|
1389
|
-
* const result = await auth.member.
|
|
1390
|
-
* userId, groupId: teamId, ancestry: true,
|
|
1379
|
+
* const result = await auth.member.inspect(ctx, {
|
|
1380
|
+
* userId, groupId: teamId, ancestry: true,
|
|
1391
1381
|
* });
|
|
1392
1382
|
* ```
|
|
1393
1383
|
*/
|
|
1394
|
-
|
|
1384
|
+
inspect: async (
|
|
1395
1385
|
ctx: ComponentReadCtx,
|
|
1396
1386
|
opts: {
|
|
1397
1387
|
userId: string;
|
|
1398
1388
|
groupId: string;
|
|
1399
1389
|
ancestry?: boolean;
|
|
1400
|
-
roleIds?: string[];
|
|
1401
|
-
grants?: string[];
|
|
1402
1390
|
maxDepth?: number;
|
|
1403
1391
|
},
|
|
1404
1392
|
) => {
|
|
1405
|
-
const normalized = normalizeRoleIds(opts.roleIds);
|
|
1406
|
-
if (!normalized.ok)
|
|
1407
|
-
return {
|
|
1408
|
-
ok: false as const,
|
|
1409
|
-
membership: null,
|
|
1410
|
-
matchedGroupId: null,
|
|
1411
|
-
roleIds: [] as string[],
|
|
1412
|
-
grants: [] as string[],
|
|
1413
|
-
missingGrants: Array.from(new Set(opts.grants ?? [])),
|
|
1414
|
-
depth: null,
|
|
1415
|
-
isDirect: false,
|
|
1416
|
-
isInherited: false,
|
|
1417
|
-
traversedGroupIds: [] as string[],
|
|
1418
|
-
code: "INVALID_ROLE_IDS" as const,
|
|
1419
|
-
invalidRoleIds: normalized.invalidRoleIds,
|
|
1420
|
-
};
|
|
1421
|
-
const requestedRoleIds = normalized.roleIds;
|
|
1422
|
-
const roleFilter =
|
|
1423
|
-
requestedRoleIds.length > 0 ? new Set(requestedRoleIds) : null;
|
|
1424
|
-
const requiredGrants = Array.from(new Set(opts.grants ?? []));
|
|
1425
1393
|
const useAncestry = opts.ancestry === true;
|
|
1426
1394
|
|
|
1427
1395
|
let membership: any = null;
|
|
1428
|
-
let matchedGroupId: string | null = null;
|
|
1429
|
-
let depth: number | null = null;
|
|
1430
|
-
let isDirect = false;
|
|
1431
|
-
let isInherited = false;
|
|
1432
|
-
let traversedGroupIds: string[] = [];
|
|
1433
1396
|
|
|
1434
1397
|
if (useAncestry) {
|
|
1435
1398
|
// Hierarchy walk — single component RPC
|
|
@@ -1444,11 +1407,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1444
1407
|
},
|
|
1445
1408
|
);
|
|
1446
1409
|
membership = result.membership;
|
|
1447
|
-
matchedGroupId = result.matchedGroupId;
|
|
1448
|
-
depth = result.depth;
|
|
1449
|
-
isDirect = result.isDirect;
|
|
1450
|
-
isInherited = result.isInherited;
|
|
1451
|
-
traversedGroupIds = result.traversedGroupIds ?? [];
|
|
1452
1410
|
} else {
|
|
1453
1411
|
// Fast path — direct lookup, 1 read
|
|
1454
1412
|
const doc = await ctx.runQuery(
|
|
@@ -1456,64 +1414,75 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1456
1414
|
{ userId: opts.userId, groupId: opts.groupId },
|
|
1457
1415
|
);
|
|
1458
1416
|
membership = doc;
|
|
1459
|
-
matchedGroupId = doc ? opts.groupId : null;
|
|
1460
|
-
depth = doc ? 0 : null;
|
|
1461
|
-
isDirect = doc !== null;
|
|
1462
1417
|
}
|
|
1463
1418
|
|
|
1464
1419
|
if (membership === null) {
|
|
1465
1420
|
return {
|
|
1466
|
-
ok: false as const,
|
|
1467
1421
|
membership: null,
|
|
1468
|
-
matchedGroupId: null,
|
|
1469
1422
|
roleIds: [] as string[],
|
|
1470
1423
|
grants: [] as string[],
|
|
1471
|
-
missingGrants: requiredGrants,
|
|
1472
|
-
depth: null,
|
|
1473
|
-
isDirect: false,
|
|
1474
|
-
isInherited: false,
|
|
1475
|
-
traversedGroupIds,
|
|
1476
1424
|
};
|
|
1477
1425
|
}
|
|
1478
1426
|
|
|
1479
1427
|
const membershipRoleIds = membership.roleIds ?? [];
|
|
1480
1428
|
const membershipGrants = resolveGrantedPermissions(membershipRoleIds);
|
|
1481
1429
|
|
|
1482
|
-
|
|
1430
|
+
return {
|
|
1431
|
+
membership,
|
|
1432
|
+
roleIds: membershipRoleIds,
|
|
1433
|
+
grants: membershipGrants,
|
|
1434
|
+
};
|
|
1435
|
+
},
|
|
1436
|
+
require: async (
|
|
1437
|
+
ctx: ComponentReadCtx,
|
|
1438
|
+
opts: {
|
|
1439
|
+
userId: string;
|
|
1440
|
+
groupId: string;
|
|
1441
|
+
ancestry?: boolean;
|
|
1442
|
+
roleIds?: string[];
|
|
1443
|
+
grants?: string[];
|
|
1444
|
+
maxDepth?: number;
|
|
1445
|
+
},
|
|
1446
|
+
) => {
|
|
1447
|
+
const validatedRoleIds = normalizeRoleIds(opts.roleIds);
|
|
1448
|
+
const requiredGrants = Array.from(new Set(opts.grants ?? []));
|
|
1449
|
+
const roleFilter =
|
|
1450
|
+
validatedRoleIds.length > 0 ? new Set(validatedRoleIds) : null;
|
|
1451
|
+
const result = await member.inspect(ctx, {
|
|
1452
|
+
userId: opts.userId,
|
|
1453
|
+
groupId: opts.groupId,
|
|
1454
|
+
ancestry: opts.ancestry,
|
|
1455
|
+
maxDepth: opts.maxDepth,
|
|
1456
|
+
});
|
|
1457
|
+
if (result.membership === null) {
|
|
1458
|
+
throw Cv.error({
|
|
1459
|
+
code: "NOT_A_MEMBER",
|
|
1460
|
+
message: "User is not a member of this group.",
|
|
1461
|
+
groupId: opts.groupId,
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1483
1464
|
if (
|
|
1484
1465
|
roleFilter !== null &&
|
|
1485
|
-
!
|
|
1466
|
+
!result.roleIds.some((roleId: string) => roleFilter.has(roleId))
|
|
1486
1467
|
) {
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
grants: [] as string[],
|
|
1493
|
-
missingGrants: requiredGrants,
|
|
1494
|
-
depth: null,
|
|
1495
|
-
isDirect: false,
|
|
1496
|
-
isInherited: false,
|
|
1497
|
-
traversedGroupIds,
|
|
1498
|
-
};
|
|
1468
|
+
throw Cv.error({
|
|
1469
|
+
code: "NOT_A_MEMBER",
|
|
1470
|
+
message: "User is not a member of this group.",
|
|
1471
|
+
groupId: opts.groupId,
|
|
1472
|
+
});
|
|
1499
1473
|
}
|
|
1500
|
-
|
|
1501
1474
|
const missingGrants = requiredGrants.filter(
|
|
1502
|
-
(grant) => !
|
|
1475
|
+
(grant) => !result.grants.includes(grant),
|
|
1503
1476
|
);
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
isDirect,
|
|
1514
|
-
isInherited,
|
|
1515
|
-
traversedGroupIds,
|
|
1516
|
-
};
|
|
1477
|
+
if (missingGrants.length > 0) {
|
|
1478
|
+
throw Cv.error({
|
|
1479
|
+
code: "MISSING_GRANTS",
|
|
1480
|
+
message: "User is missing required grants.",
|
|
1481
|
+
groupId: opts.groupId,
|
|
1482
|
+
missingGrants,
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
return result;
|
|
1517
1486
|
},
|
|
1518
1487
|
};
|
|
1519
1488
|
|
|
@@ -1529,7 +1498,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1529
1498
|
* @param data.roleIds - Role IDs from `defineRoles()` to assign on acceptance (optional).
|
|
1530
1499
|
* @param data.expiresTime - Expiration timestamp in ms since epoch (optional).
|
|
1531
1500
|
* @param data.extend - Arbitrary app-specific metadata (optional).
|
|
1532
|
-
* @returns `{
|
|
1501
|
+
* @returns `{ inviteId, token }`.
|
|
1502
|
+
* @throws `INVALID_ROLE_IDS` if any supplied role IDs are not defined.
|
|
1533
1503
|
*
|
|
1534
1504
|
* @example
|
|
1535
1505
|
* ```ts
|
|
@@ -1549,13 +1519,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1549
1519
|
extend?: Record<string, unknown>;
|
|
1550
1520
|
},
|
|
1551
1521
|
) => {
|
|
1552
|
-
const
|
|
1553
|
-
if (!normalized.ok)
|
|
1554
|
-
return {
|
|
1555
|
-
ok: false as const,
|
|
1556
|
-
code: "INVALID_ROLE_IDS" as const,
|
|
1557
|
-
invalidRoleIds: normalized.invalidRoleIds,
|
|
1558
|
-
};
|
|
1522
|
+
const roleIds = normalizeRoleIds(data.roleIds);
|
|
1559
1523
|
const token = generateRandomString(
|
|
1560
1524
|
inviteTokenLength,
|
|
1561
1525
|
inviteTokenAlphabet,
|
|
@@ -1563,9 +1527,9 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1563
1527
|
const tokenHash = await sha256(token);
|
|
1564
1528
|
const inviteId = (await ctx.runMutation(
|
|
1565
1529
|
config.component.public.inviteCreate,
|
|
1566
|
-
{ ...data, roleIds
|
|
1530
|
+
{ ...data, roleIds, tokenHash, status: "pending" },
|
|
1567
1531
|
)) as string;
|
|
1568
|
-
return {
|
|
1532
|
+
return { inviteId, token };
|
|
1569
1533
|
},
|
|
1570
1534
|
/**
|
|
1571
1535
|
* Fetch an invite document by ID.
|
|
@@ -1628,7 +1592,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1628
1592
|
* @param ctx - Convex mutation context.
|
|
1629
1593
|
* @param args.token - The raw invite token string.
|
|
1630
1594
|
* @param args.acceptedByUserId - The user accepting the invite.
|
|
1631
|
-
* @returns
|
|
1595
|
+
* @returns The created membership details.
|
|
1632
1596
|
*
|
|
1633
1597
|
* @example
|
|
1634
1598
|
* ```ts
|
|
@@ -1647,7 +1611,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1647
1611
|
config.component.public.inviteAcceptByToken,
|
|
1648
1612
|
{ tokenHash, acceptedByUserId: args.acceptedByUserId },
|
|
1649
1613
|
);
|
|
1650
|
-
return {
|
|
1614
|
+
return { ...result };
|
|
1651
1615
|
},
|
|
1652
1616
|
},
|
|
1653
1617
|
/**
|
|
@@ -1715,7 +1679,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1715
1679
|
* @param ctx - Convex mutation context.
|
|
1716
1680
|
* @param inviteId - The invite's document ID.
|
|
1717
1681
|
* @param acceptedByUserId - The user who accepted the invite (optional).
|
|
1718
|
-
* @returns `{
|
|
1682
|
+
* @returns `{ inviteId, acceptedByUserId }`.
|
|
1719
1683
|
*
|
|
1720
1684
|
* @example
|
|
1721
1685
|
* ```ts
|
|
@@ -1732,7 +1696,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1732
1696
|
...(acceptedByUserId ? { acceptedByUserId } : {}),
|
|
1733
1697
|
});
|
|
1734
1698
|
return {
|
|
1735
|
-
ok: true as const,
|
|
1736
1699
|
inviteId,
|
|
1737
1700
|
acceptedByUserId: acceptedByUserId ?? null,
|
|
1738
1701
|
};
|
|
@@ -1745,7 +1708,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1745
1708
|
*
|
|
1746
1709
|
* @param ctx - Convex mutation context.
|
|
1747
1710
|
* @param inviteId - The invite's document ID.
|
|
1748
|
-
* @returns `{
|
|
1711
|
+
* @returns `{ inviteId }`.
|
|
1749
1712
|
*
|
|
1750
1713
|
* @example
|
|
1751
1714
|
* ```ts
|
|
@@ -1754,7 +1717,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1754
1717
|
*/
|
|
1755
1718
|
revoke: async (ctx: ComponentCtx, inviteId: string) => {
|
|
1756
1719
|
await ctx.runMutation(config.component.public.inviteRevoke, { inviteId });
|
|
1757
|
-
return {
|
|
1720
|
+
return { inviteId };
|
|
1758
1721
|
},
|
|
1759
1722
|
};
|
|
1760
1723
|
|
|
@@ -1770,7 +1733,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1770
1733
|
* @param opts.rateLimit - Optional per-key rate limit `{ maxRequests, windowMs }`.
|
|
1771
1734
|
* @param opts.expiresAt - Optional expiration timestamp (ms since epoch).
|
|
1772
1735
|
* @param opts.metadata - Arbitrary app-specific metadata.
|
|
1773
|
-
* @returns `{
|
|
1736
|
+
* @returns `{ keyId, secret }`. Store `secret` securely — it cannot be retrieved later.
|
|
1774
1737
|
*
|
|
1775
1738
|
* @example
|
|
1776
1739
|
* ```ts
|
|
@@ -1791,7 +1754,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1791
1754
|
expiresAt?: number;
|
|
1792
1755
|
metadata?: Record<string, unknown>;
|
|
1793
1756
|
},
|
|
1794
|
-
): Promise<{
|
|
1757
|
+
): Promise<{ keyId: string; secret: string }> => {
|
|
1795
1758
|
const { raw, hashedKey, displayPrefix } = await generateApiKey("sk_");
|
|
1796
1759
|
const keyId = (await ctx.runMutation(config.component.public.keyInsert, {
|
|
1797
1760
|
userId: opts.userId,
|
|
@@ -1803,7 +1766,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1803
1766
|
expiresAt: opts.expiresAt,
|
|
1804
1767
|
metadata: opts.metadata,
|
|
1805
1768
|
})) as string;
|
|
1806
|
-
return {
|
|
1769
|
+
return { keyId, secret: raw };
|
|
1807
1770
|
},
|
|
1808
1771
|
/**
|
|
1809
1772
|
* Verify an API key and return the owner's identity and scopes.
|
|
@@ -1813,48 +1776,45 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1813
1776
|
*
|
|
1814
1777
|
* @param ctx - Convex mutation context (updates `lastUsedAt` and rate limit state).
|
|
1815
1778
|
* @param rawKey - The raw `sk_*` key string.
|
|
1816
|
-
* @returns
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1821
|
-
* - `"API_KEY_RATE_LIMITED"` — rate limit exceeded.
|
|
1779
|
+
* @returns `{ userId, keyId, scopes }` where `scopes.can(resource, action)` checks permissions.
|
|
1780
|
+
* @throws `INVALID_API_KEY` if the key is not found.
|
|
1781
|
+
* @throws `API_KEY_REVOKED` if the key was revoked.
|
|
1782
|
+
* @throws `API_KEY_EXPIRED` if the key is past its `expiresAt`.
|
|
1783
|
+
* @throws `API_KEY_RATE_LIMITED` if the rate limit is exceeded.
|
|
1822
1784
|
*
|
|
1823
1785
|
* @example
|
|
1824
1786
|
* ```ts
|
|
1825
|
-
* const
|
|
1826
|
-
*
|
|
1827
|
-
* const canRead = result.scopes.can("data", "read");
|
|
1787
|
+
* const { userId, scopes } = await auth.key.verify(ctx, rawKey);
|
|
1788
|
+
* const canRead = scopes.can("data", "read");
|
|
1828
1789
|
* ```
|
|
1829
1790
|
*/
|
|
1830
1791
|
verify: async (
|
|
1831
1792
|
ctx: ComponentCtx,
|
|
1832
1793
|
rawKey: string,
|
|
1833
|
-
): Promise<
|
|
1834
|
-
| { ok: true; userId: string; keyId: string; scopes: ScopeChecker }
|
|
1835
|
-
| {
|
|
1836
|
-
ok: false;
|
|
1837
|
-
code:
|
|
1838
|
-
| "INVALID_API_KEY"
|
|
1839
|
-
| "API_KEY_REVOKED"
|
|
1840
|
-
| "API_KEY_EXPIRED"
|
|
1841
|
-
| "API_KEY_RATE_LIMITED";
|
|
1842
|
-
}
|
|
1843
|
-
> => {
|
|
1794
|
+
): Promise<{ userId: string; keyId: string; scopes: ScopeChecker }> => {
|
|
1844
1795
|
const hashedKey = await hashApiKey(rawKey);
|
|
1845
1796
|
const doc = (await ctx.runQuery(
|
|
1846
1797
|
config.component.public.keyGetByHashedKey,
|
|
1847
1798
|
{ hashedKey },
|
|
1848
1799
|
)) as KeyDoc | null;
|
|
1849
1800
|
if (!doc) {
|
|
1850
|
-
|
|
1801
|
+
throw Cv.error({
|
|
1802
|
+
code: "INVALID_API_KEY",
|
|
1803
|
+
message: "Invalid API key.",
|
|
1804
|
+
});
|
|
1851
1805
|
}
|
|
1852
1806
|
const k = doc;
|
|
1853
1807
|
if (k.revoked) {
|
|
1854
|
-
|
|
1808
|
+
throw Cv.error({
|
|
1809
|
+
code: "API_KEY_REVOKED",
|
|
1810
|
+
message: "This API key has been revoked.",
|
|
1811
|
+
});
|
|
1855
1812
|
}
|
|
1856
1813
|
if (k.expiresAt && k.expiresAt < Date.now()) {
|
|
1857
|
-
|
|
1814
|
+
throw Cv.error({
|
|
1815
|
+
code: "API_KEY_EXPIRED",
|
|
1816
|
+
message: "This API key has expired.",
|
|
1817
|
+
});
|
|
1858
1818
|
}
|
|
1859
1819
|
const patchData: Record<string, unknown> = { lastUsedAt: Date.now() };
|
|
1860
1820
|
if (k.rateLimit) {
|
|
@@ -1863,7 +1823,10 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1863
1823
|
k.rateLimitState ?? undefined,
|
|
1864
1824
|
);
|
|
1865
1825
|
if (limited) {
|
|
1866
|
-
|
|
1826
|
+
throw Cv.error({
|
|
1827
|
+
code: "API_KEY_RATE_LIMITED",
|
|
1828
|
+
message: "API key rate limit exceeded. Please try again later.",
|
|
1829
|
+
});
|
|
1867
1830
|
}
|
|
1868
1831
|
patchData.rateLimitState = newState;
|
|
1869
1832
|
}
|
|
@@ -1872,7 +1835,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1872
1835
|
data: patchData,
|
|
1873
1836
|
});
|
|
1874
1837
|
return {
|
|
1875
|
-
ok: true as const,
|
|
1876
1838
|
userId: k.userId,
|
|
1877
1839
|
keyId: k._id,
|
|
1878
1840
|
scopes: buildScopeChecker(k.scopes),
|
|
@@ -1936,24 +1898,23 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1936
1898
|
*
|
|
1937
1899
|
* @param ctx - Convex query or mutation context.
|
|
1938
1900
|
* @param keyId - The API key's document ID.
|
|
1939
|
-
* @returns
|
|
1901
|
+
* @returns The key document, or `null` if not found.
|
|
1940
1902
|
*
|
|
1941
1903
|
* @example
|
|
1942
1904
|
* ```ts
|
|
1943
|
-
* const
|
|
1944
|
-
* if (!
|
|
1945
|
-
* console.log(
|
|
1905
|
+
* const key = await auth.key.get(ctx, keyId);
|
|
1906
|
+
* if (!key) throw new Error("Key not found");
|
|
1907
|
+
* console.log(key.name, key.prefix);
|
|
1946
1908
|
* ```
|
|
1947
1909
|
*/
|
|
1948
1910
|
get: async (
|
|
1949
1911
|
ctx: ComponentReadCtx,
|
|
1950
1912
|
keyId: string,
|
|
1951
|
-
): Promise<
|
|
1913
|
+
): Promise<KeyDoc | null> => {
|
|
1952
1914
|
const doc = (await ctx.runQuery(config.component.public.keyGetById, {
|
|
1953
1915
|
keyId,
|
|
1954
1916
|
})) as KeyDoc | null;
|
|
1955
|
-
|
|
1956
|
-
return { ok: true as const, key: doc };
|
|
1917
|
+
return doc ?? null;
|
|
1957
1918
|
},
|
|
1958
1919
|
/**
|
|
1959
1920
|
* Update a key's name, scopes, or rate limit.
|
|
@@ -1964,7 +1925,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1964
1925
|
* @param ctx - Convex mutation context.
|
|
1965
1926
|
* @param keyId - The API key's document ID.
|
|
1966
1927
|
* @param data - Fields to merge into the key document.
|
|
1967
|
-
* @returns `{
|
|
1928
|
+
* @returns `{ keyId }`.
|
|
1968
1929
|
*
|
|
1969
1930
|
* @example
|
|
1970
1931
|
* ```ts
|
|
@@ -1984,18 +1945,18 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1984
1945
|
},
|
|
1985
1946
|
) => {
|
|
1986
1947
|
await ctx.runMutation(config.component.public.keyPatch, { keyId, data });
|
|
1987
|
-
return {
|
|
1948
|
+
return { keyId };
|
|
1988
1949
|
},
|
|
1989
1950
|
/**
|
|
1990
1951
|
* Soft-delete: set `revoked: true`. The key can no longer be verified.
|
|
1991
1952
|
*
|
|
1992
1953
|
* After revocation, any subsequent calls to `auth.key.verify` with
|
|
1993
|
-
* this key will
|
|
1954
|
+
* this key will throw `API_KEY_REVOKED`.
|
|
1994
1955
|
* The key record is preserved for audit purposes.
|
|
1995
1956
|
*
|
|
1996
1957
|
* @param ctx - Convex mutation context.
|
|
1997
1958
|
* @param keyId - The API key's document ID.
|
|
1998
|
-
* @returns `{
|
|
1959
|
+
* @returns `{ keyId }`.
|
|
1999
1960
|
*
|
|
2000
1961
|
* @example
|
|
2001
1962
|
* ```ts
|
|
@@ -2007,7 +1968,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
2007
1968
|
keyId,
|
|
2008
1969
|
data: { revoked: true },
|
|
2009
1970
|
});
|
|
2010
|
-
return {
|
|
1971
|
+
return { keyId };
|
|
2011
1972
|
},
|
|
2012
1973
|
/**
|
|
2013
1974
|
* Hard-delete: permanently remove the key record.
|
|
@@ -2018,7 +1979,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
2018
1979
|
*
|
|
2019
1980
|
* @param ctx - Convex mutation context.
|
|
2020
1981
|
* @param keyId - The API key's document ID.
|
|
2021
|
-
* @returns `{
|
|
1982
|
+
* @returns `{ keyId }`.
|
|
2022
1983
|
*
|
|
2023
1984
|
* @example
|
|
2024
1985
|
* ```ts
|
|
@@ -2027,45 +1988,48 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
2027
1988
|
*/
|
|
2028
1989
|
delete: async (ctx: ComponentCtx, keyId: string) => {
|
|
2029
1990
|
await ctx.runMutation(config.component.public.keyDelete, { keyId });
|
|
2030
|
-
return {
|
|
1991
|
+
return { keyId };
|
|
2031
1992
|
},
|
|
2032
1993
|
/**
|
|
2033
1994
|
* Rotate a key: revokes the old key and creates a new one with the
|
|
2034
1995
|
* same user, scopes, and rate limit. Returns the new `keyId` and `secret`.
|
|
2035
|
-
*
|
|
1996
|
+
* Throws if the key does not exist or is already revoked.
|
|
2036
1997
|
*
|
|
2037
1998
|
* @param ctx - Convex mutation context.
|
|
2038
1999
|
* @param keyId - The existing API key's document ID to rotate.
|
|
2039
2000
|
* @param opts.name - Optional new name for the rotated key (defaults to the old name).
|
|
2040
2001
|
* @param opts.expiresAt - Optional new expiration timestamp in ms since epoch.
|
|
2041
|
-
* @returns `{
|
|
2002
|
+
* @returns `{ keyId, secret }` with the new key.
|
|
2003
|
+
* @throws `INVALID_PARAMETERS` if the key does not exist.
|
|
2004
|
+
* @throws `API_KEY_REVOKED` if the key is already revoked.
|
|
2042
2005
|
*
|
|
2043
2006
|
* @example
|
|
2044
2007
|
* ```ts
|
|
2045
|
-
* const
|
|
2008
|
+
* const { keyId, secret } = await auth.key.rotate(ctx, oldKeyId, {
|
|
2046
2009
|
* expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
2047
2010
|
* });
|
|
2048
|
-
*
|
|
2049
|
-
* // Store result.secret securely — shown only once
|
|
2050
|
-
* }
|
|
2011
|
+
* // Store secret securely — shown only once
|
|
2051
2012
|
* ```
|
|
2052
2013
|
*/
|
|
2053
2014
|
rotate: async (
|
|
2054
2015
|
ctx: ComponentCtx,
|
|
2055
2016
|
keyId: string,
|
|
2056
2017
|
opts?: { name?: string; expiresAt?: number },
|
|
2057
|
-
): Promise<
|
|
2058
|
-
| { ok: true; keyId: string; secret: string }
|
|
2059
|
-
| { ok: false; code: "INVALID_PARAMETERS" | "API_KEY_REVOKED" }
|
|
2060
|
-
> => {
|
|
2018
|
+
): Promise<{ keyId: string; secret: string }> => {
|
|
2061
2019
|
const existing = await ctx.runQuery(config.component.public.keyGetById, {
|
|
2062
2020
|
keyId,
|
|
2063
2021
|
});
|
|
2064
2022
|
if (!existing) {
|
|
2065
|
-
|
|
2023
|
+
throw Cv.error({
|
|
2024
|
+
code: "INVALID_PARAMETERS",
|
|
2025
|
+
message: "The provided parameters are invalid.",
|
|
2026
|
+
});
|
|
2066
2027
|
}
|
|
2067
2028
|
if ((existing as any).revoked === true) {
|
|
2068
|
-
|
|
2029
|
+
throw Cv.error({
|
|
2030
|
+
code: "API_KEY_REVOKED",
|
|
2031
|
+
message: "This API key has been revoked.",
|
|
2032
|
+
});
|
|
2069
2033
|
}
|
|
2070
2034
|
await ctx.runMutation(config.component.public.keyPatch, {
|
|
2071
2035
|
keyId,
|