better-auth 1.5.5 → 1.5.6
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/api/index.d.mts +6 -10
- package/dist/api/index.mjs +19 -4
- package/dist/api/index.mjs.map +1 -1
- package/dist/api/middlewares/origin-check.mjs +17 -8
- package/dist/api/middlewares/origin-check.mjs.map +1 -1
- package/dist/api/routes/account.d.mts +1 -1
- package/dist/api/routes/email-verification.d.mts +0 -1
- package/dist/api/routes/session.d.mts +0 -1
- package/dist/api/routes/sign-in.d.mts +2 -2
- package/dist/api/routes/sign-up.d.mts +0 -1
- package/dist/api/routes/update-session.d.mts +0 -1
- package/dist/api/routes/update-user.d.mts +0 -1
- package/dist/api/to-auth-endpoints.mjs +49 -12
- package/dist/api/to-auth-endpoints.mjs.map +1 -1
- package/dist/auth/full.d.mts +0 -1
- package/dist/auth/minimal.d.mts +0 -1
- package/dist/client/index.d.mts +0 -2
- package/dist/client/path-to-object.d.mts +3 -1
- package/dist/client/session-refresh.d.mts +0 -1
- package/dist/client/session-refresh.mjs +12 -4
- package/dist/client/session-refresh.mjs.map +1 -1
- package/dist/client/types.d.mts +0 -1
- package/dist/context/create-context.mjs +4 -1
- package/dist/context/create-context.mjs.map +1 -1
- package/dist/context/helpers.mjs +10 -4
- package/dist/context/helpers.mjs.map +1 -1
- package/dist/cookies/index.d.mts +0 -1
- package/dist/cookies/session-store.d.mts +0 -2
- package/dist/db/index.d.mts +2 -2
- package/dist/db/internal-adapter.d.mts +2 -1
- package/dist/db/internal-adapter.mjs +1 -1
- package/dist/db/internal-adapter.mjs.map +1 -1
- package/dist/db/schema.d.mts +0 -1
- package/dist/db/with-hooks.d.mts +6 -2
- package/dist/db/with-hooks.mjs +72 -31
- package/dist/db/with-hooks.mjs.map +1 -1
- package/dist/index.d.mts +0 -2
- package/dist/integrations/node.d.mts +0 -1
- package/dist/oauth2/link-account.d.mts +0 -1
- package/dist/plugins/admin/access/statement.d.mts +0 -2
- package/dist/plugins/admin/admin.d.mts +0 -1
- package/dist/plugins/admin/client.d.mts +0 -2
- package/dist/plugins/admin/types.d.mts +0 -2
- package/dist/plugins/anonymous/types.d.mts +0 -1
- package/dist/plugins/email-otp/index.mjs +2 -1
- package/dist/plugins/email-otp/index.mjs.map +1 -1
- package/dist/plugins/email-otp/otp-token.mjs +31 -2
- package/dist/plugins/email-otp/otp-token.mjs.map +1 -1
- package/dist/plugins/email-otp/routes.mjs +60 -59
- package/dist/plugins/email-otp/routes.mjs.map +1 -1
- package/dist/plugins/email-otp/types.d.mts +12 -0
- package/dist/plugins/email-otp/utils.mjs +4 -1
- package/dist/plugins/email-otp/utils.mjs.map +1 -1
- package/dist/plugins/generic-oauth/client.d.mts +0 -1
- package/dist/plugins/generic-oauth/index.d.mts +0 -1
- package/dist/plugins/index.d.mts +0 -3
- package/dist/plugins/jwt/types.d.mts +0 -1
- package/dist/plugins/magic-link/index.d.mts +2 -0
- package/dist/plugins/magic-link/index.mjs +5 -3
- package/dist/plugins/magic-link/index.mjs.map +1 -1
- package/dist/plugins/mcp/index.d.mts +0 -1
- package/dist/plugins/oidc-provider/index.d.mts +0 -1
- package/dist/plugins/oidc-provider/types.d.mts +0 -1
- package/dist/plugins/one-time-token/index.d.mts +0 -1
- package/dist/plugins/organization/access/statement.d.mts +0 -2
- package/dist/plugins/organization/adapter.d.mts +0 -2
- package/dist/plugins/organization/adapter.mjs +2 -2
- package/dist/plugins/organization/adapter.mjs.map +1 -1
- package/dist/plugins/organization/client.d.mts +0 -5
- package/dist/plugins/organization/organization.d.mts +0 -2
- package/dist/plugins/organization/permission.d.mts +0 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +0 -2
- package/dist/plugins/organization/routes/crud-invites.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-members.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-org.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-team.d.mts +2 -3
- package/dist/plugins/organization/routes/crud-team.mjs +18 -14
- package/dist/plugins/organization/routes/crud-team.mjs.map +1 -1
- package/dist/plugins/organization/schema.d.mts +0 -1
- package/dist/plugins/organization/types.d.mts +0 -2
- package/dist/plugins/phone-number/types.d.mts +0 -1
- package/dist/plugins/siwe/index.d.mts +0 -1
- package/dist/plugins/test-utils/types.d.mts +0 -2
- package/dist/plugins/two-factor/client.d.mts +7 -0
- package/dist/plugins/two-factor/client.mjs +5 -1
- package/dist/plugins/two-factor/client.mjs.map +1 -1
- package/dist/plugins/two-factor/types.d.mts +0 -1
- package/dist/test-utils/test-instance.d.mts +18 -22
- package/dist/types/index.d.mts +0 -1
- package/package.json +13 -10
|
@@ -3,6 +3,7 @@ import { generateRandomString } from "../../crypto/random.mjs";
|
|
|
3
3
|
import "../../crypto/index.mjs";
|
|
4
4
|
import { EMAIL_OTP_ERROR_CODES } from "./error-codes.mjs";
|
|
5
5
|
import { getEndpointResponse } from "../../utils/plugin-helper.mjs";
|
|
6
|
+
import { toOTPIdentifier } from "./utils.mjs";
|
|
6
7
|
import { storeOTP } from "./otp-token.mjs";
|
|
7
8
|
import { changeEmailEmailOTP, checkVerificationOTP, createVerificationOTP, forgetPasswordEmailOTP, getVerificationOTP, requestEmailChangeEmailOTP, requestPasswordResetEmailOTP, resetPasswordEmailOTP, sendVerificationOTP, signInEmailOTP, verifyEmailOTP } from "./routes.mjs";
|
|
8
9
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
@@ -60,7 +61,7 @@ const emailOTP = (options) => {
|
|
|
60
61
|
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
61
62
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
62
63
|
value: `${storedOTP}:0`,
|
|
63
|
-
identifier:
|
|
64
|
+
identifier: toOTPIdentifier("email-verification", email),
|
|
64
65
|
expiresAt: getDate(opts.expiresIn, "sec")
|
|
65
66
|
});
|
|
66
67
|
await ctx.context.runInBackgroundOrAwait(options.sendVerificationOTP({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/email-otp/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { generateRandomString } from \"../../crypto\";\nimport { getDate } from \"../../utils/date\";\nimport { getEndpointResponse } from \"../../utils/plugin-helper\";\nimport { EMAIL_OTP_ERROR_CODES } from \"./error-codes\";\nimport { storeOTP } from \"./otp-token\";\nimport {\n\tchangeEmailEmailOTP,\n\tcheckVerificationOTP,\n\tcreateVerificationOTP,\n\tforgetPasswordEmailOTP,\n\tgetVerificationOTP,\n\trequestEmailChangeEmailOTP,\n\trequestPasswordResetEmailOTP,\n\tresetPasswordEmailOTP,\n\tsendVerificationOTP,\n\tsignInEmailOTP,\n\tverifyEmailOTP,\n} from \"./routes\";\nimport type { EmailOTPOptions } from \"./types\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\t\"email-otp\": {\n\t\t\tcreator: typeof emailOTP;\n\t\t};\n\t}\n}\n\nexport type { EmailOTPOptions } from \"./types\";\n\nconst defaultOTPGenerator = (options: EmailOTPOptions) =>\n\tgenerateRandomString(options.otpLength ?? 6, \"0-9\");\n\nexport const emailOTP = (options: EmailOTPOptions) => {\n\tconst opts = {\n\t\texpiresIn: 5 * 60,\n\t\tgenerateOTP: () => defaultOTPGenerator(options),\n\t\tstoreOTP: \"plain\",\n\t\t...options,\n\t} satisfies EmailOTPOptions;\n\n\tconst sendVerificationOTPAction = sendVerificationOTP(opts);\n\n\treturn {\n\t\tid: \"email-otp\",\n\t\tinit(ctx) {\n\t\t\tif (!opts.overrideDefaultEmailVerification) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\temailVerification: {\n\t\t\t\t\t\tasync sendVerificationEmail(data, request) {\n\t\t\t\t\t\t\tawait ctx.runInBackgroundOrAwait(\n\t\t\t\t\t\t\t\tsendVerificationOTPAction({\n\t\t\t\t\t\t\t\t\tcontext: ctx,\n\t\t\t\t\t\t\t\t\trequest: request,\n\t\t\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\t\t\temail: data.user.email,\n\t\t\t\t\t\t\t\t\t\ttype: \"email-verification\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t//@ts-expect-error\n\t\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tendpoints: {\n\t\t\tsendVerificationOTP: sendVerificationOTPAction,\n\t\t\tcreateVerificationOTP: createVerificationOTP(opts),\n\t\t\tgetVerificationOTP: getVerificationOTP(opts),\n\t\t\tcheckVerificationOTP: checkVerificationOTP(opts),\n\t\t\tverifyEmailOTP: verifyEmailOTP(opts),\n\t\t\tsignInEmailOTP: signInEmailOTP(opts),\n\t\t\trequestPasswordResetEmailOTP: requestPasswordResetEmailOTP(opts),\n\t\t\tforgetPasswordEmailOTP: forgetPasswordEmailOTP(opts),\n\t\t\tresetPasswordEmailOTP: resetPasswordEmailOTP(opts),\n\t\t\trequestEmailChangeEmailOTP: requestEmailChangeEmailOTP(opts),\n\t\t\tchangeEmailEmailOTP: changeEmailEmailOTP(opts),\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-up\") &&\n\t\t\t\t\t\t\topts.sendVerificationOnSignUp &&\n\t\t\t\t\t\t\t!opts.overrideDefaultEmailVerification\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst response = await getEndpointResponse<{\n\t\t\t\t\t\t\tuser: { email: string };\n\t\t\t\t\t\t}>(ctx);\n\t\t\t\t\t\tconst email = response?.user.email;\n\t\t\t\t\t\tif (email) {\n\t\t\t\t\t\t\tconst otp =\n\t\t\t\t\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\t\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\t\t\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\t\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\t\t\t\t\tidentifier:
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/email-otp/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { generateRandomString } from \"../../crypto\";\nimport { getDate } from \"../../utils/date\";\nimport { getEndpointResponse } from \"../../utils/plugin-helper\";\nimport { EMAIL_OTP_ERROR_CODES } from \"./error-codes\";\nimport { storeOTP } from \"./otp-token\";\nimport {\n\tchangeEmailEmailOTP,\n\tcheckVerificationOTP,\n\tcreateVerificationOTP,\n\tforgetPasswordEmailOTP,\n\tgetVerificationOTP,\n\trequestEmailChangeEmailOTP,\n\trequestPasswordResetEmailOTP,\n\tresetPasswordEmailOTP,\n\tsendVerificationOTP,\n\tsignInEmailOTP,\n\tverifyEmailOTP,\n} from \"./routes\";\nimport type { EmailOTPOptions } from \"./types\";\nimport { toOTPIdentifier } from \"./utils\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\t\"email-otp\": {\n\t\t\tcreator: typeof emailOTP;\n\t\t};\n\t}\n}\n\nexport type { EmailOTPOptions } from \"./types\";\n\nconst defaultOTPGenerator = (options: EmailOTPOptions) =>\n\tgenerateRandomString(options.otpLength ?? 6, \"0-9\");\n\nexport const emailOTP = (options: EmailOTPOptions) => {\n\tconst opts = {\n\t\texpiresIn: 5 * 60,\n\t\tgenerateOTP: () => defaultOTPGenerator(options),\n\t\tstoreOTP: \"plain\",\n\t\t...options,\n\t} satisfies EmailOTPOptions;\n\n\tconst sendVerificationOTPAction = sendVerificationOTP(opts);\n\n\treturn {\n\t\tid: \"email-otp\",\n\t\tinit(ctx) {\n\t\t\tif (!opts.overrideDefaultEmailVerification) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\temailVerification: {\n\t\t\t\t\t\tasync sendVerificationEmail(data, request) {\n\t\t\t\t\t\t\tawait ctx.runInBackgroundOrAwait(\n\t\t\t\t\t\t\t\tsendVerificationOTPAction({\n\t\t\t\t\t\t\t\t\tcontext: ctx,\n\t\t\t\t\t\t\t\t\trequest: request,\n\t\t\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\t\t\temail: data.user.email,\n\t\t\t\t\t\t\t\t\t\ttype: \"email-verification\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t//@ts-expect-error\n\t\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tendpoints: {\n\t\t\tsendVerificationOTP: sendVerificationOTPAction,\n\t\t\tcreateVerificationOTP: createVerificationOTP(opts),\n\t\t\tgetVerificationOTP: getVerificationOTP(opts),\n\t\t\tcheckVerificationOTP: checkVerificationOTP(opts),\n\t\t\tverifyEmailOTP: verifyEmailOTP(opts),\n\t\t\tsignInEmailOTP: signInEmailOTP(opts),\n\t\t\trequestPasswordResetEmailOTP: requestPasswordResetEmailOTP(opts),\n\t\t\tforgetPasswordEmailOTP: forgetPasswordEmailOTP(opts),\n\t\t\tresetPasswordEmailOTP: resetPasswordEmailOTP(opts),\n\t\t\trequestEmailChangeEmailOTP: requestEmailChangeEmailOTP(opts),\n\t\t\tchangeEmailEmailOTP: changeEmailEmailOTP(opts),\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-up\") &&\n\t\t\t\t\t\t\topts.sendVerificationOnSignUp &&\n\t\t\t\t\t\t\t!opts.overrideDefaultEmailVerification\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst response = await getEndpointResponse<{\n\t\t\t\t\t\t\tuser: { email: string };\n\t\t\t\t\t\t}>(ctx);\n\t\t\t\t\t\tconst email = response?.user.email;\n\t\t\t\t\t\tif (email) {\n\t\t\t\t\t\t\tconst otp =\n\t\t\t\t\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\t\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\t\t\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\t\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\t\t\t\t\tidentifier: toOTPIdentifier(\"email-verification\", email),\n\t\t\t\t\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\t\t\t\t\toptions.sendVerificationOTP(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t\t\t\t\totp,\n\t\t\t\t\t\t\t\t\t\ttype: \"email-verification\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\n\t\trateLimit: [\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/send-verification-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/check-verification-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/verify-email\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/sign-in/email-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/request-password-reset\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/reset-password\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/forget-password/email-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/request-email-change\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/change-email\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t],\n\t\toptions,\n\t\t$ERROR_CODES: EMAIL_OTP_ERROR_CODES,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { getDate } from "../../utils/date.mjs";
|
|
1
2
|
import { constantTimeEqual } from "../../crypto/buffer.mjs";
|
|
2
3
|
import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
|
|
3
|
-
import { defaultKeyHasher } from "./utils.mjs";
|
|
4
|
+
import { defaultKeyHasher, splitAtLastColon } from "./utils.mjs";
|
|
4
5
|
|
|
5
6
|
//#region src/plugins/email-otp/otp-token.ts
|
|
6
7
|
async function storeOTP(ctx, opts, otp) {
|
|
@@ -23,7 +24,35 @@ async function verifyStoredOTP(ctx, opts, storedOtp, otp) {
|
|
|
23
24
|
if (typeof opts.storeOTP === "object" && "decrypt" in opts.storeOTP) return constantTimeEqual(await opts.storeOTP.decrypt(storedOtp), otp);
|
|
24
25
|
return constantTimeEqual(otp, storedOtp);
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Retrieves the plain-text OTP from a stored value.
|
|
29
|
+
* Returns `null` if the OTP is hashed and cannot be recovered.
|
|
30
|
+
*/
|
|
31
|
+
async function retrieveOTP(ctx, opts, storedOtp) {
|
|
32
|
+
if (opts.storeOTP === "plain" || opts.storeOTP === void 0) return storedOtp;
|
|
33
|
+
if (opts.storeOTP === "encrypted") return await symmetricDecrypt({
|
|
34
|
+
key: ctx.context.secretConfig,
|
|
35
|
+
data: storedOtp
|
|
36
|
+
});
|
|
37
|
+
if (typeof opts.storeOTP === "object" && "decrypt" in opts.storeOTP) return await opts.storeOTP.decrypt(storedOtp);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Tries to reuse an existing unexpired OTP.
|
|
42
|
+
* Returns the plain-text OTP if reusable, `null` otherwise.
|
|
43
|
+
*/
|
|
44
|
+
async function tryReuseOTP(ctx, opts, identifier) {
|
|
45
|
+
const existing = await ctx.context.internalAdapter.findVerificationValue(identifier);
|
|
46
|
+
if (!existing || existing.expiresAt < /* @__PURE__ */ new Date()) return null;
|
|
47
|
+
const [storedOtpValue, attempts] = splitAtLastColon(existing.value);
|
|
48
|
+
const allowedAttempts = opts.allowedAttempts || 3;
|
|
49
|
+
if (attempts && parseInt(attempts) >= allowedAttempts) return null;
|
|
50
|
+
const plainOtp = await retrieveOTP(ctx, opts, storedOtpValue);
|
|
51
|
+
if (!plainOtp) return null;
|
|
52
|
+
await ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, { expiresAt: getDate(opts.expiresIn, "sec") });
|
|
53
|
+
return plainOtp;
|
|
54
|
+
}
|
|
26
55
|
|
|
27
56
|
//#endregion
|
|
28
|
-
export { storeOTP, verifyStoredOTP };
|
|
57
|
+
export { storeOTP, tryReuseOTP, verifyStoredOTP };
|
|
29
58
|
//# sourceMappingURL=otp-token.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-token.mjs","names":[],"sources":["../../../src/plugins/email-otp/otp-token.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"../../crypto\";\nimport type { EmailOTPOptions } from \"./types\";\nimport { defaultKeyHasher } from \"./utils\";\n\nexport async function storeOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\totp: string,\n) {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: otp,\n\t\t});\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\treturn await defaultKeyHasher(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.hash(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.encrypt(otp);\n\t}\n\n\treturn otp;\n}\n\nexport async function verifyStoredOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\tstoredOtp: string,\n\totp: string,\n): Promise<boolean> {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\tconst decryptedOtp = await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedOtp,\n\t\t});\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\tconst hashedOtp = await defaultKeyHasher(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\tconst hashedOtp = await opts.storeOTP.hash(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\tconst decryptedOtp = await opts.storeOTP.decrypt(storedOtp);\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\n\treturn constantTimeEqual(otp, storedOtp);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"otp-token.mjs","names":[],"sources":["../../../src/plugins/email-otp/otp-token.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"../../crypto\";\nimport { getDate } from \"../../utils/date\";\nimport type { EmailOTPOptions, RequiredEmailOTPOptions } from \"./types\";\nimport { defaultKeyHasher, splitAtLastColon } from \"./utils\";\n\nexport async function storeOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\totp: string,\n) {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: otp,\n\t\t});\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\treturn await defaultKeyHasher(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.hash(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.encrypt(otp);\n\t}\n\n\treturn otp;\n}\n\nexport async function verifyStoredOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\tstoredOtp: string,\n\totp: string,\n): Promise<boolean> {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\tconst decryptedOtp = await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedOtp,\n\t\t});\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\tconst hashedOtp = await defaultKeyHasher(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\tconst hashedOtp = await opts.storeOTP.hash(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\tconst decryptedOtp = await opts.storeOTP.decrypt(storedOtp);\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\n\treturn constantTimeEqual(otp, storedOtp);\n}\n\n/**\n * Retrieves the plain-text OTP from a stored value.\n * Returns `null` if the OTP is hashed and cannot be recovered.\n */\nasync function retrieveOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\tstoredOtp: string,\n): Promise<string | null> {\n\tif (opts.storeOTP === \"plain\" || opts.storeOTP === undefined) {\n\t\treturn storedOtp;\n\t}\n\tif (opts.storeOTP === \"encrypted\") {\n\t\treturn await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedOtp,\n\t\t});\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.decrypt(storedOtp);\n\t}\n\t// hashed or custom hash -> cannot recover\n\treturn null;\n}\n\n/**\n * Tries to reuse an existing unexpired OTP.\n * Returns the plain-text OTP if reusable, `null` otherwise.\n */\nexport async function tryReuseOTP(\n\tctx: GenericEndpointContext,\n\topts: RequiredEmailOTPOptions,\n\tidentifier: string,\n): Promise<string | null> {\n\tconst existing =\n\t\tawait ctx.context.internalAdapter.findVerificationValue(identifier);\n\tif (!existing || existing.expiresAt < new Date()) return null;\n\n\tconst [storedOtpValue, attempts] = splitAtLastColon(existing.value);\n\tconst allowedAttempts = opts.allowedAttempts || 3;\n\tif (attempts && parseInt(attempts) >= allowedAttempts) return null;\n\n\tconst plainOtp = await retrieveOTP(ctx, opts, storedOtpValue);\n\tif (!plainOtp) return null;\n\n\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, {\n\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t});\n\n\treturn plainOtp;\n}\n"],"mappings":";;;;;;AAUA,eAAsB,SACrB,KACA,MACA,KACC;AACD,KAAI,KAAK,aAAa,YACrB,QAAO,MAAM,iBAAiB;EAC7B,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC;AAEH,KAAI,KAAK,aAAa,SACrB,QAAO,MAAM,iBAAiB,IAAI;AAEnC,KAAI,OAAO,KAAK,aAAa,YAAY,UAAU,KAAK,SACvD,QAAO,MAAM,KAAK,SAAS,KAAK,IAAI;AAErC,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAC1D,QAAO,MAAM,KAAK,SAAS,QAAQ,IAAI;AAGxC,QAAO;;AAGR,eAAsB,gBACrB,KACA,MACA,WACA,KACmB;AACnB,KAAI,KAAK,aAAa,YAKrB,QAAO,kBAJc,MAAM,iBAAiB;EAC3C,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC,EACqC,IAAI;AAE5C,KAAI,KAAK,aAAa,SAErB,QAAO,kBADW,MAAM,iBAAiB,IAAI,EACT,UAAU;AAE/C,KAAI,OAAO,KAAK,aAAa,YAAY,UAAU,KAAK,SAEvD,QAAO,kBADW,MAAM,KAAK,SAAS,KAAK,IAAI,EACX,UAAU;AAE/C,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAE1D,QAAO,kBADc,MAAM,KAAK,SAAS,QAAQ,UAAU,EACpB,IAAI;AAG5C,QAAO,kBAAkB,KAAK,UAAU;;;;;;AAOzC,eAAe,YACd,KACA,MACA,WACyB;AACzB,KAAI,KAAK,aAAa,WAAW,KAAK,aAAa,OAClD,QAAO;AAER,KAAI,KAAK,aAAa,YACrB,QAAO,MAAM,iBAAiB;EAC7B,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC;AAEH,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAC1D,QAAO,MAAM,KAAK,SAAS,QAAQ,UAAU;AAG9C,QAAO;;;;;;AAOR,eAAsB,YACrB,KACA,MACA,YACyB;CACzB,MAAM,WACL,MAAM,IAAI,QAAQ,gBAAgB,sBAAsB,WAAW;AACpE,KAAI,CAAC,YAAY,SAAS,4BAAY,IAAI,MAAM,CAAE,QAAO;CAEzD,MAAM,CAAC,gBAAgB,YAAY,iBAAiB,SAAS,MAAM;CACnE,MAAM,kBAAkB,KAAK,mBAAmB;AAChD,KAAI,YAAY,SAAS,SAAS,IAAI,gBAAiB,QAAO;CAE9D,MAAM,WAAW,MAAM,YAAY,KAAK,MAAM,eAAe;AAC7D,KAAI,CAAC,SAAU,QAAO;AAEtB,OAAM,IAAI,QAAQ,gBAAgB,+BAA+B,YAAY,EAC5E,WAAW,QAAQ,KAAK,WAAW,MAAM,EACzC,CAAC;AAEF,QAAO"}
|
|
@@ -6,8 +6,8 @@ import { setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
|
|
|
6
6
|
import { getSessionFromCtx, sensitiveSessionMiddleware } from "../../api/routes/session.mjs";
|
|
7
7
|
import { APIError as APIError$1 } from "../../api/index.mjs";
|
|
8
8
|
import { EMAIL_OTP_ERROR_CODES } from "./error-codes.mjs";
|
|
9
|
-
import { splitAtLastColon } from "./utils.mjs";
|
|
10
|
-
import { storeOTP, verifyStoredOTP } from "./otp-token.mjs";
|
|
9
|
+
import { splitAtLastColon, toOTPIdentifier } from "./utils.mjs";
|
|
10
|
+
import { storeOTP, tryReuseOTP, verifyStoredOTP } from "./otp-token.mjs";
|
|
11
11
|
import { BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
12
12
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
13
13
|
import { deprecate } from "@better-auth/core/utils/deprecate";
|
|
@@ -20,6 +20,37 @@ const types = [
|
|
|
20
20
|
"forget-password",
|
|
21
21
|
"change-email"
|
|
22
22
|
];
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the OTP to send: reuses an existing one if possible,
|
|
25
|
+
* otherwise generates and stores a new one.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
async function resolveOTP(ctx, opts, email, type) {
|
|
30
|
+
const identifier = toOTPIdentifier(type, email);
|
|
31
|
+
if (opts.resendStrategy === "reuse") {
|
|
32
|
+
const reused = await tryReuseOTP(ctx, opts, identifier);
|
|
33
|
+
if (reused) return reused;
|
|
34
|
+
}
|
|
35
|
+
const otp = opts.generateOTP({
|
|
36
|
+
email,
|
|
37
|
+
type
|
|
38
|
+
}, ctx) || defaultOTPGenerator(opts);
|
|
39
|
+
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
40
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
41
|
+
value: `${storedOTP}:0`,
|
|
42
|
+
identifier,
|
|
43
|
+
expiresAt: getDate(opts.expiresIn, "sec")
|
|
44
|
+
}).catch(async () => {
|
|
45
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
46
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
47
|
+
value: `${storedOTP}:0`,
|
|
48
|
+
identifier,
|
|
49
|
+
expiresAt: getDate(opts.expiresIn, "sec")
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return otp;
|
|
53
|
+
}
|
|
23
54
|
const sendVerificationOTPBodySchema = z.object({
|
|
24
55
|
email: z.string({}).meta({ description: "Email address to send the OTP" }),
|
|
25
56
|
type: z.enum(types).meta({ description: "Type of the OTP" })
|
|
@@ -64,25 +95,11 @@ const sendVerificationOTP = (opts) => createAuthEndpoint("/email-otp/send-verifi
|
|
|
64
95
|
ctx.context.logger.error("Use the /email-otp/request-email-change endpoint to send OTP for changing email");
|
|
65
96
|
throw APIError$1.fromStatus("BAD_REQUEST", { message: "Invalid OTP type" });
|
|
66
97
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await ctx.context.internalAdapter.createVerificationValue({
|
|
73
|
-
value: `${storedOTP}:0`,
|
|
74
|
-
identifier: `${ctx.body.type}-otp-${email}`,
|
|
75
|
-
expiresAt: getDate(opts.expiresIn, "sec")
|
|
76
|
-
}).catch(async (error) => {
|
|
77
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${ctx.body.type}-otp-${email}`);
|
|
78
|
-
await ctx.context.internalAdapter.createVerificationValue({
|
|
79
|
-
value: `${storedOTP}:0`,
|
|
80
|
-
identifier: `${ctx.body.type}-otp-${email}`,
|
|
81
|
-
expiresAt: getDate(opts.expiresIn, "sec")
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
if (!await ctx.context.internalAdapter.findUserByEmail(email)) if (ctx.body.type === "sign-in" && !opts.disableSignUp) {} else {
|
|
85
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${ctx.body.type}-otp-${email}`);
|
|
98
|
+
const identifier = toOTPIdentifier(ctx.body.type, email);
|
|
99
|
+
const otp = await resolveOTP(ctx, opts, email, ctx.body.type);
|
|
100
|
+
const shouldSendOTP = ctx.body.type === "sign-in" && !opts.disableSignUp;
|
|
101
|
+
if (!await ctx.context.internalAdapter.findUserByEmail(email) && !shouldSendOTP) {
|
|
102
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
86
103
|
return ctx.json({ success: true });
|
|
87
104
|
}
|
|
88
105
|
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
|
|
@@ -119,7 +136,7 @@ const createVerificationOTP = (opts) => createAuthEndpoint({
|
|
|
119
136
|
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
120
137
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
121
138
|
value: `${storedOTP}:0`,
|
|
122
|
-
identifier:
|
|
139
|
+
identifier: toOTPIdentifier(ctx.body.type, email),
|
|
123
140
|
expiresAt: getDate(opts.expiresIn, "sec")
|
|
124
141
|
});
|
|
125
142
|
return otp;
|
|
@@ -164,7 +181,7 @@ const getVerificationOTP = (opts) => createAuthEndpoint({
|
|
|
164
181
|
} }
|
|
165
182
|
}, async (ctx) => {
|
|
166
183
|
const email = ctx.query.email.toLowerCase();
|
|
167
|
-
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(
|
|
184
|
+
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(toOTPIdentifier(ctx.query.type, email));
|
|
168
185
|
if (!verificationValue || verificationValue.expiresAt < /* @__PURE__ */ new Date()) return ctx.json({ otp: null });
|
|
169
186
|
if (opts.storeOTP === "hashed" || typeof opts.storeOTP === "object" && "hash" in opts.storeOTP) throw APIError$1.fromStatus("BAD_REQUEST", { message: "OTP is hashed, cannot return the plain text OTP" });
|
|
170
187
|
const [storedOtp, _attempts] = splitAtLastColon(verificationValue.value);
|
|
@@ -217,21 +234,21 @@ const checkVerificationOTP = (opts) => createAuthEndpoint("/email-otp/check-veri
|
|
|
217
234
|
const email = ctx.body.email.toLowerCase();
|
|
218
235
|
if (!z.email().safeParse(email).success) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
|
|
219
236
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
220
|
-
const
|
|
237
|
+
const identifier = toOTPIdentifier(ctx.body.type, email);
|
|
238
|
+
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(identifier);
|
|
221
239
|
if (!verificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
|
|
222
|
-
const otpIdentifier = `${ctx.body.type}-otp-${email}`;
|
|
223
240
|
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
|
|
224
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(
|
|
241
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
225
242
|
throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.OTP_EXPIRED);
|
|
226
243
|
}
|
|
227
244
|
const [otpValue, attempts] = splitAtLastColon(verificationValue.value);
|
|
228
245
|
const allowedAttempts = opts?.allowedAttempts || 3;
|
|
229
246
|
if (attempts && parseInt(attempts) >= allowedAttempts) {
|
|
230
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(
|
|
247
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
231
248
|
throw APIError$1.from("FORBIDDEN", EMAIL_OTP_ERROR_CODES.TOO_MANY_ATTEMPTS);
|
|
232
249
|
}
|
|
233
250
|
if (!await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp)) {
|
|
234
|
-
await ctx.context.internalAdapter.updateVerificationByIdentifier(
|
|
251
|
+
await ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
|
|
235
252
|
throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
|
|
236
253
|
}
|
|
237
254
|
return ctx.json({ success: true });
|
|
@@ -291,7 +308,7 @@ const verifyEmailOTP = (opts) => createAuthEndpoint("/email-otp/verify-email", {
|
|
|
291
308
|
}, async (ctx) => {
|
|
292
309
|
const email = ctx.body.email.toLowerCase();
|
|
293
310
|
if (!z.email().safeParse(email).success) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
|
|
294
|
-
await atomicVerifyOTP(ctx, opts,
|
|
311
|
+
await atomicVerifyOTP(ctx, opts, toOTPIdentifier("email-verification", email), ctx.body.otp);
|
|
295
312
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
296
313
|
if (!user)
|
|
297
314
|
/**
|
|
@@ -382,7 +399,7 @@ const signInEmailOTP = (opts) => createAuthEndpoint("/sign-in/email-otp", {
|
|
|
382
399
|
}, async (ctx) => {
|
|
383
400
|
const { email: rawEmail, otp, name, image, ...rest } = ctx.body;
|
|
384
401
|
const email = rawEmail.toLowerCase();
|
|
385
|
-
await atomicVerifyOTP(ctx, opts,
|
|
402
|
+
await atomicVerifyOTP(ctx, opts, toOTPIdentifier("sign-in", email), otp);
|
|
386
403
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
387
404
|
if (!user) {
|
|
388
405
|
if (opts.disableSignUp) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
|
|
@@ -450,18 +467,10 @@ const requestPasswordResetEmailOTP = (opts) => createAuthEndpoint("/email-otp/re
|
|
|
450
467
|
} }
|
|
451
468
|
}, async (ctx) => {
|
|
452
469
|
const email = ctx.body.email;
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
type: "forget-password"
|
|
456
|
-
}, ctx) || defaultOTPGenerator(opts);
|
|
457
|
-
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
458
|
-
await ctx.context.internalAdapter.createVerificationValue({
|
|
459
|
-
value: `${storedOTP}:0`,
|
|
460
|
-
identifier: `forget-password-otp-${email}`,
|
|
461
|
-
expiresAt: getDate(opts.expiresIn, "sec")
|
|
462
|
-
});
|
|
470
|
+
const identifier = toOTPIdentifier("forget-password", email);
|
|
471
|
+
const otp = await resolveOTP(ctx, opts, email, "forget-password");
|
|
463
472
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
|
|
464
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(
|
|
473
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
465
474
|
return ctx.json({ success: true });
|
|
466
475
|
}
|
|
467
476
|
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
|
|
@@ -510,18 +519,10 @@ const forgetPasswordEmailOTP = (opts) => {
|
|
|
510
519
|
}, async (ctx) => {
|
|
511
520
|
warnDeprecation();
|
|
512
521
|
const email = ctx.body.email;
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
type: "forget-password"
|
|
516
|
-
}, ctx) || defaultOTPGenerator(opts);
|
|
517
|
-
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
518
|
-
await ctx.context.internalAdapter.createVerificationValue({
|
|
519
|
-
value: `${storedOTP}:0`,
|
|
520
|
-
identifier: `forget-password-otp-${email}`,
|
|
521
|
-
expiresAt: getDate(opts.expiresIn, "sec")
|
|
522
|
-
});
|
|
522
|
+
const identifier = toOTPIdentifier("forget-password", email);
|
|
523
|
+
const otp = await resolveOTP(ctx, opts, email, "forget-password");
|
|
523
524
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
|
|
524
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(
|
|
525
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
|
|
525
526
|
return ctx.json({ success: true });
|
|
526
527
|
}
|
|
527
528
|
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
|
|
@@ -568,7 +569,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
|
|
|
568
569
|
} }
|
|
569
570
|
}, async (ctx) => {
|
|
570
571
|
const email = ctx.body.email;
|
|
571
|
-
await atomicVerifyOTP(ctx, opts,
|
|
572
|
+
await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
|
|
572
573
|
const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
|
|
573
574
|
if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
574
575
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
|
@@ -636,9 +637,9 @@ const requestEmailChangeEmailOTP = (opts) => createAuthEndpoint("/email-otp/requ
|
|
|
636
637
|
}
|
|
637
638
|
if (opts.changeEmail?.verifyCurrentEmail) {
|
|
638
639
|
if (!ctx.body.otp) throw APIError$1.fromStatus("BAD_REQUEST", { message: "OTP is required to verify current email" });
|
|
639
|
-
const currentEmailVerificationValue = await ctx.context.internalAdapter.findVerificationValue(
|
|
640
|
+
const currentEmailVerificationValue = await ctx.context.internalAdapter.findVerificationValue(toOTPIdentifier("email-verification", email));
|
|
640
641
|
if (!currentEmailVerificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
|
|
641
|
-
const currentEmailIdentifier =
|
|
642
|
+
const currentEmailIdentifier = toOTPIdentifier("email-verification", email);
|
|
642
643
|
if (currentEmailVerificationValue.expiresAt < /* @__PURE__ */ new Date()) {
|
|
643
644
|
await ctx.context.internalAdapter.deleteVerificationByIdentifier(currentEmailIdentifier);
|
|
644
645
|
throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.OTP_EXPIRED);
|
|
@@ -662,11 +663,11 @@ const requestEmailChangeEmailOTP = (opts) => createAuthEndpoint("/email-otp/requ
|
|
|
662
663
|
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
663
664
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
664
665
|
value: `${storedOTP}:0`,
|
|
665
|
-
identifier:
|
|
666
|
+
identifier: toOTPIdentifier("change-email", `${email}-${newEmail}`),
|
|
666
667
|
expiresAt: getDate(opts.expiresIn, "sec")
|
|
667
668
|
});
|
|
668
669
|
if (await ctx.context.internalAdapter.findUserByEmail(newEmail)) {
|
|
669
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(
|
|
670
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(toOTPIdentifier("change-email", `${email}-${newEmail}`));
|
|
670
671
|
return ctx.json({ success: true });
|
|
671
672
|
}
|
|
672
673
|
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
|
|
@@ -723,9 +724,9 @@ const changeEmailEmailOTP = (opts) => createAuthEndpoint("/email-otp/change-emai
|
|
|
723
724
|
ctx.context.logger.error("Email is the same");
|
|
724
725
|
throw APIError$1.fromStatus("BAD_REQUEST", { message: "Email is the same" });
|
|
725
726
|
}
|
|
726
|
-
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(
|
|
727
|
+
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(toOTPIdentifier("change-email", `${email}-${newEmail}`));
|
|
727
728
|
if (!verificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
|
|
728
|
-
const changeEmailIdentifier =
|
|
729
|
+
const changeEmailIdentifier = toOTPIdentifier("change-email", `${email}-${newEmail}`);
|
|
729
730
|
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
|
|
730
731
|
await ctx.context.internalAdapter.deleteVerificationByIdentifier(changeEmailIdentifier);
|
|
731
732
|
throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.OTP_EXPIRED);
|