@robelest/convex-auth 0.0.4-preview.28 → 0.0.4-preview.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +4 -1
- package/dist/component/_generated/component.d.ts +344 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/model.d.ts +25 -25
- package/dist/component/model.js +1 -0
- package/dist/component/public/factors/totp.js +12 -0
- package/dist/component/public/groups/core.js +89 -1
- package/dist/component/public/groups/members.js +39 -1
- package/dist/component/public/identity/accounts.js +22 -3
- package/dist/component/public/identity/users.js +79 -5
- package/dist/component/public/sso/audit.js +5 -2
- package/dist/component/public/sso/core.js +3 -3
- package/dist/component/public/sso/domains.js +7 -3
- package/dist/component/public/sso/scim.js +36 -1
- package/dist/component/public.js +5 -5
- package/dist/component/schema.d.ts +293 -289
- package/dist/component/schema.js +2 -1
- package/dist/core/index.d.ts +63 -27
- package/dist/core/index.js +1 -0
- package/dist/providers/credentials.d.ts +12 -0
- package/dist/providers/password.js +28 -7
- package/dist/server/auth-context.d.ts +17 -0
- package/dist/server/auth-context.js +1 -1
- package/dist/server/auth.d.ts +18 -6
- package/dist/server/auth.js +1 -0
- package/dist/server/context.js +11 -5
- package/dist/server/contract.js +44 -12
- package/dist/server/core.js +146 -149
- package/dist/server/ctxCache.js +94 -0
- package/dist/server/limits.js +39 -9
- package/dist/server/mounts.d.ts +85 -85
- package/dist/server/mutations/credentialsSignIn.js +114 -0
- package/dist/server/mutations/index.js +2 -1
- package/dist/server/mutations/refresh.js +25 -12
- package/dist/server/mutations/retrieve.js +2 -1
- package/dist/server/mutations/signin.js +27 -9
- package/dist/server/mutations/store.js +5 -0
- package/dist/server/mutations/verify.js +32 -8
- package/dist/server/runtime.d.ts +81 -47
- package/dist/server/services/group.js +23 -16
- package/dist/server/sessions.d.ts +21 -0
- package/dist/server/sessions.js +37 -27
- package/dist/server/signin.js +32 -9
- package/dist/server/sso/domain.js +1 -2
- package/dist/server/sso/oidc.js +1 -1
- package/dist/server/tokens.js +19 -3
- package/dist/server/users.js +3 -2
- package/dist/server/utils/span.js +18 -0
- package/package.json +1 -1
- package/README.md +0 -161
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { LOG_LEVELS } from "../../shared/log.js";
|
|
2
|
+
import { verify } from "../crypto.js";
|
|
3
|
+
import { authDb } from "../db.js";
|
|
4
|
+
import { log, maybeRedact } from "../log.js";
|
|
5
|
+
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
6
|
+
import { withSpan } from "../utils/span.js";
|
|
7
|
+
import { issueSession } from "../sessions.js";
|
|
8
|
+
import { getSignInRateLimitState, isStateRateLimited, recordFailedSignIn, resetSignInRateLimit } from "../limits.js";
|
|
9
|
+
import { v } from "convex/values";
|
|
10
|
+
|
|
11
|
+
//#region src/server/mutations/credentialsSignIn.ts
|
|
12
|
+
const credentialsSignInArgs = v.object({
|
|
13
|
+
provider: v.string(),
|
|
14
|
+
account: v.object({
|
|
15
|
+
id: v.string(),
|
|
16
|
+
secret: v.string()
|
|
17
|
+
}),
|
|
18
|
+
generateTokens: v.boolean(),
|
|
19
|
+
requireVerifiedEmail: v.boolean(),
|
|
20
|
+
enforceTotp: v.boolean()
|
|
21
|
+
});
|
|
22
|
+
async function credentialsSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
23
|
+
return withSpan("convex-auth.mutations.credentialsSignIn", {
|
|
24
|
+
provider: args.provider,
|
|
25
|
+
generateTokens: args.generateTokens
|
|
26
|
+
}, () => credentialsSignInInner(ctx, args, getProviderOrThrow, config));
|
|
27
|
+
}
|
|
28
|
+
async function credentialsSignInInner(ctx, args, getProviderOrThrow, config) {
|
|
29
|
+
const { provider: providerId, account, generateTokens, requireVerifiedEmail, enforceTotp } = args;
|
|
30
|
+
const db = authDb(ctx, config);
|
|
31
|
+
log(LOG_LEVELS.DEBUG, "credentialsSignInImpl args:", {
|
|
32
|
+
provider: providerId,
|
|
33
|
+
account: {
|
|
34
|
+
id: account.id,
|
|
35
|
+
secret: maybeRedact(account.secret)
|
|
36
|
+
},
|
|
37
|
+
generateTokens,
|
|
38
|
+
requireVerifiedEmail,
|
|
39
|
+
enforceTotp
|
|
40
|
+
});
|
|
41
|
+
const existingAccount = await db.accounts.get(providerId, account.id);
|
|
42
|
+
if (existingAccount === null) return { kind: "invalidAccount" };
|
|
43
|
+
const [user, rateLimitState] = await Promise.all([db.users.getById(existingAccount.userId), getSignInRateLimitState(ctx, existingAccount._id, config)]);
|
|
44
|
+
if (user === null) {
|
|
45
|
+
log(LOG_LEVELS.ERROR, `Account ${existingAccount._id} links to missing user ${existingAccount.userId}`);
|
|
46
|
+
return { kind: "invalidAccount" };
|
|
47
|
+
}
|
|
48
|
+
if (isStateRateLimited(rateLimitState)) return { kind: "tooManyAttempts" };
|
|
49
|
+
if (!await withSpan("convex-auth.credentials.verify", { providerId }, () => verify(getProviderOrThrow(providerId), account.secret, existingAccount.secret ?? ""))) {
|
|
50
|
+
await recordFailedSignIn(ctx, existingAccount._id, config, rateLimitState);
|
|
51
|
+
return { kind: "invalidSecret" };
|
|
52
|
+
}
|
|
53
|
+
if (requireVerifiedEmail && !existingAccount.emailVerified) {
|
|
54
|
+
await resetSignInRateLimit(ctx, existingAccount._id, config, rateLimitState);
|
|
55
|
+
return {
|
|
56
|
+
kind: "emailVerificationRequired",
|
|
57
|
+
account: {
|
|
58
|
+
_id: existingAccount._id,
|
|
59
|
+
emailVerified: existingAccount.emailVerified
|
|
60
|
+
},
|
|
61
|
+
user: {
|
|
62
|
+
_id: user._id,
|
|
63
|
+
email: user.email
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
let hasTotp = user.hasTotp;
|
|
68
|
+
if (enforceTotp && hasTotp === void 0) {
|
|
69
|
+
const memberResolveRef = config.component.public["totpGetVerifiedByUserId"];
|
|
70
|
+
hasTotp = await ctx.runQuery(memberResolveRef, { userId: existingAccount.userId }) !== null;
|
|
71
|
+
await db.users.patch(existingAccount.userId, { hasTotp });
|
|
72
|
+
}
|
|
73
|
+
const totpRequired = enforceTotp && hasTotp === true;
|
|
74
|
+
const [issuance] = await Promise.all([issueSession(ctx, config, {
|
|
75
|
+
userId: existingAccount.userId,
|
|
76
|
+
generateTokens: generateTokens && !totpRequired
|
|
77
|
+
}), resetSignInRateLimit(ctx, existingAccount._id, config, rateLimitState)]);
|
|
78
|
+
if (totpRequired) return {
|
|
79
|
+
kind: "totpRequired",
|
|
80
|
+
issuance,
|
|
81
|
+
account: {
|
|
82
|
+
_id: existingAccount._id,
|
|
83
|
+
emailVerified: existingAccount.emailVerified
|
|
84
|
+
},
|
|
85
|
+
user: {
|
|
86
|
+
_id: user._id,
|
|
87
|
+
email: user.email
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
kind: "signedIn",
|
|
92
|
+
issuance,
|
|
93
|
+
account: {
|
|
94
|
+
_id: existingAccount._id,
|
|
95
|
+
emailVerified: existingAccount.emailVerified
|
|
96
|
+
},
|
|
97
|
+
user: {
|
|
98
|
+
_id: user._id,
|
|
99
|
+
email: user.email,
|
|
100
|
+
hasTotp
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** @internal */
|
|
105
|
+
const callCredentialsSignIn = async (ctx, args) => {
|
|
106
|
+
return await ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
107
|
+
type: "credentialsSignIn",
|
|
108
|
+
...args
|
|
109
|
+
} });
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
export { callCredentialsSignIn, credentialsSignInArgs, credentialsSignInImpl };
|
|
114
|
+
//# sourceMappingURL=credentialsSignIn.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { callModifyAccount } from "./account.js";
|
|
2
2
|
import { callCreateVerificationCode } from "./code.js";
|
|
3
|
+
import { callCredentialsSignIn } from "./credentialsSignIn.js";
|
|
3
4
|
import { callInvalidateSessions } from "./invalidate.js";
|
|
4
5
|
import { callUserOAuth } from "./oauth.js";
|
|
5
6
|
import { callRefreshSession } from "./refresh.js";
|
|
@@ -12,4 +13,4 @@ import { callVerifier } from "./verifier.js";
|
|
|
12
13
|
import { callVerifyCodeAndSignIn } from "./verify.js";
|
|
13
14
|
import { storeArgs, storeImpl } from "./store.js";
|
|
14
15
|
|
|
15
|
-
export { callCreateAccountFromCredentials, callCreateVerificationCode, callInvalidateSessions, callModifyAccount, callRefreshSession, callRetrieveAccountWithCredentials, callSignIn, callSignOut, callUserOAuth, callVerifier, callVerifierSignature, callVerifyCodeAndSignIn, storeArgs, storeImpl };
|
|
16
|
+
export { callCreateAccountFromCredentials, callCreateVerificationCode, callCredentialsSignIn, callInvalidateSessions, callModifyAccount, callRefreshSession, callRetrieveAccountWithCredentials, callSignIn, callSignOut, callUserOAuth, callVerifier, callVerifierSignature, callVerifyCodeAndSignIn, storeArgs, storeImpl };
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { authDb } from "../db.js";
|
|
2
2
|
import { log, maybeRedact } from "../log.js";
|
|
3
3
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
4
|
-
import { REFRESH_TOKEN_REUSE_WINDOW_MS, parseRefreshToken, refreshTokenExpirationTime } from "../refresh.js";
|
|
5
|
-
import { generateTokensForSession } from "../sessions.js";
|
|
4
|
+
import { REFRESH_TOKEN_DIVIDER, REFRESH_TOKEN_REUSE_WINDOW_MS, parseRefreshToken, refreshTokenExpirationTime } from "../refresh.js";
|
|
6
5
|
import { withSpan } from "../utils/span.js";
|
|
6
|
+
import { finalizeSessionIssuance } from "../sessions.js";
|
|
7
7
|
import { v } from "convex/values";
|
|
8
8
|
|
|
9
9
|
//#region src/server/mutations/refresh.ts
|
|
10
10
|
const refreshSessionArgs = v.object({ refreshToken: v.string() });
|
|
11
|
+
/**
|
|
12
|
+
* Exchange a refresh token and mint the next pair of session IDs + refresh
|
|
13
|
+
* token string. The RSA-2048 JWT signing used to live here; it has been
|
|
14
|
+
* moved to the action wrapper ({@link callRefreshSession}) so the mutation
|
|
15
|
+
* can commit without paying 5–30ms of CPU per refresh.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
11
19
|
async function refreshSessionImpl(ctx, args, config) {
|
|
12
20
|
const db = authDb(ctx, config);
|
|
13
21
|
return withSpan("convex-auth.refresh.session", { hasRefreshToken: true }, async () => {
|
|
@@ -33,15 +41,11 @@ async function refreshSessionImpl(ctx, args, config) {
|
|
|
33
41
|
throw new RefreshFailure("Failed to exchange refresh token");
|
|
34
42
|
}
|
|
35
43
|
if (exchanged === null) return null;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
} catch {
|
|
43
|
-
throw new RefreshFailure("Failed to generate refresh-session tokens");
|
|
44
|
-
}
|
|
44
|
+
return {
|
|
45
|
+
userId: exchanged.userId,
|
|
46
|
+
sessionId: exchanged.sessionId,
|
|
47
|
+
refreshToken: `${exchanged.refreshTokenId}${REFRESH_TOKEN_DIVIDER}${exchanged.sessionId}`
|
|
48
|
+
};
|
|
45
49
|
} catch (e) {
|
|
46
50
|
if (e instanceof RefreshFailure) {
|
|
47
51
|
log("DEBUG", e.reason);
|
|
@@ -57,11 +61,20 @@ var RefreshFailure = class extends Error {
|
|
|
57
61
|
this.reason = reason;
|
|
58
62
|
}
|
|
59
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* Action-side wrapper: exchange the refresh token via the mutation, then
|
|
66
|
+
* sign the next JWT outside the mutation transaction. See
|
|
67
|
+
* {@link refreshSessionImpl} for the rationale.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
60
71
|
const callRefreshSession = async (ctx, args) => {
|
|
61
|
-
|
|
72
|
+
const issuance = await ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
62
73
|
type: "refreshSession",
|
|
63
74
|
...args
|
|
64
75
|
} });
|
|
76
|
+
if (issuance === null || issuance.refreshToken === null) return null;
|
|
77
|
+
return (await finalizeSessionIssuance(ctx.auth.config, issuance)).tokens;
|
|
65
78
|
};
|
|
66
79
|
|
|
67
80
|
//#endregion
|
|
@@ -3,6 +3,7 @@ import { verify } from "../crypto.js";
|
|
|
3
3
|
import { authDb } from "../db.js";
|
|
4
4
|
import { log, maybeRedact } from "../log.js";
|
|
5
5
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
6
|
+
import { withSpan } from "../utils/span.js";
|
|
6
7
|
import { isSignInRateLimited, recordFailedSignIn, resetSignInRateLimit } from "../limits.js";
|
|
7
8
|
import { v } from "convex/values";
|
|
8
9
|
|
|
@@ -30,7 +31,7 @@ async function retrieveAccountWithCredentialsImpl(ctx, args, getProviderOrThrow,
|
|
|
30
31
|
if (account.secret !== void 0) {
|
|
31
32
|
const accountSecret = account.secret;
|
|
32
33
|
if (await isSignInRateLimited(ctx, existingAccount._id, config)) return "TooManyFailedAttempts";
|
|
33
|
-
if (!await verify(getProviderOrThrow(providerId), accountSecret, existingAccount.secret ?? "")) {
|
|
34
|
+
if (!await withSpan("convex-auth.credentials.verify", { providerId }, () => verify(getProviderOrThrow(providerId), accountSecret, existingAccount.secret ?? ""))) {
|
|
34
35
|
await recordFailedSignIn(ctx, existingAccount._id, config);
|
|
35
36
|
return "InvalidSecret";
|
|
36
37
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { LOG_LEVELS } from "../../shared/log.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
4
|
-
import {
|
|
4
|
+
import { withSpan } from "../utils/span.js";
|
|
5
|
+
import { finalizeSessionIssuance, getAuthSessionId, issueSession } from "../sessions.js";
|
|
5
6
|
import { v } from "convex/values";
|
|
6
7
|
|
|
7
8
|
//#region src/server/mutations/signin.ts
|
|
@@ -11,20 +12,37 @@ const signInArgs = v.object({
|
|
|
11
12
|
generateTokens: v.boolean()
|
|
12
13
|
});
|
|
13
14
|
async function signInImpl(ctx, args, config) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
return withSpan("convex-auth.mutations.signIn", {
|
|
16
|
+
hasExistingSession: args.sessionId !== void 0,
|
|
17
|
+
generateTokens: args.generateTokens
|
|
18
|
+
}, async () => {
|
|
19
|
+
log(LOG_LEVELS.DEBUG, "signInImpl args:", args);
|
|
20
|
+
const { userId, sessionId: existingSessionId, generateTokens } = args;
|
|
21
|
+
return await issueSession(ctx, config, {
|
|
22
|
+
userId,
|
|
23
|
+
existingSessionId,
|
|
24
|
+
replaceSessionId: existingSessionId === void 0 ? await getAuthSessionId(ctx) ?? void 0 : void 0,
|
|
25
|
+
generateTokens
|
|
26
|
+
});
|
|
21
27
|
});
|
|
22
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Run the sign-in mutation, then sign the JWT on the action side.
|
|
31
|
+
*
|
|
32
|
+
* Splitting the work like this keeps the 5–30ms of RSA-2048 CPU out of the
|
|
33
|
+
* mutation transaction so the mutation can commit quickly even on a cold
|
|
34
|
+
* worker. The refresh-token string itself is cheap to compute and stays
|
|
35
|
+
* inside the mutation (returned on the {@link SessionIssuance}); only the
|
|
36
|
+
* JWT signing moves here.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
23
40
|
const callSignIn = async (ctx, args) => {
|
|
24
|
-
|
|
41
|
+
const issuance = await ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
25
42
|
type: "signIn",
|
|
26
43
|
...args
|
|
27
44
|
} });
|
|
45
|
+
return await finalizeSessionIssuance(ctx.auth.config, issuance);
|
|
28
46
|
};
|
|
29
47
|
|
|
30
48
|
//#endregion
|
|
@@ -2,6 +2,7 @@ import { LOG_LEVELS } from "../../shared/log.js";
|
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { modifyAccountArgs, modifyAccountImpl } from "./account.js";
|
|
4
4
|
import { createVerificationCodeArgs, createVerificationCodeImpl } from "./code.js";
|
|
5
|
+
import { credentialsSignInArgs, credentialsSignInImpl } from "./credentialsSignIn.js";
|
|
5
6
|
import { invalidateSessionsArgs, invalidateSessionsImpl } from "./invalidate.js";
|
|
6
7
|
import { userOAuthArgs, userOAuthImpl } from "./oauth.js";
|
|
7
8
|
import { refreshSessionArgs } from "./refresh.js";
|
|
@@ -42,6 +43,9 @@ const storeArgs = v.object({ args: v.union(v.object({
|
|
|
42
43
|
}), v.object({
|
|
43
44
|
type: v.literal("retrieveAccountWithCredentials"),
|
|
44
45
|
...retrieveAccountWithCredentialsArgs.fields
|
|
46
|
+
}), v.object({
|
|
47
|
+
type: v.literal("credentialsSignIn"),
|
|
48
|
+
...credentialsSignInArgs.fields
|
|
45
49
|
}), v.object({
|
|
46
50
|
type: v.literal("modifyAccount"),
|
|
47
51
|
...modifyAccountArgs.fields
|
|
@@ -65,6 +69,7 @@ const storeImpl = async (ctx, fnArgs, services) => {
|
|
|
65
69
|
createVerificationCode: (a) => createVerificationCodeImpl(ctx, a, getProviderOrThrow, config),
|
|
66
70
|
createAccountFromCredentials: (a) => createAccountFromCredentialsImpl(ctx, a, getProviderOrThrow, config),
|
|
67
71
|
retrieveAccountWithCredentials: (a) => retrieveAccountWithCredentialsImpl(ctx, a, getProviderOrThrow, config),
|
|
72
|
+
credentialsSignIn: (a) => credentialsSignInImpl(ctx, a, getProviderOrThrow, config),
|
|
68
73
|
modifyAccount: (a) => modifyAccountImpl(ctx, a, getProviderOrThrow, config),
|
|
69
74
|
invalidateSessions: (a) => invalidateSessionsImpl(ctx, a, config)
|
|
70
75
|
}[args.type];
|
|
@@ -4,12 +4,13 @@ import { authDb } from "../db.js";
|
|
|
4
4
|
import { requireEnv } from "../env.js";
|
|
5
5
|
import { log } from "../log.js";
|
|
6
6
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
7
|
-
import {
|
|
7
|
+
import { withSpan } from "../utils/span.js";
|
|
8
|
+
import { finalizeSessionIssuance, getAuthSessionId, issueSession } from "../sessions.js";
|
|
8
9
|
import { upsertUserAndAccount } from "../users.js";
|
|
10
|
+
import { getSignInRateLimitState, isStateRateLimited, recordFailedSignIn, resetSignInRateLimit } from "../limits.js";
|
|
9
11
|
import { payloadRecordValidator } from "../payloads.js";
|
|
10
12
|
import { isGroupProviderId } from "../sso/shared.js";
|
|
11
13
|
import { createSyntheticOAuthMaterializedConfig } from "../sso/oidc.js";
|
|
12
|
-
import { isSignInRateLimited, recordFailedSignIn, resetSignInRateLimit } from "../limits.js";
|
|
13
14
|
import { v } from "convex/values";
|
|
14
15
|
|
|
15
16
|
//#region src/server/mutations/verify.ts
|
|
@@ -21,9 +22,17 @@ const verifyCodeAndSignInArgs = v.object({
|
|
|
21
22
|
allowExtraProviders: v.boolean()
|
|
22
23
|
});
|
|
23
24
|
async function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
25
|
+
return withSpan("convex-auth.mutations.verifyCodeAndSignIn", {
|
|
26
|
+
provider: args.provider ?? "",
|
|
27
|
+
generateTokens: args.generateTokens
|
|
28
|
+
}, () => verifyCodeAndSignInImplInner(ctx, args, getProviderOrThrow, config));
|
|
29
|
+
}
|
|
30
|
+
async function verifyCodeAndSignInImplInner(ctx, args, getProviderOrThrow, config) {
|
|
24
31
|
const params = args.params;
|
|
25
32
|
const { generateTokens, provider, allowExtraProviders } = args;
|
|
26
33
|
const identifier = typeof params.email === "string" ? params.email : typeof params.phone === "string" ? params.phone : void 0;
|
|
34
|
+
let rateLimitState = null;
|
|
35
|
+
let rateLimitLoaded = false;
|
|
27
36
|
try {
|
|
28
37
|
log(LOG_LEVELS.DEBUG, "verifyCodeAndSignInImpl args:", {
|
|
29
38
|
params: {
|
|
@@ -41,7 +50,9 @@ async function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
|
41
50
|
requireEnv("CONVEX_SITE_URL");
|
|
42
51
|
}
|
|
43
52
|
if (identifier !== void 0) {
|
|
44
|
-
|
|
53
|
+
rateLimitState = await getSignInRateLimitState(ctx, identifier, config);
|
|
54
|
+
rateLimitLoaded = true;
|
|
55
|
+
if (isStateRateLimited(rateLimitState)) throw new VerifyFailure("Too many failed attempts to verify code for this email");
|
|
45
56
|
}
|
|
46
57
|
const db = authDb(ctx, config);
|
|
47
58
|
const verifier = args.verifier;
|
|
@@ -50,7 +61,6 @@ async function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
|
50
61
|
const hash = await sha256(codeValue);
|
|
51
62
|
const code = await db.verificationCodes.getByCode(hash);
|
|
52
63
|
if (code === null) throw new VerifyFailure("Invalid verification code");
|
|
53
|
-
await db.verificationCodes.delete(code._id);
|
|
54
64
|
if (code.verifier !== verifier) throw new VerifyFailure("Invalid verifier");
|
|
55
65
|
if (code.expirationTime < Date.now()) throw new VerifyFailure("Expired verification code");
|
|
56
66
|
if (provider !== void 0 && code.provider !== provider) throw new VerifyFailure(`Invalid provider "${provider}" for given \`code\``);
|
|
@@ -73,16 +83,20 @@ async function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
|
73
83
|
} : {}
|
|
74
84
|
}
|
|
75
85
|
}, config)).userId;
|
|
76
|
-
|
|
86
|
+
const [, , replaceSessionId] = await Promise.all([
|
|
87
|
+
db.verificationCodes.delete(code._id),
|
|
88
|
+
identifier !== void 0 ? resetSignInRateLimit(ctx, identifier, config, rateLimitState) : Promise.resolve(),
|
|
89
|
+
getAuthSessionId(ctx)
|
|
90
|
+
]);
|
|
77
91
|
return await issueSession(ctx, config, {
|
|
78
92
|
userId,
|
|
79
|
-
replaceSessionId:
|
|
93
|
+
replaceSessionId: replaceSessionId ?? void 0,
|
|
80
94
|
generateTokens
|
|
81
95
|
});
|
|
82
96
|
} catch (error) {
|
|
83
97
|
if (error instanceof VerifyFailure) {
|
|
84
98
|
log(LOG_LEVELS.ERROR, error.reason);
|
|
85
|
-
if (identifier !== void 0) await recordFailedSignIn(ctx, identifier, config);
|
|
99
|
+
if (identifier !== void 0) await recordFailedSignIn(ctx, identifier, config, rateLimitLoaded ? rateLimitState : void 0);
|
|
86
100
|
return null;
|
|
87
101
|
}
|
|
88
102
|
throw error;
|
|
@@ -98,11 +112,21 @@ var VerifyFailure = class extends Error {
|
|
|
98
112
|
this.name = "VerifyFailure";
|
|
99
113
|
}
|
|
100
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* Run the verify-code-and-sign-in mutation, then sign the JWT on the action
|
|
117
|
+
* side. See {@link callSignIn} for the rationale — the mutation returns
|
|
118
|
+
* `SessionIssuance` (cheap string-encoded refresh token + IDs), and this
|
|
119
|
+
* wrapper does the RSA-2048 work outside the mutation transaction.
|
|
120
|
+
*
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
101
123
|
const callVerifyCodeAndSignIn = async (ctx, args) => {
|
|
102
|
-
|
|
124
|
+
const issuance = await ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
103
125
|
type: "verifyCodeAndSignIn",
|
|
104
126
|
...args
|
|
105
127
|
} });
|
|
128
|
+
if (issuance === null) return null;
|
|
129
|
+
return await finalizeSessionIssuance(ctx.auth.config, issuance);
|
|
106
130
|
};
|
|
107
131
|
|
|
108
132
|
//#endregion
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ComponentCtx, ComponentReadCtx } from "./componentContext.js";
|
|
2
|
-
import { AuthProviderConfig, ConvexAuthConfig, CorsConfig, HttpKeyContext, KeyDoc, KeyScope, ScopeChecker, UserOrderBy, UserWhere } from "./types.js";
|
|
2
|
+
import { AuthProviderConfig, ConvexAuthConfig, CorsConfig, Doc, HttpKeyContext, KeyDoc, KeyScope, ScopeChecker, UserOrderBy, UserWhere } from "./types.js";
|
|
3
3
|
import { AuthProfile, SignInParams } from "./payloads.js";
|
|
4
4
|
import { HttpAuthContext, HttpAuthContextConfig, OptionalHttpAuthContext } from "./http.js";
|
|
5
5
|
import { createGroupConnectionDomain } from "./sso/domain.js";
|
|
6
|
-
import * as
|
|
7
|
-
import * as
|
|
6
|
+
import * as convex_values1137 from "convex/values";
|
|
7
|
+
import * as convex_server108 from "convex/server";
|
|
8
8
|
import { GenericActionCtx, GenericDataModel, HttpRouter } from "convex/server";
|
|
9
9
|
|
|
10
10
|
//#region src/server/runtime.d.ts
|
|
@@ -32,7 +32,10 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
32
32
|
*/
|
|
33
33
|
auth: {
|
|
34
34
|
user: {
|
|
35
|
-
get:
|
|
35
|
+
get: {
|
|
36
|
+
(ctx: ComponentReadCtx, userId: string): Promise<Doc<"User"> | null>;
|
|
37
|
+
(ctx: ComponentReadCtx, userIds: readonly string[]): Promise<Array<Doc<"User"> | null>>;
|
|
38
|
+
};
|
|
36
39
|
list: (ctx: ComponentReadCtx, opts?: {
|
|
37
40
|
where?: UserWhere;
|
|
38
41
|
limit?: number;
|
|
@@ -41,8 +44,8 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
41
44
|
order?: "asc" | "desc";
|
|
42
45
|
}) => Promise<any>;
|
|
43
46
|
viewer: (ctx: ComponentReadCtx & {
|
|
44
|
-
auth:
|
|
45
|
-
}) => Promise<
|
|
47
|
+
auth: convex_server108.Auth;
|
|
48
|
+
}) => Promise<Doc<"User"> | null>;
|
|
46
49
|
update: (ctx: ComponentCtx, userId: string, data: Record<string, unknown>) => Promise<{
|
|
47
50
|
userId: string;
|
|
48
51
|
}>;
|
|
@@ -67,14 +70,17 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
67
70
|
};
|
|
68
71
|
session: {
|
|
69
72
|
current: (ctx: {
|
|
70
|
-
auth:
|
|
71
|
-
}) => Promise<
|
|
73
|
+
auth: convex_server108.Auth;
|
|
74
|
+
}) => Promise<convex_values1137.GenericId<"Session"> | null>;
|
|
75
|
+
userId: (ctx: {
|
|
76
|
+
auth: convex_server108.Auth;
|
|
77
|
+
}) => Promise<convex_values1137.GenericId<"User"> | null>;
|
|
72
78
|
invalidate: <DataModel extends GenericDataModel>(ctx: GenericActionCtx<DataModel>, args: {
|
|
73
|
-
userId:
|
|
74
|
-
except?:
|
|
79
|
+
userId: convex_values1137.GenericId<"User">;
|
|
80
|
+
except?: convex_values1137.GenericId<"Session">[];
|
|
75
81
|
}) => Promise<{
|
|
76
|
-
userId:
|
|
77
|
-
except:
|
|
82
|
+
userId: convex_values1137.GenericId<"User">;
|
|
83
|
+
except: convex_values1137.GenericId<"Session">[];
|
|
78
84
|
}>;
|
|
79
85
|
get: (ctx: ComponentReadCtx, sessionId: string) => Promise<any>;
|
|
80
86
|
list: (ctx: ComponentReadCtx, opts: {
|
|
@@ -143,7 +149,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
143
149
|
};
|
|
144
150
|
provider: {
|
|
145
151
|
signIn: (<DataModel extends GenericDataModel>(ctx: GenericActionCtx<DataModel>, providerConfig: AuthProviderConfig, args: {
|
|
146
|
-
accountId?:
|
|
152
|
+
accountId?: convex_values1137.GenericId<"Account">;
|
|
147
153
|
params?: SignInParams;
|
|
148
154
|
}) => Promise<{
|
|
149
155
|
userId: string;
|
|
@@ -164,7 +170,10 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
164
170
|
}) => Promise<{
|
|
165
171
|
groupId: string;
|
|
166
172
|
}>;
|
|
167
|
-
get:
|
|
173
|
+
get: {
|
|
174
|
+
(ctx: ComponentReadCtx, groupId: string): Promise<Doc<"Group"> | null>;
|
|
175
|
+
(ctx: ComponentReadCtx, groupIds: readonly string[]): Promise<Array<Doc<"Group"> | null>>;
|
|
176
|
+
};
|
|
168
177
|
list: (ctx: ComponentReadCtx, opts?: {
|
|
169
178
|
where?: {
|
|
170
179
|
slug?: string;
|
|
@@ -197,7 +206,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
197
206
|
maxDepth?: number;
|
|
198
207
|
includeSelf?: boolean;
|
|
199
208
|
}) => Promise<{
|
|
200
|
-
ancestors:
|
|
209
|
+
ancestors: Array<Exclude<Doc<"Group"> | null, null>>;
|
|
201
210
|
cycleDetected: boolean;
|
|
202
211
|
maxDepthReached: boolean;
|
|
203
212
|
}>;
|
|
@@ -231,29 +240,44 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
231
240
|
update: (ctx: ComponentCtx, memberId: string, data: Record<string, unknown>) => Promise<{
|
|
232
241
|
memberId: string;
|
|
233
242
|
}>;
|
|
234
|
-
inspect:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
ancestry?: boolean;
|
|
238
|
-
maxDepth?: number;
|
|
239
|
-
}) => Promise<{
|
|
240
|
-
membership: null;
|
|
241
|
-
roleIds: string[];
|
|
242
|
-
grants: string[];
|
|
243
|
-
} | {
|
|
244
|
-
membership: {
|
|
245
|
-
_id: string;
|
|
246
|
-
_creationTime: number;
|
|
243
|
+
inspect: {
|
|
244
|
+
(ctx: ComponentReadCtx, opts: {
|
|
245
|
+
userId: string;
|
|
247
246
|
groupId: string;
|
|
247
|
+
ancestry?: boolean;
|
|
248
|
+
maxDepth?: number;
|
|
249
|
+
}): Promise<{
|
|
250
|
+
membership: {
|
|
251
|
+
_id: string;
|
|
252
|
+
_creationTime: number;
|
|
253
|
+
groupId: string;
|
|
254
|
+
userId: string;
|
|
255
|
+
role?: string;
|
|
256
|
+
roleIds?: string[];
|
|
257
|
+
status?: string;
|
|
258
|
+
extend?: Record<string, unknown>;
|
|
259
|
+
} | null;
|
|
260
|
+
roleIds: string[];
|
|
261
|
+
grants: string[];
|
|
262
|
+
}>;
|
|
263
|
+
(ctx: ComponentReadCtx, opts: {
|
|
248
264
|
userId: string;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
groupIds: readonly string[];
|
|
266
|
+
}): Promise<Array<{
|
|
267
|
+
membership: {
|
|
268
|
+
_id: string;
|
|
269
|
+
_creationTime: number;
|
|
270
|
+
groupId: string;
|
|
271
|
+
userId: string;
|
|
272
|
+
role?: string;
|
|
273
|
+
roleIds?: string[];
|
|
274
|
+
status?: string;
|
|
275
|
+
extend?: Record<string, unknown>;
|
|
276
|
+
} | null;
|
|
277
|
+
roleIds: string[];
|
|
278
|
+
grants: string[];
|
|
279
|
+
}>>;
|
|
280
|
+
};
|
|
257
281
|
require: (ctx: ComponentReadCtx, opts: {
|
|
258
282
|
userId: string;
|
|
259
283
|
groupId: string;
|
|
@@ -271,7 +295,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
271
295
|
roleIds?: string[];
|
|
272
296
|
status?: string;
|
|
273
297
|
extend?: Record<string, unknown>;
|
|
274
|
-
};
|
|
298
|
+
} | null;
|
|
275
299
|
roleIds: string[];
|
|
276
300
|
grants: string[];
|
|
277
301
|
}>;
|
|
@@ -433,22 +457,22 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
433
457
|
context: {
|
|
434
458
|
<TResolve extends Record<string, unknown> = Record<string, never>, TCtx extends {
|
|
435
459
|
auth: {
|
|
436
|
-
getUserIdentity: () => Promise<
|
|
460
|
+
getUserIdentity: () => Promise<convex_server108.UserIdentity | null>;
|
|
437
461
|
};
|
|
438
462
|
} & ComponentReadCtx = {
|
|
439
463
|
auth: {
|
|
440
|
-
getUserIdentity: () => Promise<
|
|
464
|
+
getUserIdentity: () => Promise<convex_server108.UserIdentity | null>;
|
|
441
465
|
};
|
|
442
466
|
} & ComponentReadCtx>(ctx: TCtx, request: Request, config: HttpAuthContextConfig<TResolve, TCtx> & {
|
|
443
467
|
optional: true;
|
|
444
468
|
}): Promise<OptionalHttpAuthContext & TResolve>;
|
|
445
469
|
<TResolve extends Record<string, unknown> = Record<string, never>, TCtx extends {
|
|
446
470
|
auth: {
|
|
447
|
-
getUserIdentity: () => Promise<
|
|
471
|
+
getUserIdentity: () => Promise<convex_server108.UserIdentity | null>;
|
|
448
472
|
};
|
|
449
473
|
} & ComponentReadCtx = {
|
|
450
474
|
auth: {
|
|
451
|
-
getUserIdentity: () => Promise<
|
|
475
|
+
getUserIdentity: () => Promise<convex_server108.UserIdentity | null>;
|
|
452
476
|
};
|
|
453
477
|
} & ComponentReadCtx>(ctx: TCtx, request: Request, config?: HttpAuthContextConfig<TResolve, TCtx>): Promise<HttpAuthContext & TResolve>;
|
|
454
478
|
};
|
|
@@ -482,7 +506,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
482
506
|
action: string;
|
|
483
507
|
};
|
|
484
508
|
cors?: CorsConfig;
|
|
485
|
-
}) =>
|
|
509
|
+
}) => convex_server108.PublicHttpAction;
|
|
486
510
|
/**
|
|
487
511
|
* Register a Bearer-authenticated route **and** its OPTIONS preflight
|
|
488
512
|
* in a single call.
|
|
@@ -528,7 +552,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
528
552
|
*
|
|
529
553
|
* Also used for refreshing the session.
|
|
530
554
|
*/
|
|
531
|
-
signIn:
|
|
555
|
+
signIn: convex_server108.RegisteredAction<"public", {
|
|
532
556
|
provider?: string | undefined;
|
|
533
557
|
verifier?: string | undefined;
|
|
534
558
|
params?: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null> | undefined;
|
|
@@ -538,12 +562,12 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
538
562
|
/**
|
|
539
563
|
* Action called by the client to invalidate the current session.
|
|
540
564
|
*/
|
|
541
|
-
signOut:
|
|
565
|
+
signOut: convex_server108.RegisteredAction<"public", {}, Promise<void>>;
|
|
542
566
|
/**
|
|
543
567
|
* Internal mutation used by the library to read and write
|
|
544
568
|
* to the database during signin and signout.
|
|
545
569
|
*/
|
|
546
|
-
store:
|
|
570
|
+
store: convex_server108.RegisteredMutation<"internal", {
|
|
547
571
|
args: {
|
|
548
572
|
sessionId?: string | undefined;
|
|
549
573
|
type: "signIn";
|
|
@@ -586,11 +610,11 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
586
610
|
sessionIndex?: string | undefined;
|
|
587
611
|
} | undefined;
|
|
588
612
|
} | undefined;
|
|
589
|
-
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
590
613
|
type: "userOAuth";
|
|
591
614
|
provider: string;
|
|
592
615
|
providerAccountId: string;
|
|
593
616
|
signature: string;
|
|
617
|
+
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
594
618
|
} | {
|
|
595
619
|
email?: string | undefined;
|
|
596
620
|
phone?: string | undefined;
|
|
@@ -603,9 +627,9 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
603
627
|
} | {
|
|
604
628
|
shouldLinkViaEmail?: boolean | undefined;
|
|
605
629
|
shouldLinkViaPhone?: boolean | undefined;
|
|
606
|
-
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
607
630
|
type: "createAccountFromCredentials";
|
|
608
631
|
provider: string;
|
|
632
|
+
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
609
633
|
account: {
|
|
610
634
|
secret?: string | undefined;
|
|
611
635
|
id: string;
|
|
@@ -617,6 +641,16 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
617
641
|
secret?: string | undefined;
|
|
618
642
|
id: string;
|
|
619
643
|
};
|
|
644
|
+
} | {
|
|
645
|
+
type: "credentialsSignIn";
|
|
646
|
+
provider: string;
|
|
647
|
+
generateTokens: boolean;
|
|
648
|
+
account: {
|
|
649
|
+
id: string;
|
|
650
|
+
secret: string;
|
|
651
|
+
};
|
|
652
|
+
requireVerifiedEmail: boolean;
|
|
653
|
+
enforceTotp: boolean;
|
|
620
654
|
} | {
|
|
621
655
|
type: "modifyAccount";
|
|
622
656
|
provider: string;
|