/
opt
/
canhelp
/
node_modules
/
better-auth
/
dist
/
plugins
/
two-factor
/
backup-codes
/
/opt/canhelp/node_modules/better-auth/dist/plugins/two-factor/backup-codes
mkdir
upload
Name
Size
Mode
Actions
index.d.mts
8862
0644
edit
dl
rm
index.mjs
9791
0644
edit
dl
rm
index.mjs.map
22675
0644
edit
dl
rm
Edit:
/opt/canhelp/node_modules/better-auth/dist/plugins/two-factor/backup-codes/index.mjs
(9791B)
import { parseUserOutput } from "../../../db/schema.mjs"; import { generateRandomString } from "../../../crypto/random.mjs"; import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto/index.mjs"; import { sessionMiddleware } from "../../../api/routes/session.mjs"; import "../../../api/index.mjs"; import { TWO_FACTOR_ERROR_CODES } from "../error-code.mjs"; import { verifyTwoFactor } from "../verify-two-factor.mjs"; import { APIError } from "@better-auth/core/error"; import { safeJSONParse } from "@better-auth/core/utils/json"; import { createAuthEndpoint } from "@better-auth/core/api"; import * as z from "zod"; //#region src/plugins/two-factor/backup-codes/index.ts function generateBackupCodesFn(options) { return Array.from({ length: options?.amount ?? 10 }).fill(null).map(() => generateRandomString(options?.length ?? 10, "a-z", "0-9", "A-Z")).map((code) => `${code.slice(0, 5)}-${code.slice(5)}`); } async function generateBackupCodes(secret, options) { const backupCodes = options?.customBackupCodesGenerate ? options.customBackupCodesGenerate() : generateBackupCodesFn(options); if (options?.storeBackupCodes === "encrypted") return { backupCodes, encryptedBackupCodes: await symmetricEncrypt({ data: JSON.stringify(backupCodes), key: secret }) }; if (typeof options?.storeBackupCodes === "object" && "encrypt" in options?.storeBackupCodes) return { backupCodes, encryptedBackupCodes: await options?.storeBackupCodes.encrypt(JSON.stringify(backupCodes)) }; return { backupCodes, encryptedBackupCodes: JSON.stringify(backupCodes) }; } async function verifyBackupCode(data, key, options) { const codes = await getBackupCodes(data.backupCodes, key, options); if (!codes) return { status: false, updated: null }; return { status: codes.includes(data.code), updated: codes.filter((code) => code !== data.code) }; } async function getBackupCodes(backupCodes, key, options) { if (options?.storeBackupCodes === "encrypted") return safeJSONParse(await symmetricDecrypt({ key, data: backupCodes })); if (typeof options?.storeBackupCodes === "object" && "decrypt" in options?.storeBackupCodes) return safeJSONParse(await options?.storeBackupCodes.decrypt(backupCodes)); return safeJSONParse(backupCodes); } const verifyBackupCodeBodySchema = z.object({ code: z.string().meta({ description: `A backup code to verify. Eg: "123456"` }), disableSession: z.boolean().meta({ description: "If true, the session cookie will not be set." }).optional(), trustDevice: z.boolean().meta({ description: "If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true" }).optional() }); const viewBackupCodesBodySchema = z.object({ userId: z.coerce.string().meta({ description: `The user ID to view all backup codes. Eg: "user-id"` }) }); const generateBackupCodesBodySchema = z.object({ password: z.string().meta({ description: "The users password." }) }); const backupCode2fa = (opts) => { const twoFactorTable = "twoFactor"; return { id: "backup_code", endpoints: { verifyBackupCode: createAuthEndpoint("/two-factor/verify-backup-code", { method: "POST", body: verifyBackupCodeBodySchema, metadata: { openapi: { description: "Verify a backup code for two-factor authentication", responses: { "200": { description: "Backup code verified successfully", content: { "application/json": { schema: { type: "object", properties: { user: { type: "object", properties: { id: { type: "string", description: "Unique identifier of the user" }, email: { type: "string", format: "email", nullable: true, description: "User's email address" }, emailVerified: { type: "boolean", nullable: true, description: "Whether the email is verified" }, name: { type: "string", nullable: true, description: "User's name" }, image: { type: "string", format: "uri", nullable: true, description: "User's profile image URL" }, twoFactorEnabled: { type: "boolean", description: "Whether two-factor authentication is enabled for the user" }, createdAt: { type: "string", format: "date-time", description: "Timestamp when the user was created" }, updatedAt: { type: "string", format: "date-time", description: "Timestamp when the user was last updated" } }, required: [ "id", "twoFactorEnabled", "createdAt", "updatedAt" ], description: "The authenticated user object with two-factor details" }, session: { type: "object", properties: { token: { type: "string", description: "Session token" }, userId: { type: "string", description: "ID of the user associated with the session" }, createdAt: { type: "string", format: "date-time", description: "Timestamp when the session was created" }, expiresAt: { type: "string", format: "date-time", description: "Timestamp when the session expires" } }, required: [ "token", "userId", "createdAt", "expiresAt" ], description: "The current session object, included unless disableSession is true" } }, required: ["user", "session"] } } } } } } } }, async (ctx) => { const { session, valid } = await verifyTwoFactor(ctx); const user = session.user; const twoFactor = await ctx.context.adapter.findOne({ model: twoFactorTable, where: [{ field: "userId", value: user.id }] }); if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED); const validate = await verifyBackupCode({ backupCodes: twoFactor.backupCodes, code: ctx.body.code }, ctx.context.secretConfig, opts); if (!validate.status) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE); const updatedBackupCodes = await symmetricEncrypt({ key: ctx.context.secretConfig, data: JSON.stringify(validate.updated) }); if (!await ctx.context.adapter.update({ model: twoFactorTable, update: { backupCodes: updatedBackupCodes }, where: [{ field: "id", value: twoFactor.id }, { field: "backupCodes", value: twoFactor.backupCodes }] })) throw APIError.fromStatus("CONFLICT", { message: "Failed to verify backup code. Please try again." }); if (!ctx.body.disableSession) return valid(ctx); return ctx.json({ token: session.session?.token, user: parseUserOutput(ctx.context.options, session.user) }); }), generateBackupCodes: createAuthEndpoint("/two-factor/generate-backup-codes", { method: "POST", body: generateBackupCodesBodySchema, use: [sessionMiddleware], metadata: { openapi: { description: "Generate new backup codes for two-factor authentication", responses: { "200": { description: "Backup codes generated successfully", content: { "application/json": { schema: { type: "object", properties: { status: { type: "boolean", description: "Indicates if the backup codes were generated successfully", enum: [true] }, backupCodes: { type: "array", items: { type: "string" }, description: "Array of generated backup codes in plain text" } }, required: ["status", "backupCodes"] } } } } } } } }, async (ctx) => { const user = ctx.context.session.user; if (!user.twoFactorEnabled) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TWO_FACTOR_NOT_ENABLED); await ctx.context.password.checkPassword(user.id, ctx); const twoFactor = await ctx.context.adapter.findOne({ model: twoFactorTable, where: [{ field: "userId", value: user.id }] }); if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TWO_FACTOR_NOT_ENABLED); const backupCodes = await generateBackupCodes(ctx.context.secretConfig, opts); await ctx.context.adapter.update({ model: twoFactorTable, update: { backupCodes: backupCodes.encryptedBackupCodes }, where: [{ field: "id", value: twoFactor.id }] }); return ctx.json({ status: true, backupCodes: backupCodes.backupCodes }); }), viewBackupCodes: createAuthEndpoint({ method: "POST", body: viewBackupCodesBodySchema }, async (ctx) => { const twoFactor = await ctx.context.adapter.findOne({ model: twoFactorTable, where: [{ field: "userId", value: ctx.body.userId }] }); if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED); const decryptedBackupCodes = await getBackupCodes(twoFactor.backupCodes, ctx.context.secretConfig, opts); if (!decryptedBackupCodes) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE); return ctx.json({ status: true, backupCodes: decryptedBackupCodes }); }) } }; }; //#endregion export { backupCode2fa, generateBackupCodes }; //# sourceMappingURL=index.mjs.map
Save
cmd:
run