@robelest/convex-auth 0.0.2 → 0.0.3-preview.1
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.cjs +1 -1
- package/dist/client/index.d.ts +33 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +79 -13
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +48 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/index.d.ts +10 -4
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +8 -3
- package/dist/component/index.js.map +1 -1
- package/dist/component/public.d.ts +163 -3
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +124 -0
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +81 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +45 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/providers/anonymous.d.ts +3 -0
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +3 -0
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/credentials.d.ts +3 -0
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +3 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/email.d.ts +3 -0
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js +3 -0
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/passkey.d.ts +7 -1
- package/dist/providers/passkey.d.ts.map +1 -1
- package/dist/providers/passkey.js +7 -1
- package/dist/providers/passkey.js.map +1 -1
- package/dist/providers/password.d.ts +3 -0
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +3 -0
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +3 -0
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js +3 -0
- package/dist/providers/phone.js.map +1 -1
- package/dist/providers/totp.d.ts +8 -0
- package/dist/providers/totp.d.ts.map +1 -1
- package/dist/providers/totp.js +8 -0
- package/dist/providers/totp.js.map +1 -1
- package/dist/server/convex-auth.d.ts +185 -25
- package/dist/server/convex-auth.d.ts.map +1 -1
- package/dist/server/convex-auth.js +317 -58
- package/dist/server/convex-auth.js.map +1 -1
- package/dist/server/email-templates.d.ts +18 -0
- package/dist/server/email-templates.d.ts.map +1 -0
- package/dist/server/email-templates.js +74 -0
- package/dist/server/email-templates.js.map +1 -0
- package/dist/server/errors.d.ts +146 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +176 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/implementation/apiKey.d.ts +74 -0
- package/dist/server/implementation/apiKey.d.ts.map +1 -0
- package/dist/server/implementation/apiKey.js +139 -0
- package/dist/server/implementation/apiKey.js.map +1 -0
- package/dist/server/implementation/index.d.ts +151 -14
- package/dist/server/implementation/index.d.ts.map +1 -1
- package/dist/server/implementation/index.js +216 -24
- package/dist/server/implementation/index.js.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js +2 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.d.ts +2 -2
- package/dist/server/implementation/mutations/index.d.ts +6 -6
- package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.js +2 -1
- package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.js +2 -1
- package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.js +2 -1
- package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
- package/dist/server/implementation/passkey.d.ts.map +1 -1
- package/dist/server/implementation/passkey.js +28 -29
- package/dist/server/implementation/passkey.js.map +1 -1
- package/dist/server/implementation/provider.d.ts.map +1 -1
- package/dist/server/implementation/provider.js +5 -4
- package/dist/server/implementation/provider.js.map +1 -1
- package/dist/server/implementation/redirects.d.ts.map +1 -1
- package/dist/server/implementation/redirects.js +2 -1
- package/dist/server/implementation/redirects.js.map +1 -1
- package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
- package/dist/server/implementation/refreshTokens.js +2 -1
- package/dist/server/implementation/refreshTokens.js.map +1 -1
- package/dist/server/implementation/signIn.d.ts.map +1 -1
- package/dist/server/implementation/signIn.js +8 -18
- package/dist/server/implementation/signIn.js.map +1 -1
- package/dist/server/implementation/totp.d.ts.map +1 -1
- package/dist/server/implementation/totp.js +16 -17
- package/dist/server/implementation/totp.js.map +1 -1
- package/dist/server/implementation/users.d.ts.map +1 -1
- package/dist/server/implementation/users.js +3 -2
- package/dist/server/implementation/users.js.map +1 -1
- package/dist/server/index.d.ts +157 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +180 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth/authorizationUrl.d.ts.map +1 -1
- package/dist/server/oauth/authorizationUrl.js +2 -1
- package/dist/server/oauth/authorizationUrl.js.map +1 -1
- package/dist/server/oauth/callback.d.ts.map +1 -1
- package/dist/server/oauth/callback.js +5 -4
- package/dist/server/oauth/callback.js.map +1 -1
- package/dist/server/oauth/checks.d.ts.map +1 -1
- package/dist/server/oauth/checks.js +2 -1
- package/dist/server/oauth/checks.js.map +1 -1
- package/dist/server/oauth/convexAuth.d.ts.map +1 -1
- package/dist/server/oauth/convexAuth.js +3 -2
- package/dist/server/oauth/convexAuth.js.map +1 -1
- package/dist/server/provider_utils.d.ts +2 -0
- package/dist/server/provider_utils.d.ts.map +1 -1
- package/dist/server/types.d.ts +240 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils.d.ts.map +1 -1
- package/dist/server/utils.js +2 -1
- package/dist/server/utils.js.map +1 -1
- package/dist/server/version.d.ts +2 -0
- package/dist/server/version.d.ts.map +1 -0
- package/dist/server/version.js +3 -0
- package/dist/server/version.js.map +1 -0
- package/package.json +7 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/utils.ts +248 -0
- package/src/client/index.ts +105 -15
- package/src/component/_generated/component.ts +61 -0
- package/src/component/index.ts +11 -2
- package/src/component/public.ts +142 -0
- package/src/component/schema.ts +52 -0
- package/src/providers/anonymous.ts +3 -0
- package/src/providers/credentials.ts +3 -0
- package/src/providers/email.ts +3 -0
- package/src/providers/passkey.ts +8 -1
- package/src/providers/password.ts +3 -0
- package/src/providers/phone.ts +3 -0
- package/src/providers/totp.ts +9 -0
- package/src/server/convex-auth.ts +385 -73
- package/src/server/email-templates.ts +77 -0
- package/src/server/errors.ts +269 -0
- package/src/server/implementation/apiKey.ts +186 -0
- package/src/server/implementation/index.ts +288 -28
- package/src/server/implementation/mutations/createAccountFromCredentials.ts +2 -1
- package/src/server/implementation/mutations/modifyAccount.ts +2 -3
- package/src/server/implementation/mutations/userOAuth.ts +2 -1
- package/src/server/implementation/mutations/verifierSignature.ts +2 -1
- package/src/server/implementation/passkey.ts +33 -35
- package/src/server/implementation/provider.ts +5 -8
- package/src/server/implementation/redirects.ts +2 -3
- package/src/server/implementation/refreshTokens.ts +2 -1
- package/src/server/implementation/signIn.ts +9 -18
- package/src/server/implementation/totp.ts +18 -21
- package/src/server/implementation/users.ts +4 -7
- package/src/server/index.ts +240 -37
- package/src/server/oauth/authorizationUrl.ts +2 -1
- package/src/server/oauth/callback.ts +5 -4
- package/src/server/oauth/checks.ts +3 -1
- package/src/server/oauth/convexAuth.ts +6 -3
- package/src/server/types.ts +254 -5
- package/src/server/utils.ts +3 -1
- package/src/server/version.ts +2 -0
- package/dist/server/portal.d.ts +0 -116
- package/dist/server/portal.d.ts.map +0 -1
- package/dist/server/portal.js +0 -294
- package/dist/server/portal.js.map +0 -1
- package/src/server/portal.ts +0 -375
|
@@ -48,6 +48,7 @@ import { AuthDataModel, SessionInfo } from "./types.js";
|
|
|
48
48
|
import { callSignIn, callVerifier } from "./mutations/index.js";
|
|
49
49
|
import { callVerifierSignature } from "./mutations/verifierSignature.js";
|
|
50
50
|
import { authDb } from "./db.js";
|
|
51
|
+
import { throwAuthError } from "../errors.js";
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
type EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;
|
|
@@ -64,7 +65,8 @@ function resolveRpOptions(provider: PasskeyProviderConfig) {
|
|
|
64
65
|
// because the RP ID wouldn't match the page origin.
|
|
65
66
|
const siteUrl = process.env.SITE_URL;
|
|
66
67
|
if (!siteUrl && !provider.options.rpId) {
|
|
67
|
-
|
|
68
|
+
throwAuthError(
|
|
69
|
+
"PASSKEY_MISSING_CONFIG",
|
|
68
70
|
"Passkey provider requires SITE_URL env var (your frontend URL) " +
|
|
69
71
|
"or explicit rpId / origin in the provider config. " +
|
|
70
72
|
"CONVEX_SITE_URL cannot be used because WebAuthn RP ID must match the frontend domain.",
|
|
@@ -124,10 +126,7 @@ async function handleRegisterOptions(
|
|
|
124
126
|
// Passkey registration requires an authenticated user
|
|
125
127
|
const identity = await ctx.auth.getUserIdentity();
|
|
126
128
|
if (identity === null) {
|
|
127
|
-
|
|
128
|
-
"Passkey registration requires an authenticated user. " +
|
|
129
|
-
"Sign in first, then add a passkey to your account.",
|
|
130
|
-
);
|
|
129
|
+
throwAuthError("PASSKEY_AUTH_REQUIRED");
|
|
131
130
|
}
|
|
132
131
|
const [userId] = identity.subject.split("|");
|
|
133
132
|
|
|
@@ -215,17 +214,14 @@ async function handleRegisterVerify(
|
|
|
215
214
|
// Passkey registration requires an authenticated user
|
|
216
215
|
const identity = await ctx.auth.getUserIdentity();
|
|
217
216
|
if (identity === null) {
|
|
218
|
-
|
|
219
|
-
"Passkey registration requires an authenticated user. " +
|
|
220
|
-
"Sign in first, then add a passkey to your account.",
|
|
221
|
-
);
|
|
217
|
+
throwAuthError("PASSKEY_AUTH_REQUIRED");
|
|
222
218
|
}
|
|
223
219
|
const [userId] = identity.subject.split("|");
|
|
224
220
|
|
|
225
221
|
const rp = resolveRpOptions(provider);
|
|
226
222
|
|
|
227
223
|
if (!verifierValue) {
|
|
228
|
-
|
|
224
|
+
throwAuthError("PASSKEY_MISSING_VERIFIER");
|
|
229
225
|
}
|
|
230
226
|
|
|
231
227
|
// Decode client data
|
|
@@ -234,13 +230,14 @@ async function handleRegisterVerify(
|
|
|
234
230
|
|
|
235
231
|
// Verify client data type is "webauthn.create"
|
|
236
232
|
if (clientData.type !== ClientDataType.Create) {
|
|
237
|
-
|
|
233
|
+
throwAuthError("PASSKEY_INVALID_CLIENT_DATA", "Invalid client data type: expected webauthn.create");
|
|
238
234
|
}
|
|
239
235
|
|
|
240
236
|
// Verify origin
|
|
241
237
|
const allowedOrigins = Array.isArray(rp.origin) ? rp.origin : [rp.origin];
|
|
242
238
|
if (!allowedOrigins.includes(clientData.origin)) {
|
|
243
|
-
|
|
239
|
+
throwAuthError(
|
|
240
|
+
"PASSKEY_INVALID_ORIGIN",
|
|
244
241
|
`Invalid origin: ${clientData.origin}, expected one of: ${allowedOrigins.join(", ")}`,
|
|
245
242
|
);
|
|
246
243
|
}
|
|
@@ -254,7 +251,7 @@ async function handleRegisterVerify(
|
|
|
254
251
|
{ verifierId: verifierValue },
|
|
255
252
|
);
|
|
256
253
|
if (!verifierDoc || (verifierDoc as any).signature !== challengeHash) {
|
|
257
|
-
|
|
254
|
+
throwAuthError("PASSKEY_INVALID_CHALLENGE");
|
|
258
255
|
}
|
|
259
256
|
|
|
260
257
|
// Clean up the verifier
|
|
@@ -270,21 +267,21 @@ async function handleRegisterVerify(
|
|
|
270
267
|
|
|
271
268
|
// Verify RP ID hash
|
|
272
269
|
if (!authenticatorData.verifyRelyingPartyIdHash(rp.rpId)) {
|
|
273
|
-
|
|
270
|
+
throwAuthError("PASSKEY_RP_MISMATCH");
|
|
274
271
|
}
|
|
275
272
|
|
|
276
273
|
// Verify user presence and verification flags
|
|
277
274
|
if (!authenticatorData.userPresent) {
|
|
278
|
-
|
|
275
|
+
throwAuthError("PASSKEY_USER_PRESENCE");
|
|
279
276
|
}
|
|
280
277
|
if (rp.userVerification === "required" && !authenticatorData.userVerified) {
|
|
281
|
-
|
|
278
|
+
throwAuthError("PASSKEY_USER_VERIFICATION");
|
|
282
279
|
}
|
|
283
280
|
|
|
284
281
|
// Extract credential
|
|
285
282
|
const credential = authenticatorData.credential;
|
|
286
283
|
if (!credential) {
|
|
287
|
-
|
|
284
|
+
throwAuthError("PASSKEY_NO_CREDENTIAL");
|
|
288
285
|
}
|
|
289
286
|
|
|
290
287
|
const credentialId = encodeBase64urlNoPadding(credential.id);
|
|
@@ -320,7 +317,7 @@ async function handleRegisterVerify(
|
|
|
320
317
|
const rsaPubKey = new RSAPublicKey(rsa.n, rsa.e);
|
|
321
318
|
publicKeyBytes = rsaPubKey.encodePKCS1();
|
|
322
319
|
} else {
|
|
323
|
-
|
|
320
|
+
throwAuthError("PASSKEY_UNSUPPORTED_ALGORITHM", `Unsupported algorithm: ${algorithm}`);
|
|
324
321
|
}
|
|
325
322
|
|
|
326
323
|
const deviceType = params.deviceType ?? "single-device";
|
|
@@ -447,7 +444,7 @@ async function handleAuthVerify(
|
|
|
447
444
|
const rp = resolveRpOptions(provider);
|
|
448
445
|
|
|
449
446
|
if (!verifierValue) {
|
|
450
|
-
|
|
447
|
+
throwAuthError("PASSKEY_MISSING_VERIFIER");
|
|
451
448
|
}
|
|
452
449
|
|
|
453
450
|
// Decode client data
|
|
@@ -456,13 +453,14 @@ async function handleAuthVerify(
|
|
|
456
453
|
|
|
457
454
|
// Verify client data type is "webauthn.get"
|
|
458
455
|
if (clientData.type !== ClientDataType.Get) {
|
|
459
|
-
|
|
456
|
+
throwAuthError("PASSKEY_INVALID_CLIENT_DATA", "Invalid client data type: expected webauthn.get");
|
|
460
457
|
}
|
|
461
458
|
|
|
462
459
|
// Verify origin
|
|
463
460
|
const allowedOrigins = Array.isArray(rp.origin) ? rp.origin : [rp.origin];
|
|
464
461
|
if (!allowedOrigins.includes(clientData.origin)) {
|
|
465
|
-
|
|
462
|
+
throwAuthError(
|
|
463
|
+
"PASSKEY_INVALID_ORIGIN",
|
|
466
464
|
`Invalid origin: ${clientData.origin}, expected one of: ${allowedOrigins.join(", ")}`,
|
|
467
465
|
);
|
|
468
466
|
}
|
|
@@ -476,7 +474,7 @@ async function handleAuthVerify(
|
|
|
476
474
|
{ verifierId: verifierValue },
|
|
477
475
|
);
|
|
478
476
|
if (!verifierDoc || (verifierDoc as any).signature !== challengeHash) {
|
|
479
|
-
|
|
477
|
+
throwAuthError("PASSKEY_INVALID_CHALLENGE");
|
|
480
478
|
}
|
|
481
479
|
|
|
482
480
|
// Clean up the verifier
|
|
@@ -488,7 +486,7 @@ async function handleAuthVerify(
|
|
|
488
486
|
// Look up the credential
|
|
489
487
|
const credentialId = params.credentialId;
|
|
490
488
|
if (!credentialId) {
|
|
491
|
-
|
|
489
|
+
throwAuthError("PASSKEY_UNKNOWN_CREDENTIAL", "Missing credential ID");
|
|
492
490
|
}
|
|
493
491
|
|
|
494
492
|
const passkeyDoc = await ctx.runQuery(
|
|
@@ -496,7 +494,7 @@ async function handleAuthVerify(
|
|
|
496
494
|
{ credentialId },
|
|
497
495
|
);
|
|
498
496
|
if (!passkeyDoc) {
|
|
499
|
-
|
|
497
|
+
throwAuthError("PASSKEY_UNKNOWN_CREDENTIAL", "Unknown credential");
|
|
500
498
|
}
|
|
501
499
|
const passkey = passkeyDoc as any;
|
|
502
500
|
|
|
@@ -506,15 +504,15 @@ async function handleAuthVerify(
|
|
|
506
504
|
|
|
507
505
|
// Verify RP ID hash
|
|
508
506
|
if (!authenticatorData.verifyRelyingPartyIdHash(rp.rpId)) {
|
|
509
|
-
|
|
507
|
+
throwAuthError("PASSKEY_RP_MISMATCH");
|
|
510
508
|
}
|
|
511
509
|
|
|
512
510
|
// Verify user presence
|
|
513
511
|
if (!authenticatorData.userPresent) {
|
|
514
|
-
|
|
512
|
+
throwAuthError("PASSKEY_USER_PRESENCE");
|
|
515
513
|
}
|
|
516
514
|
if (rp.userVerification === "required" && !authenticatorData.userVerified) {
|
|
517
|
-
|
|
515
|
+
throwAuthError("PASSKEY_USER_VERIFICATION");
|
|
518
516
|
}
|
|
519
517
|
|
|
520
518
|
// Verify signature
|
|
@@ -538,7 +536,7 @@ async function handleAuthVerify(
|
|
|
538
536
|
ecdsaSignature,
|
|
539
537
|
);
|
|
540
538
|
if (!valid) {
|
|
541
|
-
|
|
539
|
+
throwAuthError("PASSKEY_INVALID_SIGNATURE");
|
|
542
540
|
}
|
|
543
541
|
} else if (passkey.algorithm === coseAlgorithmRS256) {
|
|
544
542
|
// RSA PKCS#1 v1.5 with SHA-256 verification
|
|
@@ -552,10 +550,10 @@ async function handleAuthVerify(
|
|
|
552
550
|
signature,
|
|
553
551
|
);
|
|
554
552
|
if (!valid) {
|
|
555
|
-
|
|
553
|
+
throwAuthError("PASSKEY_INVALID_SIGNATURE");
|
|
556
554
|
}
|
|
557
555
|
} else {
|
|
558
|
-
|
|
556
|
+
throwAuthError("PASSKEY_UNSUPPORTED_ALGORITHM", `Unsupported algorithm: ${passkey.algorithm}`);
|
|
559
557
|
}
|
|
560
558
|
|
|
561
559
|
// Verify counter (clone detection)
|
|
@@ -565,9 +563,7 @@ async function handleAuthVerify(
|
|
|
565
563
|
authenticatorData.signatureCounter !== 0 &&
|
|
566
564
|
authenticatorData.signatureCounter <= passkey.counter
|
|
567
565
|
) {
|
|
568
|
-
|
|
569
|
-
"Authenticator counter did not increase — possible credential cloning detected",
|
|
570
|
-
);
|
|
566
|
+
throwAuthError("PASSKEY_COUNTER_ERROR");
|
|
571
567
|
}
|
|
572
568
|
|
|
573
569
|
// Update counter and last used timestamp
|
|
@@ -611,7 +607,8 @@ export async function handlePasskey(
|
|
|
611
607
|
> {
|
|
612
608
|
const flow = args.params?.flow;
|
|
613
609
|
if (!flow) {
|
|
614
|
-
|
|
610
|
+
throwAuthError(
|
|
611
|
+
"PASSKEY_MISSING_FLOW",
|
|
615
612
|
"Missing `flow` parameter. Expected one of: register-options, register-verify, auth-options, auth-verify",
|
|
616
613
|
);
|
|
617
614
|
}
|
|
@@ -626,7 +623,8 @@ export async function handlePasskey(
|
|
|
626
623
|
case "auth-verify":
|
|
627
624
|
return handleAuthVerify(ctx, provider, args.params ?? {}, args.verifier);
|
|
628
625
|
default:
|
|
629
|
-
|
|
626
|
+
throwAuthError(
|
|
627
|
+
"PASSKEY_UNKNOWN_FLOW",
|
|
630
628
|
`Unknown passkey flow: ${flow}. Expected one of: register-options, register-verify, auth-options, auth-verify`,
|
|
631
629
|
);
|
|
632
630
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { AuthProviderMaterializedConfig } from "../types.js";
|
|
2
2
|
import { ConvexAuthMaterializedConfig } from "../types.js";
|
|
3
|
+
import { throwAuthError } from "../errors.js";
|
|
3
4
|
|
|
4
5
|
export async function hash(provider: any, secret: string) {
|
|
5
6
|
if (provider.type !== "credentials") {
|
|
6
|
-
|
|
7
|
+
throwAuthError("INVALID_CREDENTIALS_PROVIDER", `Provider ${provider.id} is not a credentials provider`, { provider: provider.id });
|
|
7
8
|
}
|
|
8
9
|
const hashSecretFn = provider.crypto?.hashSecret;
|
|
9
10
|
if (hashSecretFn === undefined) {
|
|
10
|
-
|
|
11
|
-
`Provider ${provider.id} does not have a \`crypto.hashSecret\` function`,
|
|
12
|
-
);
|
|
11
|
+
throwAuthError("MISSING_CRYPTO_FUNCTION", `Provider ${provider.id} does not have a \`crypto.hashSecret\` function`, { provider: provider.id });
|
|
13
12
|
}
|
|
14
13
|
return await hashSecretFn(secret);
|
|
15
14
|
}
|
|
@@ -20,13 +19,11 @@ export async function verify(
|
|
|
20
19
|
hash: string,
|
|
21
20
|
) {
|
|
22
21
|
if (provider.type !== "credentials") {
|
|
23
|
-
|
|
22
|
+
throwAuthError("INVALID_CREDENTIALS_PROVIDER", `Provider ${provider.id} is not a credentials provider`, { provider: provider.id });
|
|
24
23
|
}
|
|
25
24
|
const verifySecretFn = provider.crypto?.verifySecret;
|
|
26
25
|
if (verifySecretFn === undefined) {
|
|
27
|
-
|
|
28
|
-
`Provider ${provider.id} does not have a \`crypto.verifySecret\` function`,
|
|
29
|
-
);
|
|
26
|
+
throwAuthError("MISSING_CRYPTO_FUNCTION", `Provider ${provider.id} does not have a \`crypto.verifySecret\` function`, { provider: provider.id });
|
|
30
27
|
}
|
|
31
28
|
return await verifySecretFn(secret, hash);
|
|
32
29
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConvexAuthMaterializedConfig } from "../types.js";
|
|
2
2
|
import { requireEnv } from "../utils.js";
|
|
3
|
+
import { throwAuthError } from "../errors.js";
|
|
3
4
|
|
|
4
5
|
export async function redirectAbsoluteUrl(
|
|
5
6
|
config: ConvexAuthMaterializedConfig,
|
|
@@ -7,9 +8,7 @@ export async function redirectAbsoluteUrl(
|
|
|
7
8
|
) {
|
|
8
9
|
if (params.redirectTo !== undefined) {
|
|
9
10
|
if (typeof params.redirectTo !== "string") {
|
|
10
|
-
|
|
11
|
-
`Expected \`redirectTo\` to be a string, got ${params.redirectTo as any}`,
|
|
12
|
-
);
|
|
11
|
+
throwAuthError("INVALID_REDIRECT", `Expected \`redirectTo\` to be a string, got ${params.redirectTo as any}`);
|
|
13
12
|
}
|
|
14
13
|
const redirectCallback =
|
|
15
14
|
config.callbacks?.redirect ?? defaultRedirectCallback;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GenericId } from "convex/values";
|
|
2
2
|
import { ConvexAuthConfig } from "../types.js";
|
|
3
|
+
import { throwAuthError } from "../errors.js";
|
|
3
4
|
import { Doc, MutationCtx } from "./types.js";
|
|
4
5
|
import {
|
|
5
6
|
LOG_LEVELS,
|
|
@@ -47,7 +48,7 @@ export const parseRefreshToken = (
|
|
|
47
48
|
} => {
|
|
48
49
|
const [refreshTokenId, sessionId] = refreshToken.split(REFRESH_TOKEN_DIVIDER);
|
|
49
50
|
if (!refreshTokenId || !sessionId) {
|
|
50
|
-
|
|
51
|
+
throwAuthError("INVALID_REFRESH_TOKEN", `Can't parse refresh token: ${maybeRedact(refreshToken)}`);
|
|
51
52
|
}
|
|
52
53
|
return {
|
|
53
54
|
refreshTokenId: refreshTokenId as GenericId<"token">,
|
|
@@ -28,6 +28,7 @@ import { OAuth2Config, OIDCConfig } from "@auth/core/providers/oauth.js";
|
|
|
28
28
|
import { generateRandomString } from "./utils.js";
|
|
29
29
|
import { handlePasskey } from "./passkey.js";
|
|
30
30
|
import { handleTotp, checkTotpRequired } from "./totp.js";
|
|
31
|
+
import { throwAuthError } from "../errors.js";
|
|
31
32
|
|
|
32
33
|
const DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S = 60 * 60 * 24; // 24 hours
|
|
33
34
|
|
|
@@ -82,9 +83,7 @@ export async function signInImpl(
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
if (provider === null) {
|
|
85
|
-
|
|
86
|
-
"Cannot sign in: Missing `provider`, `params.code` or `refreshToken`",
|
|
87
|
-
);
|
|
86
|
+
throwAuthError("SIGN_IN_MISSING_PARAMS");
|
|
88
87
|
}
|
|
89
88
|
if (provider.type === "email" || provider.type === "phone") {
|
|
90
89
|
return handleEmailAndPhoneProvider(ctx, provider, args, options);
|
|
@@ -102,7 +101,8 @@ export async function signInImpl(
|
|
|
102
101
|
return handleTotp(ctx, provider, args);
|
|
103
102
|
}
|
|
104
103
|
const _typecheck: never = provider;
|
|
105
|
-
|
|
104
|
+
throwAuthError(
|
|
105
|
+
"UNSUPPORTED_PROVIDER_TYPE",
|
|
106
106
|
`Provider type ${(provider as any).type} is not supported yet`,
|
|
107
107
|
);
|
|
108
108
|
}
|
|
@@ -130,7 +130,7 @@ async function handleEmailAndPhoneProvider(
|
|
|
130
130
|
allowExtraProviders: options.allowExtraProviders,
|
|
131
131
|
});
|
|
132
132
|
if (result === null) {
|
|
133
|
-
|
|
133
|
+
throwAuthError("INVALID_VERIFICATION_CODE");
|
|
134
134
|
}
|
|
135
135
|
return {
|
|
136
136
|
kind: "signedIn",
|
|
@@ -170,20 +170,10 @@ async function handleEmailAndPhoneProvider(
|
|
|
170
170
|
await provider.sendVerificationRequest(
|
|
171
171
|
{
|
|
172
172
|
...verificationArgs,
|
|
173
|
-
provider
|
|
174
|
-
|
|
175
|
-
from:
|
|
176
|
-
// Simplifies demo configuration of Resend
|
|
177
|
-
provider.from === "Auth.js <no-reply@authjs.dev>" &&
|
|
178
|
-
provider.id === "resend"
|
|
179
|
-
? "My App <onboarding@resend.dev>"
|
|
180
|
-
: provider.from,
|
|
181
|
-
},
|
|
182
|
-
request: new Request("http://localhost"), // TODO: Document
|
|
173
|
+
provider,
|
|
174
|
+
request: new Request("http://localhost"),
|
|
183
175
|
theme: ctx.auth.config.theme,
|
|
184
176
|
},
|
|
185
|
-
// @ts-expect-error Figure out typing for email providers so they can
|
|
186
|
-
// access ctx.
|
|
187
177
|
ctx,
|
|
188
178
|
);
|
|
189
179
|
} else if (provider.type === "phone") {
|
|
@@ -281,7 +271,8 @@ async function handleOAuthProvider(
|
|
|
281
271
|
redirect.searchParams.set("code", verifier);
|
|
282
272
|
if (args.params?.redirectTo !== undefined) {
|
|
283
273
|
if (typeof args.params.redirectTo !== "string") {
|
|
284
|
-
|
|
274
|
+
throwAuthError(
|
|
275
|
+
"INVALID_REDIRECT",
|
|
285
276
|
`Expected \`redirectTo\` to be a string, got ${args.params.redirectTo}`,
|
|
286
277
|
);
|
|
287
278
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { AuthDataModel, SessionInfo } from "./types.js";
|
|
26
26
|
import { callSignIn, callVerifier } from "./mutations/index.js";
|
|
27
27
|
import { callVerifierSignature } from "./mutations/verifierSignature.js";
|
|
28
|
+
import { throwAuthError } from "../errors.js";
|
|
28
29
|
|
|
29
30
|
type EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;
|
|
30
31
|
|
|
@@ -53,10 +54,7 @@ async function handleSetup(
|
|
|
53
54
|
// TOTP enrollment requires an authenticated user
|
|
54
55
|
const identity = await ctx.auth.getUserIdentity();
|
|
55
56
|
if (identity === null) {
|
|
56
|
-
|
|
57
|
-
"TOTP enrollment requires an authenticated user. " +
|
|
58
|
-
"Sign in first, then add TOTP to your account.",
|
|
59
|
-
);
|
|
57
|
+
throwAuthError("TOTP_AUTH_REQUIRED");
|
|
60
58
|
}
|
|
61
59
|
const [userId] = identity.subject.split("|");
|
|
62
60
|
|
|
@@ -143,21 +141,18 @@ async function handleConfirm(
|
|
|
143
141
|
// TOTP confirmation requires an authenticated user
|
|
144
142
|
const identity = await ctx.auth.getUserIdentity();
|
|
145
143
|
if (identity === null) {
|
|
146
|
-
|
|
147
|
-
"TOTP confirmation requires an authenticated user. " +
|
|
148
|
-
"Sign in first, then confirm your TOTP enrollment.",
|
|
149
|
-
);
|
|
144
|
+
throwAuthError("TOTP_AUTH_REQUIRED");
|
|
150
145
|
}
|
|
151
146
|
const [userId] = identity.subject.split("|");
|
|
152
147
|
|
|
153
148
|
if (!verifierValue) {
|
|
154
|
-
|
|
149
|
+
throwAuthError("TOTP_MISSING_VERIFIER");
|
|
155
150
|
}
|
|
156
151
|
if (!params.code) {
|
|
157
|
-
|
|
152
|
+
throwAuthError("TOTP_MISSING_CODE");
|
|
158
153
|
}
|
|
159
154
|
if (!params.totpId) {
|
|
160
|
-
|
|
155
|
+
throwAuthError("TOTP_MISSING_ID");
|
|
161
156
|
}
|
|
162
157
|
|
|
163
158
|
// Look up the TOTP record
|
|
@@ -166,10 +161,10 @@ async function handleConfirm(
|
|
|
166
161
|
{ totpId: params.totpId },
|
|
167
162
|
);
|
|
168
163
|
if (!totpDoc) {
|
|
169
|
-
|
|
164
|
+
throwAuthError("TOTP_NOT_FOUND");
|
|
170
165
|
}
|
|
171
166
|
if ((totpDoc as any).verified) {
|
|
172
|
-
|
|
167
|
+
throwAuthError("TOTP_ALREADY_VERIFIED");
|
|
173
168
|
}
|
|
174
169
|
|
|
175
170
|
// Extract the secret from the TOTP record
|
|
@@ -184,7 +179,7 @@ async function handleConfirm(
|
|
|
184
179
|
30,
|
|
185
180
|
);
|
|
186
181
|
if (!valid) {
|
|
187
|
-
|
|
182
|
+
throwAuthError("TOTP_INVALID_CODE");
|
|
188
183
|
}
|
|
189
184
|
|
|
190
185
|
// Mark the enrollment as verified
|
|
@@ -225,10 +220,10 @@ async function handleVerify(
|
|
|
225
220
|
verifierValue: string | undefined,
|
|
226
221
|
): Promise<{ kind: "signedIn"; signedIn: SessionInfo | null }> {
|
|
227
222
|
if (!verifierValue) {
|
|
228
|
-
|
|
223
|
+
throwAuthError("TOTP_MISSING_VERIFIER");
|
|
229
224
|
}
|
|
230
225
|
if (!params.code) {
|
|
231
|
-
|
|
226
|
+
throwAuthError("TOTP_MISSING_CODE");
|
|
232
227
|
}
|
|
233
228
|
|
|
234
229
|
// Look up the verifier to retrieve the stored userId
|
|
@@ -237,7 +232,7 @@ async function handleVerify(
|
|
|
237
232
|
{ verifierId: verifierValue },
|
|
238
233
|
);
|
|
239
234
|
if (!verifierDoc) {
|
|
240
|
-
|
|
235
|
+
throwAuthError("TOTP_INVALID_VERIFIER");
|
|
241
236
|
}
|
|
242
237
|
|
|
243
238
|
// Parse the signature to extract userId
|
|
@@ -250,7 +245,7 @@ async function handleVerify(
|
|
|
250
245
|
{ userId: userId as any },
|
|
251
246
|
);
|
|
252
247
|
if (!totpDoc) {
|
|
253
|
-
|
|
248
|
+
throwAuthError("TOTP_NO_ENROLLMENT");
|
|
254
249
|
}
|
|
255
250
|
|
|
256
251
|
// Extract the secret from the TOTP record
|
|
@@ -265,7 +260,7 @@ async function handleVerify(
|
|
|
265
260
|
30,
|
|
266
261
|
);
|
|
267
262
|
if (!valid) {
|
|
268
|
-
|
|
263
|
+
throwAuthError("TOTP_INVALID_CODE");
|
|
269
264
|
}
|
|
270
265
|
|
|
271
266
|
// Update last used timestamp
|
|
@@ -317,7 +312,8 @@ export async function handleTotp(
|
|
|
317
312
|
> {
|
|
318
313
|
const flow = args.params?.flow;
|
|
319
314
|
if (!flow) {
|
|
320
|
-
|
|
315
|
+
throwAuthError(
|
|
316
|
+
"TOTP_MISSING_FLOW",
|
|
321
317
|
"Missing `flow` parameter. Expected one of: setup, confirm, verify",
|
|
322
318
|
);
|
|
323
319
|
}
|
|
@@ -340,7 +336,8 @@ export async function handleTotp(
|
|
|
340
336
|
args.verifier,
|
|
341
337
|
);
|
|
342
338
|
default:
|
|
343
|
-
|
|
339
|
+
throwAuthError(
|
|
340
|
+
"TOTP_UNKNOWN_FLOW",
|
|
344
341
|
`Unknown TOTP flow: ${flow}. Expected one of: setup, confirm, verify`,
|
|
345
342
|
);
|
|
346
343
|
}
|
|
@@ -3,6 +3,7 @@ import { Doc, MutationCtx } from "./types.js";
|
|
|
3
3
|
import { AuthProviderMaterializedConfig, ConvexAuthConfig } from "../types.js";
|
|
4
4
|
import { LOG_LEVELS, logWithLevel } from "./utils.js";
|
|
5
5
|
import { authDb } from "./db.js";
|
|
6
|
+
import { throwAuthError } from "../errors.js";
|
|
6
7
|
|
|
7
8
|
type CreateOrUpdateUserArgs = {
|
|
8
9
|
type: "oauth" | "credentials" | "email" | "phone" | "verification";
|
|
@@ -137,12 +138,10 @@ async function defaultCreateOrUpdateUser(
|
|
|
137
138
|
try {
|
|
138
139
|
await db.users.patch(userId, userData);
|
|
139
140
|
} catch (error) {
|
|
140
|
-
|
|
141
|
-
`Could not update user document with ID \`${userId}\`, ` +
|
|
141
|
+
throwAuthError("USER_UPDATE_FAILED", `Could not update user document with ID \`${userId}\`, ` +
|
|
142
142
|
`either the user has been deleted but their account has not, ` +
|
|
143
143
|
`or the profile data doesn't match the \`users\` table schema: ` +
|
|
144
|
-
`${(error as Error).message}
|
|
145
|
-
);
|
|
144
|
+
`${(error as Error).message}`);
|
|
146
145
|
}
|
|
147
146
|
} else {
|
|
148
147
|
userId = (await db.users.insert(userData)) as GenericId<"user">;
|
|
@@ -231,9 +230,7 @@ export async function getAccountOrThrow(
|
|
|
231
230
|
) {
|
|
232
231
|
const existingAccount = await authDb(ctx, config).accounts.getById(existingAccountId);
|
|
233
232
|
if (existingAccount === null) {
|
|
234
|
-
|
|
235
|
-
`Expected an account to exist for ID "${existingAccountId}"`,
|
|
236
|
-
);
|
|
233
|
+
throwAuthError("ACCOUNT_NOT_FOUND", `Expected an account to exist for ID "${existingAccountId}"`);
|
|
237
234
|
}
|
|
238
235
|
return existingAccount;
|
|
239
236
|
}
|