UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 22.2 kB
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/plugins/two-factor/backup-codes/index.ts"],"sourcesContent":["import { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { safeJSONParse } from \"@better-auth/core/utils\";\nimport { APIError } from \"better-call\";\nimport * as z from \"zod\";\nimport { sessionMiddleware } from \"../../../api\";\nimport { symmetricDecrypt, symmetricEncrypt } from \"../../../crypto\";\nimport { generateRandomString } from \"../../../crypto/random\";\nimport { TWO_FACTOR_ERROR_CODES } from \"../error-code\";\nimport type {\n\tTwoFactorProvider,\n\tTwoFactorTable,\n\tUserWithTwoFactor,\n} from \"../types\";\nimport { verifyTwoFactor } from \"../verify-two-factor\";\n\nexport interface BackupCodeOptions {\n\t/**\n\t * The amount of backup codes to generate\n\t *\n\t * @default 10\n\t */\n\tamount?: number | undefined;\n\t/**\n\t * The length of the backup codes\n\t *\n\t * @default 10\n\t */\n\tlength?: number | undefined;\n\t/**\n\t * An optional custom function to generate backup codes\n\t */\n\tcustomBackupCodesGenerate?: (() => string[]) | undefined;\n\t/**\n\t * How to store the backup codes in the database, whether encrypted or plain.\n\t */\n\tstoreBackupCodes?:\n\t\t| (\n\t\t\t\t| \"plain\"\n\t\t\t\t| \"encrypted\"\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\nfunction generateBackupCodesFn(options?: BackupCodeOptions | undefined) {\n\treturn Array.from({ length: options?.amount ?? 10 })\n\t\t.fill(null)\n\t\t.map(() => generateRandomString(options?.length ?? 10, \"a-z\", \"0-9\", \"A-Z\"))\n\t\t.map((code) => `${code.slice(0, 5)}-${code.slice(5)}`);\n}\n\nexport async function generateBackupCodes(\n\tsecret: string,\n\toptions?: BackupCodeOptions | undefined,\n) {\n\tconst backupCodes = options?.customBackupCodesGenerate\n\t\t? options.customBackupCodesGenerate()\n\t\t: generateBackupCodesFn(options);\n\tif (options?.storeBackupCodes === \"encrypted\") {\n\t\tconst encCodes = await symmetricEncrypt({\n\t\t\tdata: JSON.stringify(backupCodes),\n\t\t\tkey: secret,\n\t\t});\n\t\treturn {\n\t\t\tbackupCodes,\n\t\t\tencryptedBackupCodes: encCodes,\n\t\t};\n\t}\n\tif (\n\t\ttypeof options?.storeBackupCodes === \"object\" &&\n\t\t\"encrypt\" in options?.storeBackupCodes\n\t) {\n\t\treturn {\n\t\t\tbackupCodes,\n\t\t\tencryptedBackupCodes: await options?.storeBackupCodes.encrypt(\n\t\t\t\tJSON.stringify(backupCodes),\n\t\t\t),\n\t\t};\n\t}\n\treturn {\n\t\tbackupCodes,\n\t\tencryptedBackupCodes: JSON.stringify(backupCodes),\n\t};\n}\n\nexport async function verifyBackupCode(\n\tdata: {\n\t\tbackupCodes: string;\n\t\tcode: string;\n\t},\n\tkey: string,\n\toptions?: BackupCodeOptions | undefined,\n) {\n\tconst codes = await getBackupCodes(data.backupCodes, key, options);\n\tif (!codes) {\n\t\treturn {\n\t\t\tstatus: false,\n\t\t\tupdated: null,\n\t\t};\n\t}\n\treturn {\n\t\tstatus: codes.includes(data.code),\n\t\tupdated: codes.filter((code) => code !== data.code),\n\t};\n}\n\nexport async function getBackupCodes(\n\tbackupCodes: string,\n\tkey: string,\n\toptions?: BackupCodeOptions | undefined,\n) {\n\tif (options?.storeBackupCodes === \"encrypted\") {\n\t\tconst decrypted = await symmetricDecrypt({ key, data: backupCodes });\n\t\treturn safeJSONParse<string[]>(decrypted);\n\t}\n\tif (\n\t\ttypeof options?.storeBackupCodes === \"object\" &&\n\t\t\"decrypt\" in options?.storeBackupCodes\n\t) {\n\t\tconst decrypted = await options?.storeBackupCodes.decrypt(backupCodes);\n\t\treturn safeJSONParse<string[]>(decrypted);\n\t}\n\n\treturn safeJSONParse<string[]>(backupCodes);\n}\n\nconst verifyBackupCodeBodySchema = z.object({\n\tcode: z.string().meta({\n\t\tdescription: `A backup code to verify. Eg: \"123456\"`,\n\t}),\n\t/**\n\t * Disable setting the session cookie\n\t */\n\tdisableSession: z\n\t\t.boolean()\n\t\t.meta({\n\t\t\tdescription: \"If true, the session cookie will not be set.\",\n\t\t})\n\t\t.optional(),\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\n\t\t.boolean()\n\t\t.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\t.optional(),\n});\n\nconst viewBackupCodesBodySchema = z.object({\n\tuserId: z.coerce.string().meta({\n\t\tdescription: `The user ID to view all backup codes. Eg: \"user-id\"`,\n\t}),\n});\n\nconst generateBackupCodesBodySchema = z.object({\n\tpassword: z.string().meta({\n\t\tdescription: \"The users password.\",\n\t}),\n});\n\nexport const backupCode2fa = (opts: BackupCodeOptions) => {\n\tconst twoFactorTable = \"twoFactor\";\n\n\treturn {\n\t\tid: \"backup_code\",\n\t\tendpoints: {\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/verify-backup-code`\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.verifyBackupCode`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.twoFactor.verifyBackupCode`\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-backup-code)\n\t\t\t */\n\t\t\tverifyBackupCode: createAuthEndpoint(\n\t\t\t\t\"/two-factor/verify-backup-code\",\n\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: verifyBackupCodeBodySchema,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tdescription: \"Verify a backup code for two-factor authentication\",\n\t\t\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\t\t\tdescription: \"Backup code verified successfully\",\n\t\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\t\t\tschema: {\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\tuser: {\n\t\t\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\t\t\tproperties: {\n\t\t\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\t\t\ttype: \"string\",\n\t\t\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\t\t},\n\t\t\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\t\t\ttype: \"string\",\n\t\t\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\t\t\tnullable: true,\n\t\t\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\t\t},\n\t\t\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\t\t\ttype: \"boolean\",\n\t\t\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\t\t\tdescription: \"Whether the email is verified\",\n\t\t\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\t\t\tname: {\n\t\t\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\t\t\tnullable: true,\n\t\t\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\t\t},\n\t\t\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\t\t\ttype: \"string\",\n\t\t\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\t\t\tnullable: true,\n\t\t\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\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttwoFactorEnabled: {\n\t\t\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\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Whether two-factor authentication is enabled for the user\",\n\t\t\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\t\t\tcreatedAt: {\n\t\t\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\t\t\tformat: \"date-time\",\n\t\t\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\t\t\"Timestamp when the user was created\",\n\t\t\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\t\t\tupdatedAt: {\n\t\t\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\t\t\tformat: \"date-time\",\n\t\t\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\t\t\"Timestamp when the user was last updated\",\n\t\t\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\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"twoFactorEnabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"updatedAt\",\n\t\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\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"The authenticated user object with two-factor details\",\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\tsession: {\n\t\t\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\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttoken: {\n\t\t\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\t\t\tdescription: \"Session token\",\n\t\t\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\t\t\tuserId: {\n\t\t\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\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"ID of the user associated with the session\",\n\t\t\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\t\t\tcreatedAt: {\n\t\t\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\t\t\tformat: \"date-time\",\n\t\t\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\t\t\"Timestamp when the session was created\",\n\t\t\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\t\t\texpiresAt: {\n\t\t\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\t\t\tformat: \"date-time\",\n\t\t\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\t\t\"Timestamp when the session expires\",\n\t\t\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\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"token\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"userId\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"expiresAt\",\n\t\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\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"The current session object, included unless disableSession is true\",\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: [\"user\", \"session\"],\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\tasync (ctx) => {\n\t\t\t\t\tconst { session, valid } = await verifyTwoFactor(ctx);\n\t\t\t\t\tconst user = session.user as UserWithTwoFactor;\n\t\t\t\t\tconst twoFactor = await ctx.context.adapter.findOne<TwoFactorTable>({\n\t\t\t\t\t\tmodel: twoFactorTable,\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\t\t\tvalue: user.id,\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\tif (!twoFactor) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst validate = await verifyBackupCode(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbackupCodes: twoFactor.backupCodes,\n\t\t\t\t\t\t\tcode: ctx.body.code,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t\topts,\n\t\t\t\t\t);\n\t\t\t\t\tif (!validate.status) {\n\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst updatedBackupCodes = await symmetricEncrypt({\n\t\t\t\t\t\tkey: ctx.context.secret,\n\t\t\t\t\t\tdata: JSON.stringify(validate.updated),\n\t\t\t\t\t});\n\n\t\t\t\t\tconst updated = await ctx.context.adapter.updateMany({\n\t\t\t\t\t\tmodel: twoFactorTable,\n\t\t\t\t\t\tupdate: {\n\t\t\t\t\t\t\tbackupCodes: updatedBackupCodes,\n\t\t\t\t\t\t},\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\t\t\tvalue: user.id,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"backupCodes\",\n\t\t\t\t\t\t\t\tvalue: twoFactor.backupCodes,\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\tif (!updated) {\n\t\t\t\t\t\tthrow new APIError(\"CONFLICT\", {\n\t\t\t\t\t\t\tmessage: \"Failed to verify backup code. Please try again.\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!ctx.body.disableSession) {\n\t\t\t\t\t\treturn valid(ctx);\n\t\t\t\t\t}\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\ttoken: session.session?.token,\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tid: session.user?.id,\n\t\t\t\t\t\t\temail: session.user.email,\n\t\t\t\t\t\t\temailVerified: session.user.emailVerified,\n\t\t\t\t\t\t\tname: session.user.name,\n\t\t\t\t\t\t\timage: session.user.image,\n\t\t\t\t\t\t\tcreatedAt: session.user.createdAt,\n\t\t\t\t\t\t\tupdatedAt: session.user.updatedAt,\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\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/generate-backup-codes`\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.generateBackupCodes`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.twoFactor.generateBackupCodes`\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-generate-backup-codes)\n\t\t\t */\n\t\t\tgenerateBackupCodes: createAuthEndpoint(\n\t\t\t\t\"/two-factor/generate-backup-codes\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: generateBackupCodesBodySchema,\n\t\t\t\t\tuse: [sessionMiddleware],\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Generate new backup codes for two-factor authentication\",\n\t\t\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\t\t\tdescription: \"Backup codes generated successfully\",\n\t\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\t\t\tschema: {\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\tstatus: {\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\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the backup codes were generated successfully\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tenum: [true],\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\tbackupCodes: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titems: { type: \"string\" },\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\"Array of generated backup codes in plain text\",\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: [\"status\", \"backupCodes\"],\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\tasync (ctx) => {\n\t\t\t\t\tconst user = ctx.context.session.user as UserWithTwoFactor;\n\t\t\t\t\tif (!user.twoFactorEnabled) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.TWO_FACTOR_NOT_ENABLED,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tawait ctx.context.password.checkPassword(user.id, ctx);\n\t\t\t\t\tconst backupCodes = await generateBackupCodes(\n\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t\topts,\n\t\t\t\t\t);\n\t\t\t\t\tawait ctx.context.adapter.updateMany({\n\t\t\t\t\t\tmodel: twoFactorTable,\n\t\t\t\t\t\tupdate: {\n\t\t\t\t\t\t\tbackupCodes: backupCodes.encryptedBackupCodes,\n\t\t\t\t\t\t},\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\t\t\tvalue: ctx.context.session.user.id,\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\treturn ctx.json({\n\t\t\t\t\t\tstatus: true,\n\t\t\t\t\t\tbackupCodes: backupCodes.backupCodes,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t),\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/view-backup-codes`\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.viewBackupCodes`\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-view-backup-codes)\n\t\t\t */\n\t\t\tviewBackupCodes: createAuthEndpoint(\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: viewBackupCodesBodySchema,\n\t\t\t\t},\n\t\t\t\tasync (ctx) => {\n\t\t\t\t\tconst twoFactor = await ctx.context.adapter.findOne<TwoFactorTable>({\n\t\t\t\t\t\tmodel: twoFactorTable,\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\t\t\tvalue: ctx.body.userId,\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\tif (!twoFactor) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst decryptedBackupCodes = await getBackupCodes(\n\t\t\t\t\t\ttwoFactor.backupCodes,\n\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t\topts,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!decryptedBackupCodes) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\tstatus: true,\n\t\t\t\t\t\tbackupCodes: decryptedBackupCodes,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t} satisfies TwoFactorProvider;\n};\n"],"mappings":"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"}