UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 17.3 kB
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/plugins/two-factor/otp/index.ts"],"sourcesContent":["import type { Awaitable, GenericEndpointContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { BASE_ERROR_CODES } from \"@better-auth/core/error\";\nimport { APIError } from \"better-call\";\nimport * as z from \"zod\";\nimport { setSessionCookie } from \"../../../cookies\";\nimport {\n\tconstantTimeEqual,\n\tgenerateRandomString,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"../../../crypto\";\nimport { TWO_FACTOR_ERROR_CODES } from \"../error-code\";\nimport type { TwoFactorProvider, UserWithTwoFactor } from \"../types\";\nimport { defaultKeyHasher } from \"../utils\";\nimport { verifyTwoFactor } from \"../verify-two-factor\";\n\nexport interface OTPOptions {\n\t/**\n\t * How long the opt will be valid for in\n\t * minutes\n\t *\n\t * @default \"3 mins\"\n\t */\n\tperiod?: number | undefined;\n\t/**\n\t * Number of digits for the OTP code\n\t *\n\t * @default 6\n\t */\n\tdigits?: number | undefined;\n\t/**\n\t * Send the otp to the user\n\t *\n\t * @param user - The user to send the otp to\n\t * @param otp - The otp to send\n\t * @param request - The request object\n\t * @returns void | Promise<void>\n\t */\n\tsendOTP?:\n\t\t| ((\n\t\t\t\t/**\n\t\t\t\t * The user to send the otp to\n\t\t\t\t * @type UserWithTwoFactor\n\t\t\t\t * @default UserWithTwoFactors\n\t\t\t\t */\n\t\t\t\tdata: {\n\t\t\t\t\tuser: UserWithTwoFactor;\n\t\t\t\t\totp: string;\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The request object\n\t\t\t\t */\n\t\t\t\tctx?: GenericEndpointContext,\n\t\t ) => Awaitable<void>)\n\t\t| undefined;\n\t/**\n\t * The number of allowed attempts for the OTP\n\t *\n\t * @default 5\n\t */\n\tallowedAttempts?: number | undefined;\n\tstoreOTP?:\n\t\t| (\n\t\t\t\t| \"plain\"\n\t\t\t\t| \"encrypted\"\n\t\t\t\t| \"hashed\"\n\t\t\t\t| { hash: (token: string) => Promise<string> }\n\t\t\t\t| {\n\t\t\t\t\t\tencrypt: (token: string) => Promise<string>;\n\t\t\t\t\t\tdecrypt: (token: string) => Promise<string>;\n\t\t\t\t }\n\t\t )\n\t\t| undefined;\n}\n\nconst verifyOTPBodySchema = z.object({\n\tcode: z.string().meta({\n\t\tdescription: 'The otp code to verify. Eg: \"012345\"',\n\t}),\n\t/**\n\t * if true, the device will be trusted\n\t * for 30 days. It'll be refreshed on\n\t * every sign in request within this time.\n\t */\n\ttrustDevice: z.boolean().optional().meta({\n\t\tdescription:\n\t\t\t\"If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true\",\n\t}),\n});\n\nconst send2FaOTPBodySchema = z\n\t.object({\n\t\t/**\n\t\t * if true, the device will be trusted\n\t\t * for 30 days. It'll be refreshed on\n\t\t * every sign in request within this time.\n\t\t */\n\t\ttrustDevice: z.boolean().optional().meta({\n\t\t\tdescription:\n\t\t\t\t\"If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true\",\n\t\t}),\n\t})\n\t.optional();\n\n/**\n * The otp adapter is created from the totp adapter.\n */\nexport const otp2fa = (options?: OTPOptions | undefined) => {\n\tconst opts = {\n\t\tstoreOTP: \"plain\",\n\t\tdigits: 6,\n\t\t...options,\n\t\tperiod: (options?.period || 3) * 60 * 1000,\n\t};\n\n\tasync function storeOTP(ctx: GenericEndpointContext, otp: string) {\n\t\tif (opts.storeOTP === \"hashed\") {\n\t\t\treturn await defaultKeyHasher(otp);\n\t\t}\n\t\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\t\treturn await opts.storeOTP.hash(otp);\n\t\t}\n\t\tif (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n\t\t\treturn await opts.storeOTP.encrypt(otp);\n\t\t}\n\t\tif (opts.storeOTP === \"encrypted\") {\n\t\t\treturn await symmetricEncrypt({\n\t\t\t\tkey: ctx.context.secret,\n\t\t\t\tdata: otp,\n\t\t\t});\n\t\t}\n\t\treturn otp;\n\t}\n\n\tasync function decryptOTP(ctx: GenericEndpointContext, otp: string) {\n\t\tif (opts.storeOTP === \"hashed\") {\n\t\t\treturn await defaultKeyHasher(otp);\n\t\t}\n\t\tif (opts.storeOTP === \"encrypted\") {\n\t\t\treturn await symmetricDecrypt({\n\t\t\t\tkey: ctx.context.secret,\n\t\t\t\tdata: otp,\n\t\t\t});\n\t\t}\n\t\tif (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n\t\t\treturn await opts.storeOTP.decrypt(otp);\n\t\t}\n\t\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\t\treturn await opts.storeOTP.hash(otp);\n\t\t}\n\t\treturn otp;\n\t}\n\n\t/**\n\t * Generate OTP and send it to the user.\n\t */\n\tconst send2FaOTP = createAuthEndpoint(\n\t\t\"/two-factor/send-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: send2FaOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tsummary: \"Send two factor OTP\",\n\t\t\t\t\tdescription: \"Send two factor OTP to the user\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Successful response\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tstatus: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\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\tasync (ctx) => {\n\t\t\tif (!options || !options.sendOTP) {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"send otp isn't configured. Please configure the send otp function on otp options.\",\n\t\t\t\t);\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"otp isn't configured\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst { session, key } = await verifyTwoFactor(ctx);\n\t\t\tconst code = generateRandomString(opts.digits, \"0-9\");\n\t\t\tconst hashedCode = await storeOTP(ctx, code);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${hashedCode}:0`,\n\t\t\t\tidentifier: `2fa-otp-${key}`,\n\t\t\t\texpiresAt: new Date(Date.now() + opts.period),\n\t\t\t});\n\t\t\tconst sendOTPResult = options.sendOTP(\n\t\t\t\t{ user: session.user as UserWithTwoFactor, otp: code },\n\t\t\t\tctx,\n\t\t\t);\n\t\t\tif (sendOTPResult instanceof Promise) {\n\t\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\t\tsendOTPResult.catch((e: unknown) => {\n\t\t\t\t\t\tctx.context.logger.error(\"Failed to send two-factor OTP\", e);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn ctx.json({ status: true });\n\t\t},\n\t);\n\n\tconst verifyOTP = createAuthEndpoint(\n\t\t\"/two-factor/verify-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tsummary: \"Verify two factor OTP\",\n\t\t\t\t\tdescription: \"Verify two factor OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"Two-factor OTP verified successfully\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\ttoken: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Session token for the authenticated session\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Unique identifier of the user\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\temail: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"email\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"User's email address\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\temailVerified: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Whether the email is verified\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"User's name\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\timage: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"uri\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"User's profile image URL\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tcreatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Timestamp when the user was created\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tupdatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Timestamp when the user was last updated\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\trequired: [\"id\", \"createdAt\", \"updatedAt\"],\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The authenticated user object\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"token\", \"user\"],\n\t\t\t\t\t\t\t\t\t},\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\tasync (ctx) => {\n\t\t\tconst { session, key, valid, invalid } = await verifyTwoFactor(ctx);\n\t\t\tconst toCheckOtp =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t`2fa-otp-${key}`,\n\t\t\t\t);\n\t\t\tconst [otp, counter] = toCheckOtp?.value?.split(\":\") ?? [];\n\t\t\tconst decryptedOtp = await decryptOTP(ctx, otp!);\n\t\t\tif (!toCheckOtp || toCheckOtp.expiresAt < new Date()) {\n\t\t\t\tif (toCheckOtp) {\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationValue(\n\t\t\t\t\t\ttoCheckOtp.id,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.OTP_HAS_EXPIRED,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst allowedAttempts = options?.allowedAttempts || 5;\n\t\t\tif (parseInt(counter!) >= allowedAttempts) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationValue(\n\t\t\t\t\ttoCheckOtp.id,\n\t\t\t\t);\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst isCodeValid = constantTimeEqual(\n\t\t\t\tnew TextEncoder().encode(decryptedOtp),\n\t\t\t\tnew TextEncoder().encode(ctx.body.code),\n\t\t\t);\n\t\t\tif (isCodeValid) {\n\t\t\t\tif (!session.user.twoFactorEnabled) {\n\t\t\t\t\tif (!session.session) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst updatedUser = await ctx.context.internalAdapter.updateUser(\n\t\t\t\t\t\tsession.user.id,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttwoFactorEnabled: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tconst newSession = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\t\tsession.user.id,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tsession.session,\n\t\t\t\t\t);\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteSession(\n\t\t\t\t\t\tsession.session.token,\n\t\t\t\t\t);\n\t\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\t\tsession: newSession,\n\t\t\t\t\t\tuser: updatedUser,\n\t\t\t\t\t});\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\ttoken: newSession.token,\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tid: updatedUser.id,\n\t\t\t\t\t\t\temail: updatedUser.email,\n\t\t\t\t\t\t\temailVerified: updatedUser.emailVerified,\n\t\t\t\t\t\t\tname: updatedUser.name,\n\t\t\t\t\t\t\timage: updatedUser.image,\n\t\t\t\t\t\t\tcreatedAt: updatedUser.createdAt,\n\t\t\t\t\t\t\tupdatedAt: updatedUser.updatedAt,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn valid(ctx);\n\t\t\t} else {\n\t\t\t\tawait ctx.context.internalAdapter.updateVerificationValue(\n\t\t\t\t\ttoCheckOtp.id,\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: `${otp}:${(parseInt(counter!, 10) || 0) + 1}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn invalid(\"INVALID_CODE\");\n\t\t\t}\n\t\t},\n\t);\n\n\treturn {\n\t\tid: \"otp\",\n\t\tendpoints: {\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/send-otp`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.send2FaOTP`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.twoFactor.sendOtp`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-send-otp)\n\t\t\t */\n\t\t\tsendTwoFactorOTP: send2FaOTP,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/verify-otp`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.verifyOTP`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.twoFactor.verifyOtp`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-verify-otp)\n\t\t\t */\n\t\t\tverifyTwoFactorOTP: verifyOTP,\n\t\t},\n\t} satisfies TwoFactorProvider;\n};\n"],"mappings":"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"}