@robelest/convex-auth 0.0.4-preview.22 → 0.0.4-preview.24
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/README.md +10 -11
- 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/index.js +2 -2
- package/dist/component/model.d.ts +9 -9
- 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 +41 -41
- package/dist/component/server/auth.d.ts +127 -130
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +100 -64
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/context.js +53 -0
- package/dist/component/server/context.js.map +1 -0
- package/dist/component/server/core.js +113 -250
- 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 +59 -16
- 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.d.ts +85 -0
- package/dist/component/server/http.d.ts.map +1 -0
- package/dist/component/server/http.js +85 -22
- 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 +5 -5
- package/dist/component/server/runtime.js +156 -113
- 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 +127 -130
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +100 -64
- package/dist/server/auth.js.map +1 -1
- package/dist/server/context.d.ts +1 -0
- package/dist/server/context.js +53 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/core.d.ts +74 -195
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +113 -250
- 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 +59 -16
- 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 +81 -3
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +84 -21
- 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 +3 -2
- package/dist/server/index.js +2 -2
- package/dist/server/limits.js +21 -30
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mounts.d.ts +25 -63
- package/dist/server/mounts.d.ts.map +1 -1
- package/dist/server/mounts.js +46 -107
- 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 +12 -12
- 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 +14 -12
- 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/signout.js.map +1 -1
- package/dist/server/mutations/store.d.ts +83 -83
- 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 +7 -7
- 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 +11 -11
- package/dist/server/runtime.js +155 -112
- 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 +1 -5
- package/src/authorization/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 +9 -3
- 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 +240 -182
- package/src/server/context.ts +90 -0
- package/src/server/core.ts +195 -286
- 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 +289 -30
- package/src/server/identity.ts +5 -5
- package/src/server/index.ts +9 -3
- package/src/server/limits.ts +53 -80
- package/src/server/mounts.ts +56 -80
- 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 +340 -302
- 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,15 @@
|
|
|
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";
|
|
6
|
+
import { getSessionUserId } from "./context";
|
|
4
7
|
import {
|
|
5
8
|
buildScopeChecker,
|
|
6
9
|
checkKeyRateLimit,
|
|
7
10
|
generateApiKey,
|
|
8
11
|
hashApiKey,
|
|
9
12
|
} from "./keys";
|
|
10
|
-
import { materializeProvider } from "./config";
|
|
11
13
|
import { signInImpl } from "./signin";
|
|
12
14
|
import type {
|
|
13
15
|
AuthProviderConfig,
|
|
@@ -17,11 +19,7 @@ import type {
|
|
|
17
19
|
UserOrderBy,
|
|
18
20
|
UserWhere,
|
|
19
21
|
} from "./types";
|
|
20
|
-
import {
|
|
21
|
-
generateRandomString,
|
|
22
|
-
sha256,
|
|
23
|
-
TOKEN_SUB_CLAIM_DIVIDER,
|
|
24
|
-
} from "./utils";
|
|
22
|
+
import { generateRandomString, sha256, TOKEN_SUB_CLAIM_DIVIDER } from "./utils";
|
|
25
23
|
|
|
26
24
|
type ComponentCtx = Pick<
|
|
27
25
|
GenericActionCtx<GenericDataModel>,
|
|
@@ -104,17 +102,17 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
104
102
|
return roleDefinitions[roleId] ?? null;
|
|
105
103
|
};
|
|
106
104
|
|
|
107
|
-
const normalizeRoleIds = (
|
|
108
|
-
roleIds?: string[],
|
|
109
|
-
):
|
|
110
|
-
| { ok: true; roleIds: string[] }
|
|
111
|
-
| { ok: false; invalidRoleIds: string[] } => {
|
|
105
|
+
const normalizeRoleIds = (roleIds?: string[]): string[] => {
|
|
112
106
|
const normalized = Array.from(new Set(roleIds ?? []));
|
|
113
107
|
const invalid = normalized.filter((id) => getRoleDefinition(id) === null);
|
|
114
108
|
if (invalid.length > 0) {
|
|
115
|
-
|
|
109
|
+
throw Cv.error({
|
|
110
|
+
code: "INVALID_ROLE_IDS",
|
|
111
|
+
message: "One or more role IDs are invalid.",
|
|
112
|
+
invalidRoleIds: invalid,
|
|
113
|
+
});
|
|
116
114
|
}
|
|
117
|
-
return
|
|
115
|
+
return normalized;
|
|
118
116
|
};
|
|
119
117
|
|
|
120
118
|
const listAllKeysByUser = async (ctx: ComponentCtx, userId: string) => {
|
|
@@ -184,61 +182,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
184
182
|
|
|
185
183
|
const user = {
|
|
186
184
|
/**
|
|
187
|
-
|
|
188
|
-
*
|
|
189
|
-
* Checks two sources in order:
|
|
190
|
-
*
|
|
191
|
-
* 1. **Session JWT** — extracts the `userId` from `ctx.auth.getUserIdentity()`.
|
|
192
|
-
* This is the standard path for browser sessions and costs zero DB reads.
|
|
193
|
-
* 2. **API key** — if a `request` is provided and contains a
|
|
194
|
-
* `Bearer sk_*` Authorization header, the key is verified against the
|
|
195
|
-
* database and the owning `userId` is returned.
|
|
196
|
-
*
|
|
197
|
-
* Returns `null` when neither source produces a valid identity.
|
|
198
|
-
*
|
|
199
|
-
* @param ctx - Convex query, mutation, or action context.
|
|
200
|
-
* @param request - Optional incoming `Request` to check for API key auth.
|
|
201
|
-
* Only needed in HTTP actions or server-side handlers.
|
|
202
|
-
* @returns The user's document ID, or `null` if unauthenticated.
|
|
203
|
-
*
|
|
204
|
-
* @example Session auth (queries, mutations)
|
|
205
|
-
* ```ts
|
|
206
|
-
* const userId = await auth.user.id(ctx);
|
|
207
|
-
* if (!userId) return { ok: false, code: "NOT_SIGNED_IN" };
|
|
208
|
-
* ```
|
|
209
|
-
*
|
|
210
|
-
* @example API key auth (HTTP actions)
|
|
211
|
-
* ```ts
|
|
212
|
-
* const userId = await auth.user.id(ctx, request);
|
|
213
|
-
* ```
|
|
214
|
-
*/
|
|
215
|
-
id: async (
|
|
216
|
-
ctx: { auth: Auth } & Partial<ComponentCtx>,
|
|
217
|
-
request?: Request,
|
|
218
|
-
): Promise<string | null> => {
|
|
219
|
-
const identity = await ctx.auth.getUserIdentity();
|
|
220
|
-
if (identity !== null) {
|
|
221
|
-
const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
|
|
222
|
-
return userId;
|
|
223
|
-
}
|
|
224
|
-
if (request !== undefined && "runMutation" in ctx && ctx.runMutation) {
|
|
225
|
-
const authHeader = request.headers.get("Authorization");
|
|
226
|
-
if (authHeader?.startsWith("Bearer sk_")) {
|
|
227
|
-
const rawKey = authHeader.slice(7);
|
|
228
|
-
const result = await getAuth().key.verify(
|
|
229
|
-
ctx as ComponentCtx,
|
|
230
|
-
rawKey,
|
|
231
|
-
);
|
|
232
|
-
if (result.ok) {
|
|
233
|
-
return result.userId;
|
|
234
|
-
}
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return null;
|
|
239
|
-
},
|
|
240
|
-
/**
|
|
241
|
-
* Fetch a user document by ID.
|
|
185
|
+
* Fetch a user document by ID.
|
|
242
186
|
*
|
|
243
187
|
* Results are **cached per-execution** — calling `auth.user.get(ctx, id)`
|
|
244
188
|
* multiple times within the same query or mutation handler for the same
|
|
@@ -299,9 +243,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
299
243
|
return await ctx.runQuery(config.component.public.userList, opts);
|
|
300
244
|
},
|
|
301
245
|
/**
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
* unauthenticated. Equivalent to `auth.user.id(ctx)` then `auth.user.get(ctx, id)`.
|
|
246
|
+
* Convenience method: resolve the current session user and fetch their
|
|
247
|
+
* full document in one call. Returns `null` if unauthenticated.
|
|
305
248
|
*
|
|
306
249
|
* @param ctx - Convex query or mutation context with `auth` for session lookup.
|
|
307
250
|
* @returns The authenticated user's document, or `null` if unauthenticated.
|
|
@@ -314,7 +257,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
314
257
|
* ```
|
|
315
258
|
*/
|
|
316
259
|
viewer: async (ctx: ComponentAuthReadCtx) => {
|
|
317
|
-
const userId = await
|
|
260
|
+
const userId = await getSessionUserId(ctx);
|
|
318
261
|
if (userId === null) return null;
|
|
319
262
|
return await user.get(ctx, userId);
|
|
320
263
|
},
|
|
@@ -325,7 +268,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
325
268
|
* @param ctx - Convex mutation context.
|
|
326
269
|
* @param userId - The user's document ID.
|
|
327
270
|
* @param data - Fields to merge into the user document.
|
|
328
|
-
* @returns `{
|
|
271
|
+
* @returns `{ userId }`.
|
|
329
272
|
*
|
|
330
273
|
* @example
|
|
331
274
|
* ```ts
|
|
@@ -344,7 +287,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
344
287
|
userId,
|
|
345
288
|
data,
|
|
346
289
|
});
|
|
347
|
-
return {
|
|
290
|
+
return { userId };
|
|
348
291
|
},
|
|
349
292
|
/**
|
|
350
293
|
* Set the user's active group. Stored in `user.extend.lastActiveGroup`.
|
|
@@ -354,7 +297,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
354
297
|
* @param ctx - Convex mutation context.
|
|
355
298
|
* @param opts.userId - The user's document ID.
|
|
356
299
|
* @param opts.groupId - Group ID to set as active, or `null` to clear.
|
|
357
|
-
* @returns `{
|
|
300
|
+
* @returns `{ userId, groupId }` confirming the active group was set (or cleared).
|
|
358
301
|
*
|
|
359
302
|
* @example
|
|
360
303
|
* ```ts
|
|
@@ -380,12 +323,12 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
380
323
|
if (opts.groupId === null) {
|
|
381
324
|
const { lastActiveGroup: _omit, ...rest } = existingExtend;
|
|
382
325
|
await user.update(ctx, opts.userId, { extend: rest });
|
|
383
|
-
return {
|
|
326
|
+
return { userId: opts.userId, groupId: null };
|
|
384
327
|
}
|
|
385
328
|
await user.update(ctx, opts.userId, {
|
|
386
329
|
extend: { ...existingExtend, lastActiveGroup: opts.groupId },
|
|
387
330
|
});
|
|
388
|
-
return {
|
|
331
|
+
return { userId: opts.userId, groupId: opts.groupId };
|
|
389
332
|
},
|
|
390
333
|
/**
|
|
391
334
|
* Read the user's active group ID from `user.extend.lastActiveGroup`.
|
|
@@ -429,7 +372,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
429
372
|
* @param ctx - Convex mutation context.
|
|
430
373
|
* @param userId - The user's document ID.
|
|
431
374
|
* @param opts.cascade - Whether to delete related records (default `true`).
|
|
432
|
-
* @returns `{
|
|
375
|
+
* @returns `{ userId }`.
|
|
376
|
+
* @throws `INVALID_PARAMETERS` if `cascade` is `false` but the user has linked data.
|
|
433
377
|
*/
|
|
434
378
|
delete: async (
|
|
435
379
|
ctx: ComponentCtx,
|
|
@@ -462,7 +406,10 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
462
406
|
passkeys.length +
|
|
463
407
|
totps.length;
|
|
464
408
|
if (!cascade && totalLinked > 0) {
|
|
465
|
-
|
|
409
|
+
throw Cv.error({
|
|
410
|
+
code: "INVALID_PARAMETERS",
|
|
411
|
+
message: "The provided parameters are invalid.",
|
|
412
|
+
});
|
|
466
413
|
}
|
|
467
414
|
const deletions: Promise<unknown>[] = [];
|
|
468
415
|
for (const s of sessions)
|
|
@@ -501,7 +448,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
501
448
|
);
|
|
502
449
|
await Promise.all(deletions);
|
|
503
450
|
await ctx.runMutation(config.component.public.userDelete, { userId });
|
|
504
|
-
return {
|
|
451
|
+
return { userId };
|
|
505
452
|
},
|
|
506
453
|
};
|
|
507
454
|
|
|
@@ -547,7 +494,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
547
494
|
* @param ctx - Convex action context.
|
|
548
495
|
* @param args.userId - The user whose sessions should be invalidated.
|
|
549
496
|
* @param args.except - Optional array of session IDs to keep valid.
|
|
550
|
-
* @returns `{
|
|
497
|
+
* @returns `{ userId, except }` confirming the operation.
|
|
551
498
|
*
|
|
552
499
|
* @example Sign out everywhere except the current session
|
|
553
500
|
* ```ts
|
|
@@ -564,7 +511,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
564
511
|
) => {
|
|
565
512
|
await callInvalidateSessions(ctx, args);
|
|
566
513
|
return {
|
|
567
|
-
ok: true as const,
|
|
568
514
|
userId: args.userId,
|
|
569
515
|
except: args.except ?? [],
|
|
570
516
|
};
|
|
@@ -635,7 +581,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
635
581
|
* @param args.profile - Profile data used to create or update the user document.
|
|
636
582
|
* @param args.shouldLinkViaEmail - If `true`, link to an existing user by email match.
|
|
637
583
|
* @param args.shouldLinkViaPhone - If `true`, link to an existing user by phone match.
|
|
638
|
-
* @returns
|
|
584
|
+
* @returns The created account and user information.
|
|
639
585
|
*
|
|
640
586
|
* @example
|
|
641
587
|
* ```ts
|
|
@@ -651,7 +597,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
651
597
|
args: CreateAccountArgs,
|
|
652
598
|
) => {
|
|
653
599
|
const created = await callCreateAccountFromCredentials(ctx, args);
|
|
654
|
-
return {
|
|
600
|
+
return { ...created };
|
|
655
601
|
},
|
|
656
602
|
/**
|
|
657
603
|
* Retrieve an auth account by provider and credentials.
|
|
@@ -700,7 +646,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
700
646
|
* @param args.provider - The provider ID (e.g. `"password"`).
|
|
701
647
|
* @param args.account.id - Provider-specific account identifier.
|
|
702
648
|
* @param args.account.secret - The new credential secret to store.
|
|
703
|
-
* @returns `{
|
|
649
|
+
* @returns `{ accountId }` confirming the update.
|
|
704
650
|
*
|
|
705
651
|
* @example Password reset
|
|
706
652
|
* ```ts
|
|
@@ -715,7 +661,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
715
661
|
args: UpdateAccountCredentialsArgs,
|
|
716
662
|
) => {
|
|
717
663
|
await callModifyAccount(ctx, args);
|
|
718
|
-
return {
|
|
664
|
+
return { accountId: args.account.id };
|
|
719
665
|
},
|
|
720
666
|
/**
|
|
721
667
|
* Delete an auth account by ID.
|
|
@@ -727,16 +673,13 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
727
673
|
*
|
|
728
674
|
* @param ctx - Convex mutation context.
|
|
729
675
|
* @param accountId - The account's document ID.
|
|
730
|
-
* @returns `{
|
|
731
|
-
*
|
|
732
|
-
*
|
|
676
|
+
* @returns `{ accountId }` on success.
|
|
677
|
+
* @throws `ACCOUNT_NOT_FOUND` if the account does not exist.
|
|
678
|
+
* @throws `INVALID_PARAMETERS` if it is the user's last account.
|
|
733
679
|
*
|
|
734
680
|
* @example
|
|
735
681
|
* ```ts
|
|
736
|
-
*
|
|
737
|
-
* if (!result.ok) {
|
|
738
|
-
* console.error("Cannot delete account:", result.code);
|
|
739
|
-
* }
|
|
682
|
+
* await auth.account.delete(ctx, accountId);
|
|
740
683
|
* ```
|
|
741
684
|
*/
|
|
742
685
|
delete: async (ctx: ComponentCtx, accountId: string) => {
|
|
@@ -744,19 +687,25 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
744
687
|
accountId,
|
|
745
688
|
});
|
|
746
689
|
if (doc === null) {
|
|
747
|
-
|
|
690
|
+
throw Cv.error({
|
|
691
|
+
code: "ACCOUNT_NOT_FOUND",
|
|
692
|
+
message: "Account not found.",
|
|
693
|
+
});
|
|
748
694
|
}
|
|
749
695
|
const allAccounts = (await ctx.runQuery(
|
|
750
696
|
config.component.public.accountListByUser,
|
|
751
697
|
{ userId: (doc as any).userId },
|
|
752
698
|
)) as Array<{ _id: string }>;
|
|
753
699
|
if (allAccounts.length <= 1) {
|
|
754
|
-
|
|
700
|
+
throw Cv.error({
|
|
701
|
+
code: "INVALID_PARAMETERS",
|
|
702
|
+
message: "The provided parameters are invalid.",
|
|
703
|
+
});
|
|
755
704
|
}
|
|
756
705
|
await ctx.runMutation(config.component.public.accountDelete, {
|
|
757
706
|
accountId,
|
|
758
707
|
});
|
|
759
|
-
return {
|
|
708
|
+
return { accountId };
|
|
760
709
|
},
|
|
761
710
|
/**
|
|
762
711
|
* List all passkey credentials registered for a user.
|
|
@@ -794,7 +743,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
794
743
|
* @param ctx - Convex mutation context.
|
|
795
744
|
* @param passkeyId - The passkey credential's document ID.
|
|
796
745
|
* @param name - The new display name for the passkey.
|
|
797
|
-
* @returns `{
|
|
746
|
+
* @returns `{ passkeyId }` confirming the rename.
|
|
798
747
|
*
|
|
799
748
|
* @example
|
|
800
749
|
* ```ts
|
|
@@ -810,7 +759,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
810
759
|
passkeyId,
|
|
811
760
|
data: { name },
|
|
812
761
|
});
|
|
813
|
-
return {
|
|
762
|
+
return { passkeyId };
|
|
814
763
|
},
|
|
815
764
|
/**
|
|
816
765
|
* Delete a passkey credential.
|
|
@@ -821,7 +770,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
821
770
|
*
|
|
822
771
|
* @param ctx - Convex mutation context.
|
|
823
772
|
* @param passkeyId - The passkey credential's document ID.
|
|
824
|
-
* @returns `{
|
|
773
|
+
* @returns `{ passkeyId }` confirming the deletion.
|
|
825
774
|
*
|
|
826
775
|
* @example
|
|
827
776
|
* ```ts
|
|
@@ -832,7 +781,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
832
781
|
await ctx.runMutation(config.component.public.passkeyDelete, {
|
|
833
782
|
passkeyId,
|
|
834
783
|
});
|
|
835
|
-
return {
|
|
784
|
+
return { passkeyId };
|
|
836
785
|
},
|
|
837
786
|
/**
|
|
838
787
|
* List all TOTP (time-based one-time password) factors for a user.
|
|
@@ -863,7 +812,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
863
812
|
*
|
|
864
813
|
* @param ctx - Convex mutation context.
|
|
865
814
|
* @param totpId - The TOTP factor's document ID.
|
|
866
|
-
* @returns `{
|
|
815
|
+
* @returns `{ totpId }` confirming the deletion.
|
|
867
816
|
*
|
|
868
817
|
* @example
|
|
869
818
|
* ```ts
|
|
@@ -872,7 +821,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
872
821
|
*/
|
|
873
822
|
deleteTotp: async (ctx: ComponentCtx, totpId: string) => {
|
|
874
823
|
await ctx.runMutation(config.component.public.totpDelete, { totpId });
|
|
875
|
-
return {
|
|
824
|
+
return { totpId };
|
|
876
825
|
},
|
|
877
826
|
};
|
|
878
827
|
|
|
@@ -954,7 +903,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
954
903
|
* @param data.parentGroupId - Nest under this group. Omit for a root group.
|
|
955
904
|
* @param data.tags - Faceted classification tags (normalized at write time).
|
|
956
905
|
* @param data.extend - Arbitrary app-specific metadata.
|
|
957
|
-
* @returns `{
|
|
906
|
+
* @returns `{ groupId }`.
|
|
958
907
|
*
|
|
959
908
|
* @example Root group
|
|
960
909
|
* ```ts
|
|
@@ -980,12 +929,12 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
980
929
|
tags?: Array<{ key: string; value: string }>;
|
|
981
930
|
extend?: Record<string, unknown>;
|
|
982
931
|
},
|
|
983
|
-
): Promise<{
|
|
932
|
+
): Promise<{ groupId: string }> => {
|
|
984
933
|
const groupId = (await ctx.runMutation(
|
|
985
934
|
config.component.public.groupCreate,
|
|
986
935
|
data,
|
|
987
936
|
)) as string;
|
|
988
|
-
return {
|
|
937
|
+
return { groupId };
|
|
989
938
|
},
|
|
990
939
|
/**
|
|
991
940
|
* Fetch a group document by ID.
|
|
@@ -1074,7 +1023,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1074
1023
|
* @param ctx - Convex mutation context.
|
|
1075
1024
|
* @param groupId - The group's document ID.
|
|
1076
1025
|
* @param data - Fields to merge (e.g. `name`, `slug`, `tags`, `parentGroupId`).
|
|
1077
|
-
* @returns `{
|
|
1026
|
+
* @returns `{ groupId }`.
|
|
1078
1027
|
*
|
|
1079
1028
|
* @example
|
|
1080
1029
|
* ```ts
|
|
@@ -1093,7 +1042,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1093
1042
|
groupId,
|
|
1094
1043
|
data,
|
|
1095
1044
|
});
|
|
1096
|
-
return {
|
|
1045
|
+
return { groupId };
|
|
1097
1046
|
},
|
|
1098
1047
|
/**
|
|
1099
1048
|
* Delete a group and recursively cascade to all descendant groups,
|
|
@@ -1101,7 +1050,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1101
1050
|
*
|
|
1102
1051
|
* @param ctx - Convex mutation context.
|
|
1103
1052
|
* @param groupId - The group's document ID.
|
|
1104
|
-
* @returns `{
|
|
1053
|
+
* @returns `{ groupId }`.
|
|
1105
1054
|
*
|
|
1106
1055
|
* @example
|
|
1107
1056
|
* ```ts
|
|
@@ -1110,7 +1059,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1110
1059
|
*/
|
|
1111
1060
|
delete: async (ctx: ComponentCtx, groupId: string) => {
|
|
1112
1061
|
await ctx.runMutation(config.component.public.groupDelete, { groupId });
|
|
1113
|
-
return {
|
|
1062
|
+
return { groupId };
|
|
1114
1063
|
},
|
|
1115
1064
|
/**
|
|
1116
1065
|
* Walk up the group hierarchy from `groupId` and return all ancestor
|
|
@@ -1176,7 +1125,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1176
1125
|
* Add a user to a group with optional role IDs.
|
|
1177
1126
|
*
|
|
1178
1127
|
* Role IDs are validated against the roles defined in `defineRoles()` —
|
|
1179
|
-
* invalid IDs
|
|
1128
|
+
* invalid IDs throw `INVALID_ROLE_IDS`.
|
|
1180
1129
|
* Throws `DUPLICATE_MEMBERSHIP` if the user is already a member.
|
|
1181
1130
|
*
|
|
1182
1131
|
* @param ctx - Convex mutation context.
|
|
@@ -1185,7 +1134,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1185
1134
|
* @param data.roleIds - Role IDs from `defineRoles()` (optional).
|
|
1186
1135
|
* @param data.status - Membership status string (optional, app-defined).
|
|
1187
1136
|
* @param data.extend - Arbitrary app-specific metadata.
|
|
1188
|
-
* @returns `{
|
|
1137
|
+
* @returns `{ memberId }`.
|
|
1138
|
+
* @throws `INVALID_ROLE_IDS` if any supplied role IDs are not defined.
|
|
1189
1139
|
*
|
|
1190
1140
|
* @example
|
|
1191
1141
|
* ```ts
|
|
@@ -1206,18 +1156,12 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1206
1156
|
extend?: Record<string, unknown>;
|
|
1207
1157
|
},
|
|
1208
1158
|
) => {
|
|
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
|
-
};
|
|
1159
|
+
const roleIds = normalizeRoleIds(data.roleIds);
|
|
1216
1160
|
const memberId = (await ctx.runMutation(
|
|
1217
1161
|
config.component.public.memberAdd,
|
|
1218
|
-
{ ...data, roleIds
|
|
1162
|
+
{ ...data, roleIds },
|
|
1219
1163
|
)) as string;
|
|
1220
|
-
return {
|
|
1164
|
+
return { memberId };
|
|
1221
1165
|
},
|
|
1222
1166
|
/**
|
|
1223
1167
|
* Fetch a membership document by its document ID.
|
|
@@ -1291,7 +1235,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1291
1235
|
*
|
|
1292
1236
|
* @param ctx - Convex mutation context.
|
|
1293
1237
|
* @param memberId - The membership document ID.
|
|
1294
|
-
* @returns `{
|
|
1238
|
+
* @returns `{ memberId }`.
|
|
1295
1239
|
*
|
|
1296
1240
|
* @example
|
|
1297
1241
|
* ```ts
|
|
@@ -1300,7 +1244,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1300
1244
|
*/
|
|
1301
1245
|
delete: async (ctx: ComponentCtx, memberId: string) => {
|
|
1302
1246
|
await ctx.runMutation(config.component.public.memberRemove, { memberId });
|
|
1303
|
-
return {
|
|
1247
|
+
return { memberId };
|
|
1304
1248
|
},
|
|
1305
1249
|
/**
|
|
1306
1250
|
* Patch a membership's `roleIds`, `status`, or `extend` fields.
|
|
@@ -1309,7 +1253,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1309
1253
|
* @param ctx - Convex mutation context.
|
|
1310
1254
|
* @param memberId - The membership document ID.
|
|
1311
1255
|
* @param data - Fields to merge. `roleIds` are validated.
|
|
1312
|
-
* @returns `{
|
|
1256
|
+
* @returns `{ memberId }`.
|
|
1257
|
+
* @throws `INVALID_ROLE_IDS` if any supplied role IDs are not defined.
|
|
1313
1258
|
*
|
|
1314
1259
|
* @example
|
|
1315
1260
|
* ```ts
|
|
@@ -1326,24 +1271,17 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1326
1271
|
) => {
|
|
1327
1272
|
const nextData = { ...data };
|
|
1328
1273
|
if ("roleIds" in nextData) {
|
|
1329
|
-
|
|
1274
|
+
nextData.roleIds = normalizeRoleIds(
|
|
1330
1275
|
Array.isArray(nextData.roleIds)
|
|
1331
1276
|
? (nextData.roleIds as string[])
|
|
1332
1277
|
: undefined,
|
|
1333
1278
|
);
|
|
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
1279
|
}
|
|
1342
1280
|
await ctx.runMutation(config.component.public.memberUpdate, {
|
|
1343
1281
|
memberId,
|
|
1344
1282
|
data: nextData,
|
|
1345
1283
|
});
|
|
1346
|
-
return {
|
|
1284
|
+
return { memberId };
|
|
1347
1285
|
},
|
|
1348
1286
|
/**
|
|
1349
1287
|
* Resolve a user's membership in a group, optionally walking the
|
|
@@ -1364,72 +1302,42 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1364
1302
|
* @param opts.userId - The user's document ID.
|
|
1365
1303
|
* @param opts.groupId - The group to check membership in.
|
|
1366
1304
|
* @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
1305
|
* @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.
|
|
1306
|
+
* @returns `{ membership, roleIds, grants }`.
|
|
1372
1307
|
*
|
|
1373
1308
|
* @example Direct lookup
|
|
1374
1309
|
* ```ts
|
|
1375
|
-
* const result = await auth.member.
|
|
1376
|
-
* if (!result.membership) return
|
|
1310
|
+
* const result = await auth.member.inspect(ctx, { userId, groupId });
|
|
1311
|
+
* if (!result.membership) return null;
|
|
1377
1312
|
* ```
|
|
1378
1313
|
*
|
|
1379
|
-
* @example Check grants
|
|
1314
|
+
* @example Check grants after inspection
|
|
1380
1315
|
* ```ts
|
|
1381
|
-
* const result = await auth.member.
|
|
1382
|
-
* userId, groupId,
|
|
1316
|
+
* const result = await auth.member.inspect(ctx, {
|
|
1317
|
+
* userId, groupId,
|
|
1383
1318
|
* });
|
|
1384
|
-
*
|
|
1319
|
+
* const canCreate = result.grants.includes("issues.create");
|
|
1385
1320
|
* ```
|
|
1386
1321
|
*
|
|
1387
1322
|
* @example Walk hierarchy + check grants
|
|
1388
1323
|
* ```ts
|
|
1389
|
-
* const result = await auth.member.
|
|
1390
|
-
* userId, groupId: teamId, ancestry: true,
|
|
1324
|
+
* const result = await auth.member.inspect(ctx, {
|
|
1325
|
+
* userId, groupId: teamId, ancestry: true,
|
|
1391
1326
|
* });
|
|
1392
1327
|
* ```
|
|
1393
1328
|
*/
|
|
1394
|
-
|
|
1329
|
+
inspect: async (
|
|
1395
1330
|
ctx: ComponentReadCtx,
|
|
1396
1331
|
opts: {
|
|
1397
1332
|
userId: string;
|
|
1398
1333
|
groupId: string;
|
|
1399
1334
|
ancestry?: boolean;
|
|
1400
|
-
roleIds?: string[];
|
|
1401
|
-
grants?: string[];
|
|
1402
1335
|
maxDepth?: number;
|
|
1403
1336
|
},
|
|
1404
1337
|
) => {
|
|
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
1338
|
const useAncestry = opts.ancestry === true;
|
|
1426
1339
|
|
|
1427
1340
|
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
1341
|
|
|
1434
1342
|
if (useAncestry) {
|
|
1435
1343
|
// Hierarchy walk — single component RPC
|
|
@@ -1444,11 +1352,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1444
1352
|
},
|
|
1445
1353
|
);
|
|
1446
1354
|
membership = result.membership;
|
|
1447
|
-
matchedGroupId = result.matchedGroupId;
|
|
1448
|
-
depth = result.depth;
|
|
1449
|
-
isDirect = result.isDirect;
|
|
1450
|
-
isInherited = result.isInherited;
|
|
1451
|
-
traversedGroupIds = result.traversedGroupIds ?? [];
|
|
1452
1355
|
} else {
|
|
1453
1356
|
// Fast path — direct lookup, 1 read
|
|
1454
1357
|
const doc = await ctx.runQuery(
|
|
@@ -1456,64 +1359,75 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1456
1359
|
{ userId: opts.userId, groupId: opts.groupId },
|
|
1457
1360
|
);
|
|
1458
1361
|
membership = doc;
|
|
1459
|
-
matchedGroupId = doc ? opts.groupId : null;
|
|
1460
|
-
depth = doc ? 0 : null;
|
|
1461
|
-
isDirect = doc !== null;
|
|
1462
1362
|
}
|
|
1463
1363
|
|
|
1464
1364
|
if (membership === null) {
|
|
1465
1365
|
return {
|
|
1466
|
-
ok: false as const,
|
|
1467
1366
|
membership: null,
|
|
1468
|
-
matchedGroupId: null,
|
|
1469
1367
|
roleIds: [] as string[],
|
|
1470
1368
|
grants: [] as string[],
|
|
1471
|
-
missingGrants: requiredGrants,
|
|
1472
|
-
depth: null,
|
|
1473
|
-
isDirect: false,
|
|
1474
|
-
isInherited: false,
|
|
1475
|
-
traversedGroupIds,
|
|
1476
1369
|
};
|
|
1477
1370
|
}
|
|
1478
1371
|
|
|
1479
1372
|
const membershipRoleIds = membership.roleIds ?? [];
|
|
1480
1373
|
const membershipGrants = resolveGrantedPermissions(membershipRoleIds);
|
|
1481
1374
|
|
|
1482
|
-
|
|
1375
|
+
return {
|
|
1376
|
+
membership,
|
|
1377
|
+
roleIds: membershipRoleIds,
|
|
1378
|
+
grants: membershipGrants,
|
|
1379
|
+
};
|
|
1380
|
+
},
|
|
1381
|
+
require: async (
|
|
1382
|
+
ctx: ComponentReadCtx,
|
|
1383
|
+
opts: {
|
|
1384
|
+
userId: string;
|
|
1385
|
+
groupId: string;
|
|
1386
|
+
ancestry?: boolean;
|
|
1387
|
+
roleIds?: string[];
|
|
1388
|
+
grants?: string[];
|
|
1389
|
+
maxDepth?: number;
|
|
1390
|
+
},
|
|
1391
|
+
) => {
|
|
1392
|
+
const validatedRoleIds = normalizeRoleIds(opts.roleIds);
|
|
1393
|
+
const requiredGrants = Array.from(new Set(opts.grants ?? []));
|
|
1394
|
+
const roleFilter =
|
|
1395
|
+
validatedRoleIds.length > 0 ? new Set(validatedRoleIds) : null;
|
|
1396
|
+
const result = await member.inspect(ctx, {
|
|
1397
|
+
userId: opts.userId,
|
|
1398
|
+
groupId: opts.groupId,
|
|
1399
|
+
ancestry: opts.ancestry,
|
|
1400
|
+
maxDepth: opts.maxDepth,
|
|
1401
|
+
});
|
|
1402
|
+
if (result.membership === null) {
|
|
1403
|
+
throw Cv.error({
|
|
1404
|
+
code: "NOT_A_MEMBER",
|
|
1405
|
+
message: "User is not a member of this group.",
|
|
1406
|
+
groupId: opts.groupId,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1483
1409
|
if (
|
|
1484
1410
|
roleFilter !== null &&
|
|
1485
|
-
!
|
|
1411
|
+
!result.roleIds.some((roleId: string) => roleFilter.has(roleId))
|
|
1486
1412
|
) {
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
grants: [] as string[],
|
|
1493
|
-
missingGrants: requiredGrants,
|
|
1494
|
-
depth: null,
|
|
1495
|
-
isDirect: false,
|
|
1496
|
-
isInherited: false,
|
|
1497
|
-
traversedGroupIds,
|
|
1498
|
-
};
|
|
1413
|
+
throw Cv.error({
|
|
1414
|
+
code: "NOT_A_MEMBER",
|
|
1415
|
+
message: "User is not a member of this group.",
|
|
1416
|
+
groupId: opts.groupId,
|
|
1417
|
+
});
|
|
1499
1418
|
}
|
|
1500
|
-
|
|
1501
1419
|
const missingGrants = requiredGrants.filter(
|
|
1502
|
-
(grant) => !
|
|
1420
|
+
(grant) => !result.grants.includes(grant),
|
|
1503
1421
|
);
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
isDirect,
|
|
1514
|
-
isInherited,
|
|
1515
|
-
traversedGroupIds,
|
|
1516
|
-
};
|
|
1422
|
+
if (missingGrants.length > 0) {
|
|
1423
|
+
throw Cv.error({
|
|
1424
|
+
code: "MISSING_GRANTS",
|
|
1425
|
+
message: "User is missing required grants.",
|
|
1426
|
+
groupId: opts.groupId,
|
|
1427
|
+
missingGrants,
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
return result;
|
|
1517
1431
|
},
|
|
1518
1432
|
};
|
|
1519
1433
|
|
|
@@ -1529,7 +1443,8 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1529
1443
|
* @param data.roleIds - Role IDs from `defineRoles()` to assign on acceptance (optional).
|
|
1530
1444
|
* @param data.expiresTime - Expiration timestamp in ms since epoch (optional).
|
|
1531
1445
|
* @param data.extend - Arbitrary app-specific metadata (optional).
|
|
1532
|
-
* @returns `{
|
|
1446
|
+
* @returns `{ inviteId, token }`.
|
|
1447
|
+
* @throws `INVALID_ROLE_IDS` if any supplied role IDs are not defined.
|
|
1533
1448
|
*
|
|
1534
1449
|
* @example
|
|
1535
1450
|
* ```ts
|
|
@@ -1549,13 +1464,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1549
1464
|
extend?: Record<string, unknown>;
|
|
1550
1465
|
},
|
|
1551
1466
|
) => {
|
|
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
|
-
};
|
|
1467
|
+
const roleIds = normalizeRoleIds(data.roleIds);
|
|
1559
1468
|
const token = generateRandomString(
|
|
1560
1469
|
inviteTokenLength,
|
|
1561
1470
|
inviteTokenAlphabet,
|
|
@@ -1563,9 +1472,9 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1563
1472
|
const tokenHash = await sha256(token);
|
|
1564
1473
|
const inviteId = (await ctx.runMutation(
|
|
1565
1474
|
config.component.public.inviteCreate,
|
|
1566
|
-
{ ...data, roleIds
|
|
1475
|
+
{ ...data, roleIds, tokenHash, status: "pending" },
|
|
1567
1476
|
)) as string;
|
|
1568
|
-
return {
|
|
1477
|
+
return { inviteId, token };
|
|
1569
1478
|
},
|
|
1570
1479
|
/**
|
|
1571
1480
|
* Fetch an invite document by ID.
|
|
@@ -1628,7 +1537,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1628
1537
|
* @param ctx - Convex mutation context.
|
|
1629
1538
|
* @param args.token - The raw invite token string.
|
|
1630
1539
|
* @param args.acceptedByUserId - The user accepting the invite.
|
|
1631
|
-
* @returns
|
|
1540
|
+
* @returns The created membership details.
|
|
1632
1541
|
*
|
|
1633
1542
|
* @example
|
|
1634
1543
|
* ```ts
|
|
@@ -1647,7 +1556,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1647
1556
|
config.component.public.inviteAcceptByToken,
|
|
1648
1557
|
{ tokenHash, acceptedByUserId: args.acceptedByUserId },
|
|
1649
1558
|
);
|
|
1650
|
-
return {
|
|
1559
|
+
return { ...result };
|
|
1651
1560
|
},
|
|
1652
1561
|
},
|
|
1653
1562
|
/**
|
|
@@ -1715,7 +1624,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1715
1624
|
* @param ctx - Convex mutation context.
|
|
1716
1625
|
* @param inviteId - The invite's document ID.
|
|
1717
1626
|
* @param acceptedByUserId - The user who accepted the invite (optional).
|
|
1718
|
-
* @returns `{
|
|
1627
|
+
* @returns `{ inviteId, acceptedByUserId }`.
|
|
1719
1628
|
*
|
|
1720
1629
|
* @example
|
|
1721
1630
|
* ```ts
|
|
@@ -1732,7 +1641,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1732
1641
|
...(acceptedByUserId ? { acceptedByUserId } : {}),
|
|
1733
1642
|
});
|
|
1734
1643
|
return {
|
|
1735
|
-
ok: true as const,
|
|
1736
1644
|
inviteId,
|
|
1737
1645
|
acceptedByUserId: acceptedByUserId ?? null,
|
|
1738
1646
|
};
|
|
@@ -1745,7 +1653,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1745
1653
|
*
|
|
1746
1654
|
* @param ctx - Convex mutation context.
|
|
1747
1655
|
* @param inviteId - The invite's document ID.
|
|
1748
|
-
* @returns `{
|
|
1656
|
+
* @returns `{ inviteId }`.
|
|
1749
1657
|
*
|
|
1750
1658
|
* @example
|
|
1751
1659
|
* ```ts
|
|
@@ -1754,7 +1662,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1754
1662
|
*/
|
|
1755
1663
|
revoke: async (ctx: ComponentCtx, inviteId: string) => {
|
|
1756
1664
|
await ctx.runMutation(config.component.public.inviteRevoke, { inviteId });
|
|
1757
|
-
return {
|
|
1665
|
+
return { inviteId };
|
|
1758
1666
|
},
|
|
1759
1667
|
};
|
|
1760
1668
|
|
|
@@ -1770,7 +1678,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1770
1678
|
* @param opts.rateLimit - Optional per-key rate limit `{ maxRequests, windowMs }`.
|
|
1771
1679
|
* @param opts.expiresAt - Optional expiration timestamp (ms since epoch).
|
|
1772
1680
|
* @param opts.metadata - Arbitrary app-specific metadata.
|
|
1773
|
-
* @returns `{
|
|
1681
|
+
* @returns `{ keyId, secret }`. Store `secret` securely — it cannot be retrieved later.
|
|
1774
1682
|
*
|
|
1775
1683
|
* @example
|
|
1776
1684
|
* ```ts
|
|
@@ -1791,7 +1699,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1791
1699
|
expiresAt?: number;
|
|
1792
1700
|
metadata?: Record<string, unknown>;
|
|
1793
1701
|
},
|
|
1794
|
-
): Promise<{
|
|
1702
|
+
): Promise<{ keyId: string; secret: string }> => {
|
|
1795
1703
|
const { raw, hashedKey, displayPrefix } = await generateApiKey("sk_");
|
|
1796
1704
|
const keyId = (await ctx.runMutation(config.component.public.keyInsert, {
|
|
1797
1705
|
userId: opts.userId,
|
|
@@ -1803,7 +1711,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1803
1711
|
expiresAt: opts.expiresAt,
|
|
1804
1712
|
metadata: opts.metadata,
|
|
1805
1713
|
})) as string;
|
|
1806
|
-
return {
|
|
1714
|
+
return { keyId, secret: raw };
|
|
1807
1715
|
},
|
|
1808
1716
|
/**
|
|
1809
1717
|
* Verify an API key and return the owner's identity and scopes.
|
|
@@ -1813,48 +1721,45 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1813
1721
|
*
|
|
1814
1722
|
* @param ctx - Convex mutation context (updates `lastUsedAt` and rate limit state).
|
|
1815
1723
|
* @param rawKey - The raw `sk_*` key string.
|
|
1816
|
-
* @returns
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1821
|
-
* - `"API_KEY_RATE_LIMITED"` — rate limit exceeded.
|
|
1724
|
+
* @returns `{ userId, keyId, scopes }` where `scopes.can(resource, action)` checks permissions.
|
|
1725
|
+
* @throws `INVALID_API_KEY` if the key is not found.
|
|
1726
|
+
* @throws `API_KEY_REVOKED` if the key was revoked.
|
|
1727
|
+
* @throws `API_KEY_EXPIRED` if the key is past its `expiresAt`.
|
|
1728
|
+
* @throws `API_KEY_RATE_LIMITED` if the rate limit is exceeded.
|
|
1822
1729
|
*
|
|
1823
1730
|
* @example
|
|
1824
1731
|
* ```ts
|
|
1825
|
-
* const
|
|
1826
|
-
*
|
|
1827
|
-
* const canRead = result.scopes.can("data", "read");
|
|
1732
|
+
* const { userId, scopes } = await auth.key.verify(ctx, rawKey);
|
|
1733
|
+
* const canRead = scopes.can("data", "read");
|
|
1828
1734
|
* ```
|
|
1829
1735
|
*/
|
|
1830
1736
|
verify: async (
|
|
1831
1737
|
ctx: ComponentCtx,
|
|
1832
1738
|
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
|
-
> => {
|
|
1739
|
+
): Promise<{ userId: string; keyId: string; scopes: ScopeChecker }> => {
|
|
1844
1740
|
const hashedKey = await hashApiKey(rawKey);
|
|
1845
1741
|
const doc = (await ctx.runQuery(
|
|
1846
1742
|
config.component.public.keyGetByHashedKey,
|
|
1847
1743
|
{ hashedKey },
|
|
1848
1744
|
)) as KeyDoc | null;
|
|
1849
1745
|
if (!doc) {
|
|
1850
|
-
|
|
1746
|
+
throw Cv.error({
|
|
1747
|
+
code: "INVALID_API_KEY",
|
|
1748
|
+
message: "Invalid API key.",
|
|
1749
|
+
});
|
|
1851
1750
|
}
|
|
1852
1751
|
const k = doc;
|
|
1853
1752
|
if (k.revoked) {
|
|
1854
|
-
|
|
1753
|
+
throw Cv.error({
|
|
1754
|
+
code: "API_KEY_REVOKED",
|
|
1755
|
+
message: "This API key has been revoked.",
|
|
1756
|
+
});
|
|
1855
1757
|
}
|
|
1856
1758
|
if (k.expiresAt && k.expiresAt < Date.now()) {
|
|
1857
|
-
|
|
1759
|
+
throw Cv.error({
|
|
1760
|
+
code: "API_KEY_EXPIRED",
|
|
1761
|
+
message: "This API key has expired.",
|
|
1762
|
+
});
|
|
1858
1763
|
}
|
|
1859
1764
|
const patchData: Record<string, unknown> = { lastUsedAt: Date.now() };
|
|
1860
1765
|
if (k.rateLimit) {
|
|
@@ -1863,7 +1768,10 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1863
1768
|
k.rateLimitState ?? undefined,
|
|
1864
1769
|
);
|
|
1865
1770
|
if (limited) {
|
|
1866
|
-
|
|
1771
|
+
throw Cv.error({
|
|
1772
|
+
code: "API_KEY_RATE_LIMITED",
|
|
1773
|
+
message: "API key rate limit exceeded. Please try again later.",
|
|
1774
|
+
});
|
|
1867
1775
|
}
|
|
1868
1776
|
patchData.rateLimitState = newState;
|
|
1869
1777
|
}
|
|
@@ -1872,7 +1780,6 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1872
1780
|
data: patchData,
|
|
1873
1781
|
});
|
|
1874
1782
|
return {
|
|
1875
|
-
ok: true as const,
|
|
1876
1783
|
userId: k.userId,
|
|
1877
1784
|
keyId: k._id,
|
|
1878
1785
|
scopes: buildScopeChecker(k.scopes),
|
|
@@ -1936,24 +1843,23 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1936
1843
|
*
|
|
1937
1844
|
* @param ctx - Convex query or mutation context.
|
|
1938
1845
|
* @param keyId - The API key's document ID.
|
|
1939
|
-
* @returns
|
|
1846
|
+
* @returns The key document, or `null` if not found.
|
|
1940
1847
|
*
|
|
1941
1848
|
* @example
|
|
1942
1849
|
* ```ts
|
|
1943
|
-
* const
|
|
1944
|
-
* if (!
|
|
1945
|
-
* console.log(
|
|
1850
|
+
* const key = await auth.key.get(ctx, keyId);
|
|
1851
|
+
* if (!key) throw new Error("Key not found");
|
|
1852
|
+
* console.log(key.name, key.prefix);
|
|
1946
1853
|
* ```
|
|
1947
1854
|
*/
|
|
1948
1855
|
get: async (
|
|
1949
1856
|
ctx: ComponentReadCtx,
|
|
1950
1857
|
keyId: string,
|
|
1951
|
-
): Promise<
|
|
1858
|
+
): Promise<KeyDoc | null> => {
|
|
1952
1859
|
const doc = (await ctx.runQuery(config.component.public.keyGetById, {
|
|
1953
1860
|
keyId,
|
|
1954
1861
|
})) as KeyDoc | null;
|
|
1955
|
-
|
|
1956
|
-
return { ok: true as const, key: doc };
|
|
1862
|
+
return doc ?? null;
|
|
1957
1863
|
},
|
|
1958
1864
|
/**
|
|
1959
1865
|
* Update a key's name, scopes, or rate limit.
|
|
@@ -1964,7 +1870,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1964
1870
|
* @param ctx - Convex mutation context.
|
|
1965
1871
|
* @param keyId - The API key's document ID.
|
|
1966
1872
|
* @param data - Fields to merge into the key document.
|
|
1967
|
-
* @returns `{
|
|
1873
|
+
* @returns `{ keyId }`.
|
|
1968
1874
|
*
|
|
1969
1875
|
* @example
|
|
1970
1876
|
* ```ts
|
|
@@ -1984,18 +1890,18 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
1984
1890
|
},
|
|
1985
1891
|
) => {
|
|
1986
1892
|
await ctx.runMutation(config.component.public.keyPatch, { keyId, data });
|
|
1987
|
-
return {
|
|
1893
|
+
return { keyId };
|
|
1988
1894
|
},
|
|
1989
1895
|
/**
|
|
1990
1896
|
* Soft-delete: set `revoked: true`. The key can no longer be verified.
|
|
1991
1897
|
*
|
|
1992
1898
|
* After revocation, any subsequent calls to `auth.key.verify` with
|
|
1993
|
-
* this key will
|
|
1899
|
+
* this key will throw `API_KEY_REVOKED`.
|
|
1994
1900
|
* The key record is preserved for audit purposes.
|
|
1995
1901
|
*
|
|
1996
1902
|
* @param ctx - Convex mutation context.
|
|
1997
1903
|
* @param keyId - The API key's document ID.
|
|
1998
|
-
* @returns `{
|
|
1904
|
+
* @returns `{ keyId }`.
|
|
1999
1905
|
*
|
|
2000
1906
|
* @example
|
|
2001
1907
|
* ```ts
|
|
@@ -2007,7 +1913,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
2007
1913
|
keyId,
|
|
2008
1914
|
data: { revoked: true },
|
|
2009
1915
|
});
|
|
2010
|
-
return {
|
|
1916
|
+
return { keyId };
|
|
2011
1917
|
},
|
|
2012
1918
|
/**
|
|
2013
1919
|
* Hard-delete: permanently remove the key record.
|
|
@@ -2018,7 +1924,7 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
2018
1924
|
*
|
|
2019
1925
|
* @param ctx - Convex mutation context.
|
|
2020
1926
|
* @param keyId - The API key's document ID.
|
|
2021
|
-
* @returns `{
|
|
1927
|
+
* @returns `{ keyId }`.
|
|
2022
1928
|
*
|
|
2023
1929
|
* @example
|
|
2024
1930
|
* ```ts
|
|
@@ -2027,45 +1933,48 @@ export function createCoreDomains(deps: CoreDeps) {
|
|
|
2027
1933
|
*/
|
|
2028
1934
|
delete: async (ctx: ComponentCtx, keyId: string) => {
|
|
2029
1935
|
await ctx.runMutation(config.component.public.keyDelete, { keyId });
|
|
2030
|
-
return {
|
|
1936
|
+
return { keyId };
|
|
2031
1937
|
},
|
|
2032
1938
|
/**
|
|
2033
1939
|
* Rotate a key: revokes the old key and creates a new one with the
|
|
2034
1940
|
* same user, scopes, and rate limit. Returns the new `keyId` and `secret`.
|
|
2035
|
-
*
|
|
1941
|
+
* Throws if the key does not exist or is already revoked.
|
|
2036
1942
|
*
|
|
2037
1943
|
* @param ctx - Convex mutation context.
|
|
2038
1944
|
* @param keyId - The existing API key's document ID to rotate.
|
|
2039
1945
|
* @param opts.name - Optional new name for the rotated key (defaults to the old name).
|
|
2040
1946
|
* @param opts.expiresAt - Optional new expiration timestamp in ms since epoch.
|
|
2041
|
-
* @returns `{
|
|
1947
|
+
* @returns `{ keyId, secret }` with the new key.
|
|
1948
|
+
* @throws `INVALID_PARAMETERS` if the key does not exist.
|
|
1949
|
+
* @throws `API_KEY_REVOKED` if the key is already revoked.
|
|
2042
1950
|
*
|
|
2043
1951
|
* @example
|
|
2044
1952
|
* ```ts
|
|
2045
|
-
* const
|
|
1953
|
+
* const { keyId, secret } = await auth.key.rotate(ctx, oldKeyId, {
|
|
2046
1954
|
* expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
2047
1955
|
* });
|
|
2048
|
-
*
|
|
2049
|
-
* // Store result.secret securely — shown only once
|
|
2050
|
-
* }
|
|
1956
|
+
* // Store secret securely — shown only once
|
|
2051
1957
|
* ```
|
|
2052
1958
|
*/
|
|
2053
1959
|
rotate: async (
|
|
2054
1960
|
ctx: ComponentCtx,
|
|
2055
1961
|
keyId: string,
|
|
2056
1962
|
opts?: { name?: string; expiresAt?: number },
|
|
2057
|
-
): Promise<
|
|
2058
|
-
| { ok: true; keyId: string; secret: string }
|
|
2059
|
-
| { ok: false; code: "INVALID_PARAMETERS" | "API_KEY_REVOKED" }
|
|
2060
|
-
> => {
|
|
1963
|
+
): Promise<{ keyId: string; secret: string }> => {
|
|
2061
1964
|
const existing = await ctx.runQuery(config.component.public.keyGetById, {
|
|
2062
1965
|
keyId,
|
|
2063
1966
|
});
|
|
2064
1967
|
if (!existing) {
|
|
2065
|
-
|
|
1968
|
+
throw Cv.error({
|
|
1969
|
+
code: "INVALID_PARAMETERS",
|
|
1970
|
+
message: "The provided parameters are invalid.",
|
|
1971
|
+
});
|
|
2066
1972
|
}
|
|
2067
1973
|
if ((existing as any).revoked === true) {
|
|
2068
|
-
|
|
1974
|
+
throw Cv.error({
|
|
1975
|
+
code: "API_KEY_REVOKED",
|
|
1976
|
+
message: "This API key has been revoked.",
|
|
1977
|
+
});
|
|
2069
1978
|
}
|
|
2070
1979
|
await ctx.runMutation(config.component.public.keyPatch, {
|
|
2071
1980
|
keyId,
|