better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 12.6 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/plugins/two-factor/totp/index.ts"],"sourcesContent":["import { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { BASE_ERROR_CODES } from \"@better-auth/core/error\";\nimport { createOTP } from \"@better-auth/utils/otp\";\nimport { APIError } from \"better-call\";\nimport * as z from \"zod\";\nimport { sessionMiddleware } from \"../../../api\";\nimport { setSessionCookie } from \"../../../cookies\";\nimport { symmetricDecrypt } from \"../../../crypto\";\nimport type { BackupCodeOptions } from \"../backup-codes\";\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 type TOTPOptions = {\n\t/**\n\t * Issuer\n\t */\n\tissuer?: string | undefined;\n\t/**\n\t * How many digits the otp to be\n\t *\n\t * @default 6\n\t */\n\tdigits?: (6 | 8) | undefined;\n\t/**\n\t * Period for otp in seconds.\n\t * @default 30\n\t */\n\tperiod?: number | undefined;\n\t/**\n\t * Backup codes configuration\n\t */\n\tbackupCodes?: BackupCodeOptions | undefined;\n\t/**\n\t * Disable totp\n\t */\n\tdisable?: boolean | undefined;\n};\n\nconst generateTOTPBodySchema = z.object({\n\tsecret: z.string().meta({\n\t\tdescription: \"The secret to generate the TOTP code\",\n\t}),\n});\n\nconst getTOTPURIBodySchema = z.object({\n\tpassword: z.string().meta({\n\t\tdescription: \"User password\",\n\t}),\n});\n\nconst verifyTOTPBodySchema = 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\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\nexport const totp2fa = (options?: TOTPOptions | undefined) => {\n\tconst opts = {\n\t\t...options,\n\t\tdigits: options?.digits || 6,\n\t\tperiod: options?.period || 30,\n\t};\n\n\tconst twoFactorTable = \"twoFactor\";\n\n\tconst generateTOTP = createAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: generateTOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tsummary: \"Generate TOTP code\",\n\t\t\t\t\tdescription: \"Use this endpoint to generate a TOTP code\",\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\tcode: {\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},\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?.disable) {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"totp isn't configured. please pass totp option on two factor plugin to enable totp\",\n\t\t\t\t);\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"totp isn't configured\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst code = await createOTP(ctx.body.secret, {\n\t\t\t\tperiod: opts.period,\n\t\t\t\tdigits: opts.digits,\n\t\t\t}).totp();\n\t\t\treturn { code };\n\t\t},\n\t);\n\n\tconst getTOTPURI = createAuthEndpoint(\n\t\t\"/two-factor/get-totp-uri\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tuse: [sessionMiddleware],\n\t\t\tbody: getTOTPURIBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tsummary: \"Get TOTP URI\",\n\t\t\t\t\tdescription: \"Use this endpoint to get the TOTP URI\",\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\ttotpURI: {\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},\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?.disable) {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"totp isn't configured. please pass totp option on two factor plugin to enable totp\",\n\t\t\t\t);\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"totp isn't configured\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst user = ctx.context.session.user as UserWithTwoFactor;\n\t\t\tconst twoFactor = await ctx.context.adapter.findOne<TwoFactorTable>({\n\t\t\t\tmodel: twoFactorTable,\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\tvalue: user.id,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\tif (!twoFactor) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst secret = await symmetricDecrypt({\n\t\t\t\tkey: ctx.context.secret,\n\t\t\t\tdata: twoFactor.secret,\n\t\t\t});\n\t\t\tawait ctx.context.password.checkPassword(user.id, ctx);\n\t\t\tconst totpURI = createOTP(secret, {\n\t\t\t\tdigits: opts.digits,\n\t\t\t\tperiod: opts.period,\n\t\t\t}).url(options?.issuer || ctx.context.appName, user.email);\n\t\t\treturn {\n\t\t\t\ttotpURI,\n\t\t\t};\n\t\t},\n\t);\n\n\tconst verifyTOTP = createAuthEndpoint(\n\t\t\"/two-factor/verify-totp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyTOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tsummary: \"Verify two factor TOTP\",\n\t\t\t\t\tdescription: \"Verify two factor TOTP\",\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?.disable) {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"totp isn't configured. please pass totp option on two factor plugin to enable totp\",\n\t\t\t\t);\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"totp isn't configured\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst { session, valid, invalid } = await verifyTwoFactor(ctx);\n\t\t\tconst user = session.user as UserWithTwoFactor;\n\t\t\tconst twoFactor = await ctx.context.adapter.findOne<TwoFactorTable>({\n\t\t\t\tmodel: twoFactorTable,\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\tvalue: user.id,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tif (!twoFactor) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst decrypted = await symmetricDecrypt({\n\t\t\t\tkey: ctx.context.secret,\n\t\t\t\tdata: twoFactor.secret,\n\t\t\t});\n\t\t\tconst status = await createOTP(decrypted, {\n\t\t\t\tperiod: opts.period,\n\t\t\t\tdigits: opts.digits,\n\t\t\t}).verify(ctx.body.code);\n\t\t\tif (!status) {\n\t\t\t\treturn invalid(\"INVALID_CODE\");\n\t\t\t}\n\n\t\t\tif (!user.twoFactorEnabled) {\n\t\t\t\tif (!session.session) {\n\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\tmessage: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst updatedUser = await ctx.context.internalAdapter.updateUser(\n\t\t\t\t\tuser.id,\n\t\t\t\t\t{\n\t\t\t\t\t\ttwoFactorEnabled: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tconst newSession = await ctx.context.internalAdapter\n\t\t\t\t\t.createSession(user.id, false, session.session)\n\t\t\t\t\t.catch((e) => {\n\t\t\t\t\t\tthrow e;\n\t\t\t\t\t});\n\n\t\t\t\tawait ctx.context.internalAdapter.deleteSession(session.session.token);\n\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\tsession: newSession,\n\t\t\t\t\tuser: updatedUser,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn valid(ctx);\n\t\t},\n\t);\n\n\treturn {\n\t\tid: \"totp\",\n\t\tendpoints: {\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/totp/generate`\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.generateTOTP`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#totp)\n\t\t\t */\n\t\t\tgenerateTOTP: generateTOTP,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/get-totp-uri`\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.getTOTPURI`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.twoFactor.getTotpUri`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#getting-totp-uri)\n\t\t\t */\n\t\t\tgetTOTPURI: getTOTPURI,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/two-factor/verify-totp`\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.verifyTOTP`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.twoFactor.verifyTotp`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#verifying-totp)\n\t\t\t */\n\t\t\tverifyTOTP,\n\t\t},\n\t} satisfies TwoFactorProvider;\n};\n"],"mappings":";;;;;;;;;;;;;AA2CA,MAAM,yBAAyB,EAAE,OAAO,EACvC,QAAQ,EAAE,QAAQ,CAAC,KAAK,EACvB,aAAa,wCACb,CAAC,EACF,CAAC;AAEF,MAAM,uBAAuB,EAAE,OAAO,EACrC,UAAU,EAAE,QAAQ,CAAC,KAAK,EACzB,aAAa,iBACb,CAAC,EACF,CAAC;AAEF,MAAM,uBAAuB,EAAE,OAAO;CACrC,MAAM,EAAE,QAAQ,CAAC,KAAK,EACrB,aAAa,0CACb,CAAC;CAMF,aAAa,EACX,SAAS,CACT,KAAK,EACL,aACC,2HACD,CAAC,CACD,UAAU;CACZ,CAAC;AAEF,MAAa,WAAW,YAAsC;CAC7D,MAAM,OAAO;EACZ,GAAG;EACH,QAAQ,SAAS,UAAU;EAC3B,QAAQ,SAAS,UAAU;EAC3B;CAED,MAAM,iBAAiB;AAmNvB,QAAO;EACN,IAAI;EACJ,WAAW;GAaV,cAhOmB,mBACpB;IACC,QAAQ;IACR,MAAM;IACN,UAAU,EACT,SAAS;KACR,SAAS;KACT,aAAa;KACb,WAAW,EACV,KAAK;MACJ,aAAa;MACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;OACP,MAAM;OACN,YAAY,EACX,MAAM,EACL,MAAM,UACN,EACD;OACD,EACD,EACD;MACD,EACD;KACD,EACD;IACD,EACD,OAAO,QAAQ;AACd,QAAI,SAAS,SAAS;AACrB,SAAI,QAAQ,OAAO,MAClB,qFACA;AACD,WAAM,IAAI,SAAS,eAAe,EACjC,SAAS,yBACT,CAAC;;AAMH,WAAO,EAAE,MAJI,MAAM,UAAU,IAAI,KAAK,QAAQ;KAC7C,QAAQ,KAAK;KACb,QAAQ,KAAK;KACb,CAAC,CAAC,MAAM,EACM;KAEhB;GAqMC,YAnMiB,mBAClB,4BACA;IACC,QAAQ;IACR,KAAK,CAAC,kBAAkB;IACxB,MAAM;IACN,UAAU,EACT,SAAS;KACR,SAAS;KACT,aAAa;KACb,WAAW,EACV,KAAK;MACJ,aAAa;MACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;OACP,MAAM;OACN,YAAY,EACX,SAAS,EACR,MAAM,UACN,EACD;OACD,EACD,EACD;MACD,EACD;KACD,EACD;IACD,EACD,OAAO,QAAQ;AACd,QAAI,SAAS,SAAS;AACrB,SAAI,QAAQ,OAAO,MAClB,qFACA;AACD,WAAM,IAAI,SAAS,eAAe,EACjC,SAAS,yBACT,CAAC;;IAEH,MAAM,OAAO,IAAI,QAAQ,QAAQ;IACjC,MAAM,YAAY,MAAM,IAAI,QAAQ,QAAQ,QAAwB;KACnE,OAAO;KACP,OAAO,CACN;MACC,OAAO;MACP,OAAO,KAAK;MACZ,CACD;KACD,CAAC;AACF,QAAI,CAAC,UACJ,OAAM,IAAI,SAAS,eAAe,EACjC,SAAS,uBAAuB,kBAChC,CAAC;IAEH,MAAM,SAAS,MAAM,iBAAiB;KACrC,KAAK,IAAI,QAAQ;KACjB,MAAM,UAAU;KAChB,CAAC;AACF,UAAM,IAAI,QAAQ,SAAS,cAAc,KAAK,IAAI,IAAI;AAKtD,WAAO,EACN,SALe,UAAU,QAAQ;KACjC,QAAQ,KAAK;KACb,QAAQ,KAAK;KACb,CAAC,CAAC,IAAI,SAAS,UAAU,IAAI,QAAQ,SAAS,KAAK,MAAM,EAGzD;KAEF;GAgJC,YA9IiB,mBAClB,2BACA;IACC,QAAQ;IACR,MAAM;IACN,UAAU,EACT,SAAS;KACR,SAAS;KACT,aAAa;KACb,WAAW,EACV,KAAK;MACJ,aAAa;MACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;OACP,MAAM;OACN,YAAY,EACX,QAAQ,EACP,MAAM,WACN,EACD;OACD,EACD,EACD;MACD,EACD;KACD,EACD;IACD,EACD,OAAO,QAAQ;AACd,QAAI,SAAS,SAAS;AACrB,SAAI,QAAQ,OAAO,MAClB,qFACA;AACD,WAAM,IAAI,SAAS,eAAe,EACjC,SAAS,yBACT,CAAC;;IAEH,MAAM,EAAE,SAAS,OAAO,YAAY,MAAM,gBAAgB,IAAI;IAC9D,MAAM,OAAO,QAAQ;IACrB,MAAM,YAAY,MAAM,IAAI,QAAQ,QAAQ,QAAwB;KACnE,OAAO;KACP,OAAO,CACN;MACC,OAAO;MACP,OAAO,KAAK;MACZ,CACD;KACD,CAAC;AAEF,QAAI,CAAC,UACJ,OAAM,IAAI,SAAS,eAAe,EACjC,SAAS,uBAAuB,kBAChC,CAAC;AAUH,QAAI,CAJW,MAAM,UAJH,MAAM,iBAAiB;KACxC,KAAK,IAAI,QAAQ;KACjB,MAAM,UAAU;KAChB,CAAC,EACwC;KACzC,QAAQ,KAAK;KACb,QAAQ,KAAK;KACb,CAAC,CAAC,OAAO,IAAI,KAAK,KAAK,CAEvB,QAAO,QAAQ,eAAe;AAG/B,QAAI,CAAC,KAAK,kBAAkB;AAC3B,SAAI,CAAC,QAAQ,QACZ,OAAM,IAAI,SAAS,eAAe,EACjC,SAAS,iBAAiB,0BAC1B,CAAC;KAEH,MAAM,cAAc,MAAM,IAAI,QAAQ,gBAAgB,WACrD,KAAK,IACL,EACC,kBAAkB,MAClB,CACD;KACD,MAAM,aAAa,MAAM,IAAI,QAAQ,gBACnC,cAAc,KAAK,IAAI,OAAO,QAAQ,QAAQ,CAC9C,OAAO,MAAM;AACb,YAAM;OACL;AAEH,WAAM,IAAI,QAAQ,gBAAgB,cAAc,QAAQ,QAAQ,MAAM;AACtE,WAAM,iBAAiB,KAAK;MAC3B,SAAS;MACT,MAAM;MACN,CAAC;;AAEH,WAAO,MAAM,IAAI;KAElB;GAkDC;EACD"}