@robelest/convex-auth 0.0.4-preview.27 → 0.0.4-preview.28
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 +3 -5
- package/dist/bin.js +6488 -1571
- package/dist/browser/index.js +10 -7
- package/dist/browser/locks.js +3 -5
- package/dist/browser/navigation.js +7 -10
- package/dist/browser/runtime.js +35 -33
- package/dist/client/core/types.js +17 -0
- package/dist/client/factors/device.js +26 -19
- package/dist/client/index.js +151 -163
- package/dist/client/runtime/proxy.js +6 -6
- package/dist/client/services/adapters.js +3 -7
- package/dist/client/services/http.js +2 -5
- package/dist/client/services/resolve.js +5 -11
- package/dist/client/services/runtime.js +2 -5
- package/dist/component/_generated/component.d.ts +46 -0
- package/dist/component/index.d.ts +3 -3
- package/dist/component/model.d.ts +25 -25
- package/dist/component/public/identity/sessions.js +38 -1
- package/dist/component/public/identity/tokens.js +81 -3
- package/dist/component/public/identity/verifiers.js +9 -3
- package/dist/component/public.js +3 -3
- package/dist/component/schema.d.ts +320 -320
- package/dist/core/index.d.ts +380 -0
- package/dist/core/index.js +83 -0
- package/dist/otel.d.ts +13 -17
- package/dist/otel.js +39 -49
- package/dist/providers/email.d.ts +2 -2
- package/dist/providers/password.js +8 -16
- package/dist/providers/phone.js +2 -9
- package/dist/server/auth-context.d.ts +204 -0
- package/dist/server/auth-context.js +76 -0
- package/dist/server/auth.d.ts +25 -187
- package/dist/server/auth.js +5 -96
- package/dist/server/componentContext.d.ts +12 -0
- package/dist/server/componentContext.js +1 -0
- package/dist/server/config.js +1 -12
- package/dist/server/constants.js +6 -0
- package/dist/server/contract.d.ts +1 -1
- package/dist/server/core.js +5 -14
- package/dist/server/crypto.js +26 -18
- package/dist/server/db.js +6 -1
- package/dist/server/device.js +88 -78
- package/dist/server/http.d.ts +4 -3
- package/dist/server/http.js +74 -86
- package/dist/server/index.d.ts +2 -1
- package/dist/server/limits.js +22 -15
- package/dist/server/mounts.d.ts +103 -103
- package/dist/server/mutations/account.js +6 -4
- package/dist/server/mutations/invalidate.js +3 -6
- package/dist/server/mutations/oauth.js +86 -88
- package/dist/server/mutations/refresh.js +45 -87
- package/dist/server/mutations/register.js +19 -19
- package/dist/server/mutations/retrieve.js +17 -15
- package/dist/server/mutations/signature.js +9 -13
- package/dist/server/mutations/signin.js +7 -3
- package/dist/server/mutations/signout.js +10 -15
- package/dist/server/mutations/store.js +22 -12
- package/dist/server/mutations/verifier.js +11 -6
- package/dist/server/mutations/verify.js +55 -46
- package/dist/server/oauth/runtime.js +27 -25
- package/dist/server/passkey.js +299 -250
- package/dist/server/prefetch.js +283 -281
- package/dist/server/refresh.js +7 -60
- package/dist/server/runtime.d.ts +82 -206
- package/dist/server/runtime.js +63 -56
- package/dist/server/services/config.js +5 -3
- package/dist/server/services/logger.js +2 -4
- package/dist/server/services/providers.js +2 -4
- package/dist/server/services/refresh.js +2 -4
- package/dist/server/services/resolve.js +15 -14
- package/dist/server/services/signin.js +2 -4
- package/dist/server/sessions.js +32 -33
- package/dist/server/signin.js +177 -142
- package/dist/server/sso/domain.d.ts +20 -68
- package/dist/server/sso/domain.js +444 -413
- package/dist/server/sso/http.js +53 -59
- package/dist/server/sso/oidc.js +94 -80
- package/dist/server/tokens.js +13 -3
- package/dist/server/totp.js +153 -116
- package/dist/server/types.d.ts +2 -2
- package/dist/server/users.js +18 -23
- package/dist/server/utils/cache.js +51 -0
- package/dist/server/utils/dispatch.js +36 -0
- package/dist/server/utils/retry.js +24 -0
- package/dist/server/utils/span.js +32 -0
- package/dist/shared/errors.js +9 -3
- package/dist/shared/log.js +20 -22
- package/package.json +41 -33
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { authDb } from "../db.js";
|
|
2
2
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
3
3
|
import { deleteSession, getAuthSessionId } from "../sessions.js";
|
|
4
|
-
import { Effect, Option, pipe } from "effect";
|
|
5
4
|
|
|
6
5
|
//#region src/server/mutations/signout.ts
|
|
7
|
-
function signOutImpl(ctx, config) {
|
|
6
|
+
async function signOutImpl(ctx, config) {
|
|
8
7
|
const db = authDb(ctx, config);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
})
|
|
19
|
-
})))
|
|
20
|
-
}));
|
|
21
|
-
});
|
|
8
|
+
const sessionId = await getAuthSessionId(ctx);
|
|
9
|
+
if (sessionId == null) return null;
|
|
10
|
+
const session = await db.sessions.getById(sessionId);
|
|
11
|
+
if (session == null) return null;
|
|
12
|
+
await deleteSession(ctx, session, config);
|
|
13
|
+
return {
|
|
14
|
+
userId: session.userId,
|
|
15
|
+
sessionId: session._id
|
|
16
|
+
};
|
|
22
17
|
}
|
|
23
18
|
const callSignOut = async (ctx) => {
|
|
24
19
|
return ctx.runMutation(AUTH_STORE_REF, { args: { type: "signOut" } });
|
|
@@ -10,10 +10,9 @@ import { retrieveAccountWithCredentialsArgs, retrieveAccountWithCredentialsImpl
|
|
|
10
10
|
import { verifierSignatureArgs, verifierSignatureImpl } from "./signature.js";
|
|
11
11
|
import { signInArgs, signInImpl } from "./signin.js";
|
|
12
12
|
import { signOutImpl } from "./signout.js";
|
|
13
|
-
import { verifierImpl } from "./verifier.js";
|
|
13
|
+
import { verifierArgs, verifierImpl } from "./verifier.js";
|
|
14
14
|
import { verifyCodeAndSignInArgs, verifyCodeAndSignInImpl } from "./verify.js";
|
|
15
15
|
import { v } from "convex/values";
|
|
16
|
-
import { Cause, Effect, Exit, Match } from "effect";
|
|
17
16
|
|
|
18
17
|
//#region src/server/mutations/store.ts
|
|
19
18
|
const storeArgs = v.object({ args: v.union(v.object({
|
|
@@ -25,7 +24,10 @@ const storeArgs = v.object({ args: v.union(v.object({
|
|
|
25
24
|
}), v.object({
|
|
26
25
|
type: v.literal("verifyCodeAndSignIn"),
|
|
27
26
|
...verifyCodeAndSignInArgs.fields
|
|
28
|
-
}), v.object({
|
|
27
|
+
}), v.object({
|
|
28
|
+
type: v.literal("verifier"),
|
|
29
|
+
...verifierArgs.fields
|
|
30
|
+
}), v.object({
|
|
29
31
|
type: v.literal("verifierSignature"),
|
|
30
32
|
...verifierSignatureArgs.fields
|
|
31
33
|
}), v.object({
|
|
@@ -51,15 +53,23 @@ const storeImpl = async (ctx, fnArgs, services) => {
|
|
|
51
53
|
const args = fnArgs.args;
|
|
52
54
|
const config = services.config;
|
|
53
55
|
const getProviderOrThrow = services.providerRegistry.getProviderOrThrow;
|
|
54
|
-
log(LOG_LEVELS.
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
log(LOG_LEVELS.DEBUG, `\`auth:store\` type: ${args.type}`);
|
|
57
|
+
const handler = {
|
|
58
|
+
signIn: (a) => signInImpl(ctx, a, config),
|
|
59
|
+
signOut: () => signOutImpl(ctx, config),
|
|
60
|
+
refreshSession: (a) => services.refresh.refresh(ctx, a),
|
|
61
|
+
verifyCodeAndSignIn: (a) => verifyCodeAndSignInImpl(ctx, a, getProviderOrThrow, config),
|
|
62
|
+
verifier: (a) => verifierImpl(ctx, a, config),
|
|
63
|
+
verifierSignature: (a) => verifierSignatureImpl(ctx, a, config),
|
|
64
|
+
userOAuth: (a) => userOAuthImpl(ctx, a, getProviderOrThrow, config),
|
|
65
|
+
createVerificationCode: (a) => createVerificationCodeImpl(ctx, a, getProviderOrThrow, config),
|
|
66
|
+
createAccountFromCredentials: (a) => createAccountFromCredentialsImpl(ctx, a, getProviderOrThrow, config),
|
|
67
|
+
retrieveAccountWithCredentials: (a) => retrieveAccountWithCredentialsImpl(ctx, a, getProviderOrThrow, config),
|
|
68
|
+
modifyAccount: (a) => modifyAccountImpl(ctx, a, getProviderOrThrow, config),
|
|
69
|
+
invalidateSessions: (a) => invalidateSessionsImpl(ctx, a, config)
|
|
70
|
+
}[args.type];
|
|
71
|
+
if (!handler) throw new Error(`Unknown store type: "${args.type}"`);
|
|
72
|
+
return await handler(args);
|
|
63
73
|
};
|
|
64
74
|
|
|
65
75
|
//#endregion
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { authDb } from "../db.js";
|
|
2
2
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
3
3
|
import { getAuthSessionId } from "../sessions.js";
|
|
4
|
-
import {
|
|
4
|
+
import { v } from "convex/values";
|
|
5
5
|
|
|
6
6
|
//#region src/server/mutations/verifier.ts
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const verifierArgs = v.object({ signature: v.optional(v.string()) });
|
|
8
|
+
async function verifierImpl(ctx, args, config) {
|
|
9
|
+
const sessionId = await getAuthSessionId(ctx);
|
|
10
|
+
return await authDb(ctx, config).verifiers.create(sessionId ?? void 0, args.signature);
|
|
9
11
|
}
|
|
10
|
-
const callVerifier = async (ctx) => {
|
|
11
|
-
return ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
12
|
+
const callVerifier = async (ctx, signature) => {
|
|
13
|
+
return ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
14
|
+
type: "verifier",
|
|
15
|
+
...signature === void 0 ? {} : { signature }
|
|
16
|
+
} });
|
|
12
17
|
};
|
|
13
18
|
|
|
14
19
|
//#endregion
|
|
15
|
-
export { callVerifier, verifierImpl };
|
|
20
|
+
export { callVerifier, verifierArgs, verifierImpl };
|
|
16
21
|
//# sourceMappingURL=verifier.js.map
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { LOG_LEVELS } from "../../shared/log.js";
|
|
2
|
-
import { requireEnv } from "../env.js";
|
|
3
2
|
import { sha256 } from "../random.js";
|
|
4
3
|
import { authDb } from "../db.js";
|
|
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 { getAuthSessionId, issueSession } from "../sessions.js";
|
|
8
8
|
import { upsertUserAndAccount } from "../users.js";
|
|
9
9
|
import { payloadRecordValidator } from "../payloads.js";
|
|
10
10
|
import { isGroupProviderId } from "../sso/shared.js";
|
|
11
11
|
import { createSyntheticOAuthMaterializedConfig } from "../sso/oidc.js";
|
|
12
12
|
import { isSignInRateLimited, recordFailedSignIn, resetSignInRateLimit } from "../limits.js";
|
|
13
13
|
import { v } from "convex/values";
|
|
14
|
-
import { Data, Effect } from "effect";
|
|
15
14
|
|
|
16
15
|
//#region src/server/mutations/verify.ts
|
|
17
16
|
const verifyCodeAndSignInArgs = v.object({
|
|
@@ -21,50 +20,46 @@ const verifyCodeAndSignInArgs = v.object({
|
|
|
21
20
|
generateTokens: v.boolean(),
|
|
22
21
|
allowExtraProviders: v.boolean()
|
|
23
22
|
});
|
|
24
|
-
|
|
25
|
-
var VerifyFailure = class extends Data.TaggedError("VerifyFailure") {};
|
|
26
|
-
function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
23
|
+
async function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
27
24
|
const params = args.params;
|
|
28
25
|
const { generateTokens, provider, allowExtraProviders } = args;
|
|
29
26
|
const identifier = typeof params.email === "string" ? params.email : typeof params.phone === "string" ? params.phone : void 0;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
allowExtraProviders: args.allowExtraProviders
|
|
41
|
-
});
|
|
42
|
-
if (generateTokens) {
|
|
43
|
-
requireEnv("JWT_PRIVATE_KEY");
|
|
44
|
-
requireEnv("JWKS");
|
|
45
|
-
requireEnv("CONVEX_SITE_URL");
|
|
46
|
-
}
|
|
27
|
+
try {
|
|
28
|
+
log(LOG_LEVELS.DEBUG, "verifyCodeAndSignInImpl args:", {
|
|
29
|
+
params: {
|
|
30
|
+
email: params.email,
|
|
31
|
+
phone: params.phone
|
|
32
|
+
},
|
|
33
|
+
provider: args.provider,
|
|
34
|
+
verifier: args.verifier,
|
|
35
|
+
generateTokens: args.generateTokens,
|
|
36
|
+
allowExtraProviders: args.allowExtraProviders
|
|
47
37
|
});
|
|
38
|
+
if (generateTokens) {
|
|
39
|
+
requireEnv("JWT_PRIVATE_KEY");
|
|
40
|
+
requireEnv("JWKS");
|
|
41
|
+
requireEnv("CONVEX_SITE_URL");
|
|
42
|
+
}
|
|
48
43
|
if (identifier !== void 0) {
|
|
49
|
-
if (
|
|
44
|
+
if (await isSignInRateLimited(ctx, identifier, config)) throw new VerifyFailure("Too many failed attempts to verify code for this email");
|
|
50
45
|
}
|
|
51
46
|
const db = authDb(ctx, config);
|
|
52
47
|
const verifier = args.verifier;
|
|
53
48
|
const codeValue = params.code;
|
|
54
|
-
if (typeof codeValue !== "string")
|
|
55
|
-
const hash =
|
|
56
|
-
const code =
|
|
57
|
-
if (code === null)
|
|
58
|
-
|
|
59
|
-
if (code.verifier !== verifier)
|
|
60
|
-
if (code.expirationTime < Date.now())
|
|
61
|
-
if (provider !== void 0 && code.provider !== provider)
|
|
62
|
-
const account =
|
|
63
|
-
if (account === null)
|
|
49
|
+
if (typeof codeValue !== "string") throw new VerifyFailure("Invalid verification code");
|
|
50
|
+
const hash = await sha256(codeValue);
|
|
51
|
+
const code = await db.verificationCodes.getByCode(hash);
|
|
52
|
+
if (code === null) throw new VerifyFailure("Invalid verification code");
|
|
53
|
+
await db.verificationCodes.delete(code._id);
|
|
54
|
+
if (code.verifier !== verifier) throw new VerifyFailure("Invalid verifier");
|
|
55
|
+
if (code.expirationTime < Date.now()) throw new VerifyFailure("Expired verification code");
|
|
56
|
+
if (provider !== void 0 && code.provider !== provider) throw new VerifyFailure(`Invalid provider "${provider}" for given \`code\``);
|
|
57
|
+
const account = await db.accounts.getById(code.accountId);
|
|
58
|
+
if (account === null) throw new VerifyFailure("Account associated with this email has been deleted");
|
|
64
59
|
const codeProvider = isGroupProviderId(code.provider) ? createSyntheticOAuthMaterializedConfig(code.provider) : getProviderOrThrow(code.provider, allowExtraProviders);
|
|
65
|
-
if (codeProvider !== null && (codeProvider.type === "email" || codeProvider.type === "phone") && codeProvider.authorize !== void 0)
|
|
60
|
+
if (codeProvider !== null && (codeProvider.type === "email" || codeProvider.type === "phone") && codeProvider.authorize !== void 0) await codeProvider.authorize(params, account);
|
|
66
61
|
const methodProvider = isGroupProviderId(account.provider) ? createSyntheticOAuthMaterializedConfig(account.provider) : getProviderOrThrow(account.provider);
|
|
67
|
-
const userId = methodProvider.type === "oauth" ? account.userId : (
|
|
62
|
+
const userId = methodProvider.type === "oauth" ? account.userId : (await upsertUserAndAccount(ctx, await getAuthSessionId(ctx), { existingAccount: account }, {
|
|
68
63
|
type: "verification",
|
|
69
64
|
provider: methodProvider,
|
|
70
65
|
profile: {
|
|
@@ -77,18 +72,32 @@ function verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config) {
|
|
|
77
72
|
phoneVerified: true
|
|
78
73
|
} : {}
|
|
79
74
|
}
|
|
80
|
-
}, config))
|
|
81
|
-
if (identifier !== void 0)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
log(LOG_LEVELS.ERROR, error.reason);
|
|
75
|
+
}, config)).userId;
|
|
76
|
+
if (identifier !== void 0) await resetSignInRateLimit(ctx, identifier, config);
|
|
77
|
+
return await issueSession(ctx, config, {
|
|
78
|
+
userId,
|
|
79
|
+
replaceSessionId: await getAuthSessionId(ctx) ?? void 0,
|
|
80
|
+
generateTokens
|
|
87
81
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error instanceof VerifyFailure) {
|
|
84
|
+
log(LOG_LEVELS.ERROR, error.reason);
|
|
85
|
+
if (identifier !== void 0) await recordFailedSignIn(ctx, identifier, config);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
91
90
|
}
|
|
91
|
+
/** A soft verification failure -- logged and collapsed to null at the boundary. */
|
|
92
|
+
var VerifyFailure = class extends Error {
|
|
93
|
+
_tag = "VerifyFailure";
|
|
94
|
+
reason;
|
|
95
|
+
constructor(reason) {
|
|
96
|
+
super(reason);
|
|
97
|
+
this.reason = reason;
|
|
98
|
+
this.name = "VerifyFailure";
|
|
99
|
+
}
|
|
100
|
+
};
|
|
92
101
|
const callVerifyCodeAndSignIn = async (ctx, args) => {
|
|
93
102
|
return ctx.runMutation(AUTH_STORE_REF, { args: {
|
|
94
103
|
type: "verifyCodeAndSignIn",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { envOptionalString, readConfigSync } from "../env.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { withSpan } from "../utils/span.js";
|
|
2
4
|
import { isLocalHost } from "../url.js";
|
|
3
5
|
import { SHARED_COOKIE_OPTIONS } from "../cookies.js";
|
|
4
|
-
import { log } from "../log.js";
|
|
5
6
|
import { ConvexError } from "convex/values";
|
|
6
|
-
import { Effect } from "effect";
|
|
7
7
|
import * as arctic from "arctic";
|
|
8
8
|
|
|
9
9
|
//#region src/server/oauth/runtime.ts
|
|
@@ -16,13 +16,14 @@ import * as arctic from "arctic";
|
|
|
16
16
|
* @module
|
|
17
17
|
*/
|
|
18
18
|
function failConvex(data) {
|
|
19
|
-
|
|
19
|
+
throw new ConvexError(data);
|
|
20
20
|
}
|
|
21
|
-
function tryConvex(options) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
async function tryConvex(options) {
|
|
22
|
+
try {
|
|
23
|
+
return await options.try();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new ConvexError(options.catch(error));
|
|
26
|
+
}
|
|
26
27
|
}
|
|
27
28
|
const COOKIE_TTL = 900;
|
|
28
29
|
const oauthCookiePrefix = !isLocalHost(readConfigSync(envOptionalString("CONVEX_SITE_URL")) ?? void 0) ? "__Host-" : "";
|
|
@@ -67,7 +68,7 @@ function getAuthorizationSignature({ codeVerifier, state }) {
|
|
|
67
68
|
function requiresPKCE(provider) {
|
|
68
69
|
return provider.pkce === "required" || provider.pkce === "optional";
|
|
69
70
|
}
|
|
70
|
-
function exchangeCode(provider, code, codeVerifier) {
|
|
71
|
+
async function exchangeCode(provider, code, codeVerifier) {
|
|
71
72
|
return tryConvex({
|
|
72
73
|
try: () => provider.validateAuthorizationCode({
|
|
73
74
|
code,
|
|
@@ -89,7 +90,7 @@ function exchangeCode(provider, code, codeVerifier) {
|
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
|
-
function extractProfile(providerId, oauthConfig, tokens) {
|
|
93
|
+
async function extractProfile(providerId, oauthConfig, tokens) {
|
|
93
94
|
if (oauthConfig.profile) return tryConvex({
|
|
94
95
|
try: () => oauthConfig.profile(tokens),
|
|
95
96
|
catch: (error) => ({
|
|
@@ -99,12 +100,12 @@ function extractProfile(providerId, oauthConfig, tokens) {
|
|
|
99
100
|
});
|
|
100
101
|
if (typeof tokens.idToken === "string") {
|
|
101
102
|
const claims = arctic.decodeIdToken(tokens.idToken);
|
|
102
|
-
return
|
|
103
|
+
return {
|
|
103
104
|
id: claims.sub ?? crypto.randomUUID(),
|
|
104
105
|
name: claims.name ?? void 0,
|
|
105
106
|
email: claims.email ?? void 0,
|
|
106
107
|
image: claims.picture ?? void 0
|
|
107
|
-
}
|
|
108
|
+
};
|
|
108
109
|
}
|
|
109
110
|
return failConvex({
|
|
110
111
|
code: "OAUTH_INVALID_PROFILE",
|
|
@@ -112,7 +113,8 @@ function extractProfile(providerId, oauthConfig, tokens) {
|
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
function validateProfileId(providerId, profile) {
|
|
115
|
-
|
|
116
|
+
if (typeof profile.id === "string" && profile.id) return profile;
|
|
117
|
+
return failConvex({
|
|
116
118
|
code: "OAUTH_INVALID_PROFILE",
|
|
117
119
|
message: `The profile callback for "${providerId}" must return an object with a string \`id\` field.`
|
|
118
120
|
});
|
|
@@ -163,16 +165,16 @@ async function createOAuthAuthorizationURL(providerId, oauthConfig, options) {
|
|
|
163
165
|
* Handle the OAuth callback: validate state, exchange code for tokens,
|
|
164
166
|
* extract profile.
|
|
165
167
|
*/
|
|
166
|
-
function handleOAuthCallback(providerId, oauthConfig, params, cookies) {
|
|
167
|
-
return
|
|
168
|
-
if (oauthConfig.provider === null) return
|
|
168
|
+
async function handleOAuthCallback(providerId, oauthConfig, params, cookies) {
|
|
169
|
+
return withSpan("convex-auth.oauth.callback", { providerId }, async () => {
|
|
170
|
+
if (oauthConfig.provider === null) return failConvex({
|
|
169
171
|
code: "OAUTH_PROVIDER_ERROR",
|
|
170
172
|
message: `OAuth provider "${providerId}" is missing a runtime client.`
|
|
171
173
|
});
|
|
172
174
|
const responseCookies = [];
|
|
173
175
|
const storedState = cookies[oauthCookieName("state", providerId)];
|
|
174
176
|
const returnedState = params.state;
|
|
175
|
-
if (!storedState || !returnedState || !constantTimeEqual(storedState, returnedState)) return
|
|
177
|
+
if (!storedState || !returnedState || !constantTimeEqual(storedState, returnedState)) return failConvex({
|
|
176
178
|
code: "OAUTH_INVALID_STATE",
|
|
177
179
|
message: "Invalid OAuth state. Please try signing in again."
|
|
178
180
|
});
|
|
@@ -184,21 +186,21 @@ function handleOAuthCallback(providerId, oauthConfig, params, cookies) {
|
|
|
184
186
|
error_description: params.error_description
|
|
185
187
|
};
|
|
186
188
|
log("DEBUG", "OAuthCallbackError", cause);
|
|
187
|
-
return
|
|
189
|
+
return failConvex({
|
|
188
190
|
code: "OAUTH_PROVIDER_ERROR",
|
|
189
191
|
message: "OAuth provider returned an error",
|
|
190
192
|
cause: JSON.stringify(cause)
|
|
191
193
|
});
|
|
192
194
|
}
|
|
193
195
|
const code = params.code;
|
|
194
|
-
if (code == null) return
|
|
196
|
+
if (code == null) return failConvex({
|
|
195
197
|
code: "OAUTH_PROVIDER_ERROR",
|
|
196
198
|
message: "Missing authorization code in callback"
|
|
197
199
|
});
|
|
198
200
|
let codeVerifier;
|
|
199
201
|
if (requiresPKCE(oauthConfig.provider)) {
|
|
200
202
|
const storedVerifier = cookies[oauthCookieName("pkce", providerId)];
|
|
201
|
-
if (storedVerifier == null) return
|
|
203
|
+
if (storedVerifier == null) return failConvex({
|
|
202
204
|
code: "OAUTH_MISSING_VERIFIER",
|
|
203
205
|
message: "Missing PKCE verifier cookie for OAuth callback"
|
|
204
206
|
});
|
|
@@ -208,22 +210,22 @@ function handleOAuthCallback(providerId, oauthConfig, params, cookies) {
|
|
|
208
210
|
let nonce;
|
|
209
211
|
if (oauthConfig.nonce === true) {
|
|
210
212
|
const storedNonce = cookies[oauthCookieName("nonce", providerId)];
|
|
211
|
-
if (storedNonce == null) return
|
|
213
|
+
if (storedNonce == null) return failConvex({
|
|
212
214
|
code: "OAUTH_PROVIDER_ERROR",
|
|
213
215
|
message: "Missing nonce cookie for OAuth callback"
|
|
214
216
|
});
|
|
215
217
|
nonce = storedNonce;
|
|
216
218
|
responseCookies.push(clearCookie("nonce", providerId));
|
|
217
219
|
}
|
|
218
|
-
const tokens =
|
|
219
|
-
if (oauthConfig.validateTokens !== void 0)
|
|
220
|
+
const tokens = await exchangeCode(oauthConfig.provider, code, codeVerifier);
|
|
221
|
+
if (oauthConfig.validateTokens !== void 0) await tryConvex({
|
|
220
222
|
try: () => oauthConfig.validateTokens(tokens, { nonce }),
|
|
221
223
|
catch: (error) => ({
|
|
222
224
|
code: "OAUTH_PROVIDER_ERROR",
|
|
223
225
|
message: `Token validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
224
226
|
})
|
|
225
227
|
});
|
|
226
|
-
const profile =
|
|
228
|
+
const profile = validateProfileId(providerId, await extractProfile(providerId, oauthConfig, tokens));
|
|
227
229
|
log("DEBUG", "OAuth callback profile extracted", {
|
|
228
230
|
providerId,
|
|
229
231
|
profileId: profile.id
|
|
@@ -238,7 +240,7 @@ function handleOAuthCallback(providerId, oauthConfig, params, cookies) {
|
|
|
238
240
|
cookies: responseCookies,
|
|
239
241
|
signature
|
|
240
242
|
};
|
|
241
|
-
})
|
|
243
|
+
});
|
|
242
244
|
}
|
|
243
245
|
|
|
244
246
|
//#endregion
|