better-auth 1.2.6-beta.7 → 1.2.7-beta.1
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/adapters/drizzle-adapter/index.cjs +186 -249
- package/dist/adapters/drizzle-adapter/index.d.cts +11 -49
- package/dist/adapters/drizzle-adapter/index.d.mts +11 -49
- package/dist/adapters/drizzle-adapter/index.d.ts +11 -49
- package/dist/adapters/drizzle-adapter/index.mjs +186 -249
- package/dist/adapters/index.cjs +26 -0
- package/dist/adapters/index.d.cts +17 -0
- package/dist/adapters/index.d.mts +17 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.mjs +20 -0
- package/dist/adapters/kysely-adapter/index.cjs +7 -7
- package/dist/adapters/kysely-adapter/index.d.cts +17 -49
- package/dist/adapters/kysely-adapter/index.d.mts +17 -49
- package/dist/adapters/kysely-adapter/index.d.ts +17 -49
- package/dist/adapters/kysely-adapter/index.mjs +8 -8
- package/dist/adapters/memory-adapter/index.cjs +7 -8
- package/dist/adapters/memory-adapter/index.d.cts +9 -49
- package/dist/adapters/memory-adapter/index.d.mts +9 -49
- package/dist/adapters/memory-adapter/index.d.ts +9 -49
- package/dist/adapters/memory-adapter/index.mjs +8 -9
- package/dist/adapters/mongodb-adapter/index.cjs +2 -2
- package/dist/adapters/mongodb-adapter/index.d.cts +4 -4
- package/dist/adapters/mongodb-adapter/index.d.mts +4 -4
- package/dist/adapters/mongodb-adapter/index.d.ts +4 -4
- package/dist/adapters/mongodb-adapter/index.mjs +3 -3
- package/dist/adapters/prisma-adapter/index.cjs +130 -203
- package/dist/adapters/prisma-adapter/index.d.cts +17 -49
- package/dist/adapters/prisma-adapter/index.d.mts +17 -49
- package/dist/adapters/prisma-adapter/index.d.ts +17 -49
- package/dist/adapters/prisma-adapter/index.mjs +131 -204
- package/dist/adapters/test.cjs +710 -377
- package/dist/adapters/test.d.cts +64 -5
- package/dist/adapters/test.d.mts +64 -5
- package/dist/adapters/test.d.ts +64 -5
- package/dist/adapters/test.mjs +712 -380
- package/dist/api/index.cjs +61 -25
- package/dist/api/index.d.cts +3 -3
- package/dist/api/index.d.mts +3 -3
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.mjs +63 -27
- package/dist/client/index.d.cts +3 -3
- package/dist/client/index.d.mts +3 -3
- package/dist/client/index.d.ts +3 -3
- package/dist/client/plugins/index.cjs +13 -15
- package/dist/client/plugins/index.d.cts +80 -19
- package/dist/client/plugins/index.d.mts +80 -19
- package/dist/client/plugins/index.d.ts +80 -19
- package/dist/client/plugins/index.mjs +13 -16
- package/dist/client/react/index.cjs +4 -4
- package/dist/client/react/index.d.cts +3 -3
- package/dist/client/react/index.d.mts +3 -3
- package/dist/client/react/index.d.ts +3 -3
- package/dist/client/solid/index.d.cts +3 -3
- package/dist/client/solid/index.d.mts +3 -3
- package/dist/client/solid/index.d.ts +3 -3
- package/dist/client/svelte/index.d.cts +3 -3
- package/dist/client/svelte/index.d.mts +3 -3
- package/dist/client/svelte/index.d.ts +3 -3
- package/dist/client/vue/index.d.cts +3 -3
- package/dist/client/vue/index.d.mts +3 -3
- package/dist/client/vue/index.d.ts +3 -3
- package/dist/cookies/index.cjs +13 -2
- package/dist/cookies/index.d.cts +3 -3
- package/dist/cookies/index.d.mts +3 -3
- package/dist/cookies/index.d.ts +3 -3
- package/dist/cookies/index.mjs +13 -2
- package/dist/db/index.cjs +6 -5
- package/dist/db/index.d.cts +4 -4
- package/dist/db/index.d.mts +4 -4
- package/dist/db/index.d.ts +4 -4
- package/dist/db/index.mjs +7 -6
- package/dist/index.cjs +11 -7
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +14 -10
- package/dist/integrations/next-js.cjs +4 -5
- package/dist/integrations/next-js.d.cts +3 -3
- package/dist/integrations/next-js.d.mts +3 -3
- package/dist/integrations/next-js.d.ts +3 -3
- package/dist/integrations/next-js.mjs +5 -6
- package/dist/integrations/node.d.cts +3 -3
- package/dist/integrations/node.d.mts +3 -3
- package/dist/integrations/node.d.ts +3 -3
- package/dist/integrations/react-start.cjs +5 -6
- package/dist/integrations/react-start.d.cts +3 -3
- package/dist/integrations/react-start.d.mts +3 -3
- package/dist/integrations/react-start.d.ts +3 -3
- package/dist/integrations/react-start.mjs +6 -7
- package/dist/integrations/svelte-kit.d.cts +3 -3
- package/dist/integrations/svelte-kit.d.mts +3 -3
- package/dist/integrations/svelte-kit.d.ts +3 -3
- package/dist/oauth2/index.d.cts +5 -5
- package/dist/oauth2/index.d.mts +5 -5
- package/dist/oauth2/index.d.ts +5 -5
- package/dist/plugins/access/index.d.cts +1 -1
- package/dist/plugins/access/index.d.mts +1 -1
- package/dist/plugins/access/index.d.ts +1 -1
- package/dist/plugins/admin/access/index.d.cts +1 -1
- package/dist/plugins/admin/access/index.d.mts +1 -1
- package/dist/plugins/admin/access/index.d.ts +1 -1
- package/dist/plugins/admin/index.cjs +4 -4
- package/dist/plugins/admin/index.d.cts +74 -14
- package/dist/plugins/admin/index.d.mts +74 -14
- package/dist/plugins/admin/index.d.ts +74 -14
- package/dist/plugins/admin/index.mjs +5 -5
- package/dist/plugins/anonymous/index.cjs +4 -5
- package/dist/plugins/anonymous/index.d.cts +3 -3
- package/dist/plugins/anonymous/index.d.mts +3 -3
- package/dist/plugins/anonymous/index.d.ts +3 -3
- package/dist/plugins/anonymous/index.mjs +5 -6
- package/dist/plugins/bearer/index.cjs +2 -2
- package/dist/plugins/bearer/index.d.cts +3 -3
- package/dist/plugins/bearer/index.d.mts +3 -3
- package/dist/plugins/bearer/index.d.ts +3 -3
- package/dist/plugins/bearer/index.mjs +3 -3
- package/dist/plugins/captcha/index.cjs +110 -45
- package/dist/plugins/captcha/index.d.cts +26 -6
- package/dist/plugins/captcha/index.d.mts +26 -6
- package/dist/plugins/captcha/index.d.ts +26 -6
- package/dist/plugins/captcha/index.mjs +110 -45
- package/dist/plugins/custom-session/index.cjs +24 -5
- package/dist/plugins/custom-session/index.d.cts +25 -6
- package/dist/plugins/custom-session/index.d.mts +25 -6
- package/dist/plugins/custom-session/index.d.ts +25 -6
- package/dist/plugins/custom-session/index.mjs +25 -6
- package/dist/plugins/email-otp/index.cjs +96 -30
- package/dist/plugins/email-otp/index.d.cts +33 -10
- package/dist/plugins/email-otp/index.d.mts +33 -10
- package/dist/plugins/email-otp/index.d.ts +33 -10
- package/dist/plugins/email-otp/index.mjs +97 -31
- package/dist/plugins/generic-oauth/index.cjs +81 -20
- package/dist/plugins/generic-oauth/index.d.cts +46 -3
- package/dist/plugins/generic-oauth/index.d.mts +46 -3
- package/dist/plugins/generic-oauth/index.d.ts +46 -3
- package/dist/plugins/generic-oauth/index.mjs +82 -21
- package/dist/plugins/haveibeenpwned/index.cjs +98 -0
- package/dist/plugins/haveibeenpwned/index.d.cts +36 -0
- package/dist/plugins/haveibeenpwned/index.d.mts +36 -0
- package/dist/plugins/haveibeenpwned/index.d.ts +36 -0
- package/dist/plugins/haveibeenpwned/index.mjs +96 -0
- package/dist/plugins/index.cjs +583 -19
- package/dist/plugins/index.d.cts +7 -5
- package/dist/plugins/index.d.mts +7 -5
- package/dist/plugins/index.d.ts +7 -5
- package/dist/plugins/index.mjs +583 -21
- package/dist/plugins/jwt/index.cjs +45 -21
- package/dist/plugins/jwt/index.d.cts +52 -6
- package/dist/plugins/jwt/index.d.mts +52 -6
- package/dist/plugins/jwt/index.d.ts +52 -6
- package/dist/plugins/jwt/index.mjs +46 -22
- package/dist/plugins/magic-link/index.cjs +3 -3
- package/dist/plugins/magic-link/index.mjs +4 -4
- package/dist/plugins/multi-session/index.cjs +3 -3
- package/dist/plugins/multi-session/index.d.cts +3 -3
- package/dist/plugins/multi-session/index.d.mts +3 -3
- package/dist/plugins/multi-session/index.d.ts +3 -3
- package/dist/plugins/multi-session/index.mjs +4 -4
- package/dist/plugins/oauth-proxy/index.cjs +4 -4
- package/dist/plugins/oauth-proxy/index.d.cts +3 -3
- package/dist/plugins/oauth-proxy/index.d.mts +3 -3
- package/dist/plugins/oauth-proxy/index.d.ts +3 -3
- package/dist/plugins/oauth-proxy/index.mjs +5 -5
- package/dist/plugins/oidc-provider/index.cjs +227 -8
- package/dist/plugins/oidc-provider/index.d.cts +215 -3
- package/dist/plugins/oidc-provider/index.d.mts +215 -3
- package/dist/plugins/oidc-provider/index.d.ts +215 -3
- package/dist/plugins/oidc-provider/index.mjs +228 -9
- package/dist/plugins/one-tap/index.cjs +5 -5
- package/dist/plugins/one-tap/index.mjs +6 -6
- package/dist/plugins/one-time-token/index.cjs +119 -0
- package/dist/plugins/one-time-token/index.d.cts +134 -0
- package/dist/plugins/one-time-token/index.d.mts +134 -0
- package/dist/plugins/one-time-token/index.d.ts +134 -0
- package/dist/plugins/one-time-token/index.mjs +117 -0
- package/dist/plugins/open-api/index.cjs +3 -3
- package/dist/plugins/open-api/index.d.cts +3 -3
- package/dist/plugins/open-api/index.d.mts +3 -3
- package/dist/plugins/open-api/index.d.ts +3 -3
- package/dist/plugins/open-api/index.mjs +4 -4
- package/dist/plugins/organization/access/index.d.cts +1 -1
- package/dist/plugins/organization/access/index.d.mts +1 -1
- package/dist/plugins/organization/access/index.d.ts +1 -1
- package/dist/plugins/organization/index.cjs +4 -4
- package/dist/plugins/organization/index.d.cts +708 -55
- package/dist/plugins/organization/index.d.mts +708 -55
- package/dist/plugins/organization/index.d.ts +708 -55
- package/dist/plugins/organization/index.mjs +5 -5
- package/dist/plugins/passkey/index.cjs +82 -8
- package/dist/plugins/passkey/index.d.cts +72 -3
- package/dist/plugins/passkey/index.d.mts +72 -3
- package/dist/plugins/passkey/index.d.ts +72 -3
- package/dist/plugins/passkey/index.mjs +83 -9
- package/dist/plugins/phone-number/index.cjs +194 -26
- package/dist/plugins/phone-number/index.d.cts +132 -8
- package/dist/plugins/phone-number/index.d.mts +132 -8
- package/dist/plugins/phone-number/index.d.ts +132 -8
- package/dist/plugins/phone-number/index.mjs +195 -27
- package/dist/plugins/sso/index.cjs +190 -7
- package/dist/plugins/sso/index.d.cts +181 -15
- package/dist/plugins/sso/index.d.mts +181 -15
- package/dist/plugins/sso/index.d.ts +181 -15
- package/dist/plugins/sso/index.mjs +191 -8
- package/dist/plugins/two-factor/index.cjs +443 -92
- package/dist/plugins/two-factor/index.d.cts +230 -396
- package/dist/plugins/two-factor/index.d.mts +230 -396
- package/dist/plugins/two-factor/index.d.ts +230 -396
- package/dist/plugins/two-factor/index.mjs +431 -80
- package/dist/plugins/username/index.cjs +34 -31
- package/dist/plugins/username/index.d.cts +15 -12
- package/dist/plugins/username/index.d.mts +15 -12
- package/dist/plugins/username/index.d.ts +15 -12
- package/dist/plugins/username/index.mjs +35 -32
- package/dist/shared/better-auth.1DR6suCQ.mjs +307 -0
- package/dist/shared/{better-auth.BSsp73pg.cjs → better-auth.B7cZ2juS.cjs} +15 -14
- package/dist/shared/{better-auth.bKwabe3I.d.mts → better-auth.B88xucNq.d.mts} +529 -39
- package/dist/shared/{better-auth.CApEjVDP.cjs → better-auth.BW8BpneG.cjs} +4 -1
- package/dist/shared/{better-auth.BiQsvaIP.d.cts → better-auth.BcU1Kjyq.d.cts} +2051 -518
- package/dist/shared/better-auth.BfG24BjZ.cjs +118 -0
- package/dist/shared/{better-auth.A3TjrU8G.mjs → better-auth.Bk5IMdhM.mjs} +32 -12
- package/dist/shared/{better-auth.D9VnBkRI.mjs → better-auth.Bm9HxIzE.mjs} +47 -24
- package/dist/shared/{better-auth.BRf6Iynu.d.ts → better-auth.Bwc-6kOr.d.ts} +1 -1
- package/dist/shared/{better-auth.D-oLmHIj.d.mts → better-auth.CA2hFK4N.d.ts} +2051 -518
- package/dist/shared/{better-auth.Dmhe30iW.d.mts → better-auth.CGukGrxT.d.cts} +1 -1
- package/dist/shared/{better-auth.CsSpq0zL.cjs → better-auth.CHUzBidy.cjs} +46 -23
- package/dist/shared/{better-auth.DWRligF8.d.cts → better-auth.CT9J6rD-.d.cts} +539 -7
- package/dist/shared/better-auth.CVCo5Z2T.cjs +310 -0
- package/dist/shared/{better-auth.D4jH-sJA.mjs → better-auth.CWwVo_61.mjs} +458 -118
- package/dist/shared/{better-auth.Bi8FQwDD.d.cts → better-auth.CYegVoq1.d.cts} +1 -1
- package/dist/shared/{better-auth.Bi8FQwDD.d.mts → better-auth.CYegVoq1.d.mts} +1 -1
- package/dist/shared/{better-auth.Bi8FQwDD.d.ts → better-auth.CYegVoq1.d.ts} +1 -1
- package/dist/shared/{better-auth.CepcSj5H.mjs → better-auth.Cc72UxUH.mjs} +1 -2
- package/dist/shared/{better-auth.BWp5dztg.d.ts → better-auth.CmN4mlPh.d.ts} +539 -7
- package/dist/shared/{better-auth.DH3YjMQH.mjs → better-auth.Cqykj82J.mjs} +1 -1
- package/dist/shared/{better-auth.wcdMj2cT.d.mts → better-auth.DIt2e3lu.d.mts} +539 -7
- package/dist/shared/{better-auth.BANAxdkL.d.ts → better-auth.DNTAFSt1.d.ts} +529 -39
- package/dist/shared/{better-auth.DU2QNVc_.d.ts → better-auth.DQ7OSJbI.d.mts} +2051 -518
- package/dist/shared/{better-auth.DLTzKoOS.cjs → better-auth.DSVbLSt7.cjs} +4 -1
- package/dist/shared/{better-auth.B2Fw1vhH.d.cts → better-auth.DTiSPWEk.d.cts} +529 -39
- package/dist/shared/better-auth.DURsStt9.mjs +116 -0
- package/dist/shared/{better-auth.BIjcZ_vt.cjs → better-auth.DYoLD99C.cjs} +31 -11
- package/dist/shared/{better-auth.CV1L7TPV.cjs → better-auth.D_ZIX1O8.cjs} +317 -47
- package/dist/shared/{better-auth.C5H9XEzZ.cjs → better-auth.DcWKCjjf.cjs} +1 -2
- package/dist/shared/{better-auth.BDYXUcLv.cjs → better-auth.Dg0siV5C.cjs} +457 -117
- package/dist/shared/better-auth.DjryM8pE.cjs +760 -0
- package/dist/shared/{better-auth.DPBQN9Fs.mjs → better-auth.Dn_Ms1Uf.mjs} +318 -48
- package/dist/shared/{better-auth.DiG4KL2x.mjs → better-auth.OuYYTHC7.mjs} +4 -1
- package/dist/shared/{better-auth.DtC8i3pf.d.cts → better-auth.S1jimRbX.d.mts} +1 -1
- package/dist/shared/better-auth.SPmq4a4z.d.mts +344 -0
- package/dist/shared/{better-auth.cOCrlspr.mjs → better-auth.bkwPl2G4.mjs} +4 -1
- package/dist/shared/better-auth.cp2rC2iM.d.ts +344 -0
- package/dist/shared/better-auth.eVy4DZvP.d.cts +344 -0
- package/dist/shared/{better-auth.BrOpzmqo.mjs → better-auth.iKoUsdFE.mjs} +15 -14
- package/dist/shared/better-auth.rSYJCd3o.mjs +758 -0
- package/dist/social-providers/index.cjs +75 -3
- package/dist/social-providers/index.d.cts +2 -2
- package/dist/social-providers/index.d.mts +2 -2
- package/dist/social-providers/index.d.ts +2 -2
- package/dist/social-providers/index.mjs +77 -6
- package/dist/types/index.d.cts +4 -4
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.d.ts +4 -4
- package/package.json +42 -5
- package/dist/chunks/server.cjs +0 -905
- package/dist/chunks/server.mjs +0 -895
- package/dist/shared/better-auth.BcoSd9tC.mjs +0 -10
- package/dist/shared/better-auth.BnRFp-t0.mjs +0 -405
- package/dist/shared/better-auth.C1-vpKly.cjs +0 -12
- package/dist/shared/better-auth.ClTSOgiD.mjs +0 -140
- package/dist/shared/better-auth.DC8JQbiE.mjs +0 -173
- package/dist/shared/better-auth.DWHWPllD.cjs +0 -175
- package/dist/shared/better-auth.DqLjzBlO.cjs +0 -408
- package/dist/shared/better-auth.m575EIBC.cjs +0 -144
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { g as generateRandomString } from '../../shared/better-auth.B4Qoxdgc.mjs';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { a as createAuthEndpoint, s as sessionMiddleware,
|
|
3
|
+
import { g as getSessionFromCtx, a as createAuthEndpoint, s as sessionMiddleware, B as BASE_ERROR_CODES, c as createAuthMiddleware } from '../../shared/better-auth.CWwVo_61.mjs';
|
|
4
4
|
import { APIError } from 'better-call';
|
|
5
5
|
import { setSessionCookie, deleteSessionCookie } from '../../cookies/index.mjs';
|
|
6
|
-
import { m as mergeSchema } from '../../shared/better-auth.
|
|
6
|
+
import { m as mergeSchema } from '../../shared/better-auth.Cc72UxUH.mjs';
|
|
7
7
|
import '../../shared/better-auth.8zoxzg-F.mjs';
|
|
8
|
-
import '../../shared/better-auth.
|
|
8
|
+
import '../../shared/better-auth.Cqykj82J.mjs';
|
|
9
9
|
import 'defu';
|
|
10
10
|
import { symmetricEncrypt, symmetricDecrypt } from '../../crypto/index.mjs';
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
11
|
+
import '@better-auth/utils/base64';
|
|
12
|
+
import { createHMAC } from '@better-auth/utils/hmac';
|
|
13
13
|
import '@better-auth/utils/hash';
|
|
14
14
|
import '@noble/ciphers/chacha';
|
|
15
15
|
import '@noble/ciphers/utils';
|
|
16
16
|
import '@noble/ciphers/webcrypto';
|
|
17
|
-
import '@better-auth/utils/base64';
|
|
18
17
|
import 'jose';
|
|
19
18
|
import '@noble/hashes/scrypt';
|
|
20
19
|
import '@better-auth/utils';
|
|
@@ -22,7 +21,6 @@ import '@better-auth/utils/hex';
|
|
|
22
21
|
import '@noble/hashes/utils';
|
|
23
22
|
import { createOTP } from '@better-auth/utils/otp';
|
|
24
23
|
import { v as validatePassword } from '../../shared/better-auth.YwDQhoPc.mjs';
|
|
25
|
-
import { createHMAC } from '@better-auth/utils/hmac';
|
|
26
24
|
export { t as twoFactorClient } from '../../shared/better-auth.Ddw8bVyV.mjs';
|
|
27
25
|
import '@better-auth/utils/random';
|
|
28
26
|
import '../../social-providers/index.mjs';
|
|
@@ -31,14 +29,151 @@ import '../../shared/better-auth.DCVeP5-W.mjs';
|
|
|
31
29
|
import '../../shared/better-auth.CW6D9eSx.mjs';
|
|
32
30
|
import '../../shared/better-auth.DdzSJf-n.mjs';
|
|
33
31
|
import '../../shared/better-auth.BdyOG_p4.mjs';
|
|
32
|
+
import '../../shared/better-auth.tB5eU6EY.mjs';
|
|
34
33
|
import '../../shared/better-auth.BUPPRXfK.mjs';
|
|
35
34
|
import '../../shared/better-auth.DDEbWX-S.mjs';
|
|
36
35
|
import '../../shared/better-auth.VTXNLFMT.mjs';
|
|
37
36
|
import 'jose/errors';
|
|
38
|
-
import '../../shared/better-auth.tB5eU6EY.mjs';
|
|
39
37
|
import '@better-auth/utils/binary';
|
|
40
38
|
import '../../shared/better-auth.OT3XFeFk.mjs';
|
|
41
39
|
|
|
40
|
+
const TWO_FACTOR_ERROR_CODES = {
|
|
41
|
+
OTP_NOT_ENABLED: "OTP not enabled",
|
|
42
|
+
OTP_HAS_EXPIRED: "OTP has expired",
|
|
43
|
+
TOTP_NOT_ENABLED: "TOTP not enabled",
|
|
44
|
+
TWO_FACTOR_NOT_ENABLED: "Two factor isn't enabled",
|
|
45
|
+
BACKUP_CODES_NOT_ENABLED: "Backup codes aren't enabled",
|
|
46
|
+
INVALID_BACKUP_CODE: "Invalid backup code",
|
|
47
|
+
INVALID_CODE: "Invalid code",
|
|
48
|
+
TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE: "Too many attempts. Please request a new code.",
|
|
49
|
+
INVALID_TWO_FACTOR_COOKIE: "Invalid two factor cookie"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const TWO_FACTOR_COOKIE_NAME = "two_factor";
|
|
53
|
+
const TRUST_DEVICE_COOKIE_NAME = "trust_device";
|
|
54
|
+
|
|
55
|
+
async function verifyTwoFactor(ctx) {
|
|
56
|
+
const session = await getSessionFromCtx(ctx);
|
|
57
|
+
if (!session) {
|
|
58
|
+
const cookieName = ctx.context.createAuthCookie(TWO_FACTOR_COOKIE_NAME);
|
|
59
|
+
const twoFactorCookie = await ctx.getSignedCookie(
|
|
60
|
+
cookieName.name,
|
|
61
|
+
ctx.context.secret
|
|
62
|
+
);
|
|
63
|
+
if (!twoFactorCookie) {
|
|
64
|
+
throw new APIError("UNAUTHORIZED", {
|
|
65
|
+
message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const verificationToken = await ctx.context.internalAdapter.findVerificationValue(twoFactorCookie);
|
|
69
|
+
if (!verificationToken) {
|
|
70
|
+
throw new APIError("UNAUTHORIZED", {
|
|
71
|
+
message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const user = await ctx.context.internalAdapter.findUserById(
|
|
75
|
+
verificationToken.value
|
|
76
|
+
);
|
|
77
|
+
if (!user) {
|
|
78
|
+
throw new APIError("UNAUTHORIZED", {
|
|
79
|
+
message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const dontRememberMe = await ctx.getSignedCookie(
|
|
83
|
+
ctx.context.authCookies.dontRememberToken.name,
|
|
84
|
+
ctx.context.secret
|
|
85
|
+
);
|
|
86
|
+
return {
|
|
87
|
+
valid: async (ctx2) => {
|
|
88
|
+
const session2 = await ctx2.context.internalAdapter.createSession(
|
|
89
|
+
verificationToken.value,
|
|
90
|
+
ctx2.headers,
|
|
91
|
+
!!dontRememberMe
|
|
92
|
+
);
|
|
93
|
+
if (!session2) {
|
|
94
|
+
throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
95
|
+
message: "failed to create session"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
await setSessionCookie(ctx2, {
|
|
99
|
+
session: session2,
|
|
100
|
+
user
|
|
101
|
+
});
|
|
102
|
+
if (ctx2.body.trustDevice) {
|
|
103
|
+
const trustDeviceCookie = ctx2.context.createAuthCookie(
|
|
104
|
+
TRUST_DEVICE_COOKIE_NAME,
|
|
105
|
+
{
|
|
106
|
+
maxAge: 30 * 24 * 60 * 60
|
|
107
|
+
// 30 days, it'll be refreshed on sign in requests
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
const token = await createHMAC("SHA-256", "base64urlnopad").sign(
|
|
111
|
+
ctx2.context.secret,
|
|
112
|
+
`${user.id}!${session2.token}`
|
|
113
|
+
);
|
|
114
|
+
await ctx2.setSignedCookie(
|
|
115
|
+
trustDeviceCookie.name,
|
|
116
|
+
`${token}!${session2.token}`,
|
|
117
|
+
ctx2.context.secret,
|
|
118
|
+
trustDeviceCookie.attributes
|
|
119
|
+
);
|
|
120
|
+
ctx2.setCookie(ctx2.context.authCookies.dontRememberToken.name, "", {
|
|
121
|
+
maxAge: 0
|
|
122
|
+
});
|
|
123
|
+
ctx2.setCookie(cookieName.name, "", {
|
|
124
|
+
maxAge: 0
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return ctx2.json({
|
|
128
|
+
token: session2.token,
|
|
129
|
+
user: {
|
|
130
|
+
id: user.id,
|
|
131
|
+
email: user.email,
|
|
132
|
+
emailVerified: user.emailVerified,
|
|
133
|
+
name: user.name,
|
|
134
|
+
image: user.image,
|
|
135
|
+
createdAt: user.createdAt,
|
|
136
|
+
updatedAt: user.updatedAt
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
invalid: async (errorKey) => {
|
|
141
|
+
throw new APIError("UNAUTHORIZED", {
|
|
142
|
+
message: TWO_FACTOR_ERROR_CODES[errorKey]
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
session: {
|
|
146
|
+
session: null,
|
|
147
|
+
user
|
|
148
|
+
},
|
|
149
|
+
key: twoFactorCookie
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
valid: async (ctx2) => {
|
|
154
|
+
return ctx2.json({
|
|
155
|
+
token: session.session.token,
|
|
156
|
+
user: {
|
|
157
|
+
id: session.user.id,
|
|
158
|
+
email: session.user.email,
|
|
159
|
+
emailVerified: session.user.emailVerified,
|
|
160
|
+
name: session.user.name,
|
|
161
|
+
image: session.user.image,
|
|
162
|
+
createdAt: session.user.createdAt,
|
|
163
|
+
updatedAt: session.user.updatedAt
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
invalid: async () => {
|
|
168
|
+
throw new APIError("UNAUTHORIZED", {
|
|
169
|
+
message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
session,
|
|
173
|
+
key: `${session.user.id}!${session.session.id}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
42
177
|
function generateBackupCodesFn(options) {
|
|
43
178
|
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)}`);
|
|
44
179
|
}
|
|
@@ -106,10 +241,112 @@ const backupCode2fa = (options) => {
|
|
|
106
241
|
description: "If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time."
|
|
107
242
|
}).optional()
|
|
108
243
|
}),
|
|
109
|
-
|
|
244
|
+
metadata: {
|
|
245
|
+
openapi: {
|
|
246
|
+
description: "Verify a backup code for two-factor authentication",
|
|
247
|
+
responses: {
|
|
248
|
+
"200": {
|
|
249
|
+
description: "Backup code verified successfully",
|
|
250
|
+
content: {
|
|
251
|
+
"application/json": {
|
|
252
|
+
schema: {
|
|
253
|
+
type: "object",
|
|
254
|
+
properties: {
|
|
255
|
+
user: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
id: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "Unique identifier of the user"
|
|
261
|
+
},
|
|
262
|
+
email: {
|
|
263
|
+
type: "string",
|
|
264
|
+
format: "email",
|
|
265
|
+
nullable: true,
|
|
266
|
+
description: "User's email address"
|
|
267
|
+
},
|
|
268
|
+
emailVerified: {
|
|
269
|
+
type: "boolean",
|
|
270
|
+
nullable: true,
|
|
271
|
+
description: "Whether the email is verified"
|
|
272
|
+
},
|
|
273
|
+
name: {
|
|
274
|
+
type: "string",
|
|
275
|
+
nullable: true,
|
|
276
|
+
description: "User's name"
|
|
277
|
+
},
|
|
278
|
+
image: {
|
|
279
|
+
type: "string",
|
|
280
|
+
format: "uri",
|
|
281
|
+
nullable: true,
|
|
282
|
+
description: "User's profile image URL"
|
|
283
|
+
},
|
|
284
|
+
twoFactorEnabled: {
|
|
285
|
+
type: "boolean",
|
|
286
|
+
description: "Whether two-factor authentication is enabled for the user"
|
|
287
|
+
},
|
|
288
|
+
createdAt: {
|
|
289
|
+
type: "string",
|
|
290
|
+
format: "date-time",
|
|
291
|
+
description: "Timestamp when the user was created"
|
|
292
|
+
},
|
|
293
|
+
updatedAt: {
|
|
294
|
+
type: "string",
|
|
295
|
+
format: "date-time",
|
|
296
|
+
description: "Timestamp when the user was last updated"
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
required: [
|
|
300
|
+
"id",
|
|
301
|
+
"twoFactorEnabled",
|
|
302
|
+
"createdAt",
|
|
303
|
+
"updatedAt"
|
|
304
|
+
],
|
|
305
|
+
description: "The authenticated user object with two-factor details"
|
|
306
|
+
},
|
|
307
|
+
session: {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
token: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "Session token"
|
|
313
|
+
},
|
|
314
|
+
userId: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: "ID of the user associated with the session"
|
|
317
|
+
},
|
|
318
|
+
createdAt: {
|
|
319
|
+
type: "string",
|
|
320
|
+
format: "date-time",
|
|
321
|
+
description: "Timestamp when the session was created"
|
|
322
|
+
},
|
|
323
|
+
expiresAt: {
|
|
324
|
+
type: "string",
|
|
325
|
+
format: "date-time",
|
|
326
|
+
description: "Timestamp when the session expires"
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
required: [
|
|
330
|
+
"token",
|
|
331
|
+
"userId",
|
|
332
|
+
"createdAt",
|
|
333
|
+
"expiresAt"
|
|
334
|
+
],
|
|
335
|
+
description: "The current session object, included unless disableSession is true"
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
required: ["user", "session"]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
110
346
|
},
|
|
111
347
|
async (ctx) => {
|
|
112
|
-
const
|
|
348
|
+
const { session, valid } = await verifyTwoFactor(ctx);
|
|
349
|
+
const user = session.user;
|
|
113
350
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
114
351
|
model: twoFactorTable,
|
|
115
352
|
where: [
|
|
@@ -153,14 +390,19 @@ const backupCode2fa = (options) => {
|
|
|
153
390
|
]
|
|
154
391
|
});
|
|
155
392
|
if (!ctx.body.disableSession) {
|
|
156
|
-
|
|
157
|
-
session: ctx.context.session.session,
|
|
158
|
-
user
|
|
159
|
-
});
|
|
393
|
+
return valid(ctx);
|
|
160
394
|
}
|
|
161
395
|
return ctx.json({
|
|
162
|
-
|
|
163
|
-
|
|
396
|
+
token: session.session?.token,
|
|
397
|
+
user: {
|
|
398
|
+
id: session.user?.id,
|
|
399
|
+
email: session.user.email,
|
|
400
|
+
emailVerified: session.user.emailVerified,
|
|
401
|
+
name: session.user.name,
|
|
402
|
+
image: session.user.image,
|
|
403
|
+
createdAt: session.user.createdAt,
|
|
404
|
+
updatedAt: session.user.updatedAt
|
|
405
|
+
}
|
|
164
406
|
});
|
|
165
407
|
}
|
|
166
408
|
),
|
|
@@ -171,7 +413,37 @@ const backupCode2fa = (options) => {
|
|
|
171
413
|
body: z.object({
|
|
172
414
|
password: z.string()
|
|
173
415
|
}),
|
|
174
|
-
use: [sessionMiddleware]
|
|
416
|
+
use: [sessionMiddleware],
|
|
417
|
+
metadata: {
|
|
418
|
+
openapi: {
|
|
419
|
+
description: "Generate new backup codes for two-factor authentication",
|
|
420
|
+
responses: {
|
|
421
|
+
"200": {
|
|
422
|
+
description: "Backup codes generated successfully",
|
|
423
|
+
content: {
|
|
424
|
+
"application/json": {
|
|
425
|
+
schema: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
status: {
|
|
429
|
+
type: "boolean",
|
|
430
|
+
description: "Indicates if the backup codes were generated successfully",
|
|
431
|
+
enum: [true]
|
|
432
|
+
},
|
|
433
|
+
backupCodes: {
|
|
434
|
+
type: "array",
|
|
435
|
+
items: { type: "string" },
|
|
436
|
+
description: "Array of generated backup codes in plain text"
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
required: ["status", "backupCodes"]
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
175
447
|
},
|
|
176
448
|
async (ctx) => {
|
|
177
449
|
const user = ctx.context.session.user;
|
|
@@ -267,7 +539,6 @@ const otp2fa = (options) => {
|
|
|
267
539
|
*/
|
|
268
540
|
trustDevice: z.boolean().optional()
|
|
269
541
|
}).optional(),
|
|
270
|
-
use: [verifyTwoFactorMiddleware],
|
|
271
542
|
metadata: {
|
|
272
543
|
openapi: {
|
|
273
544
|
summary: "Send two factor OTP",
|
|
@@ -301,13 +572,13 @@ const otp2fa = (options) => {
|
|
|
301
572
|
message: "otp isn't configured"
|
|
302
573
|
});
|
|
303
574
|
}
|
|
304
|
-
const
|
|
575
|
+
const { session, key } = await verifyTwoFactor(ctx);
|
|
305
576
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
306
577
|
model: twoFactorTable,
|
|
307
578
|
where: [
|
|
308
579
|
{
|
|
309
580
|
field: "userId",
|
|
310
|
-
value: user.id
|
|
581
|
+
value: session.user.id
|
|
311
582
|
}
|
|
312
583
|
]
|
|
313
584
|
});
|
|
@@ -318,11 +589,14 @@ const otp2fa = (options) => {
|
|
|
318
589
|
}
|
|
319
590
|
const code = generateRandomString(opts.digits, "0-9");
|
|
320
591
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
321
|
-
value: code
|
|
322
|
-
identifier: `2fa-otp-${
|
|
592
|
+
value: `${code}!0`,
|
|
593
|
+
identifier: `2fa-otp-${key}`,
|
|
323
594
|
expiresAt: new Date(Date.now() + opts.period)
|
|
324
595
|
});
|
|
325
|
-
await options.sendOTP(
|
|
596
|
+
await options.sendOTP(
|
|
597
|
+
{ user: session.user, otp: code },
|
|
598
|
+
ctx.request
|
|
599
|
+
);
|
|
326
600
|
return ctx.json({ status: true });
|
|
327
601
|
}
|
|
328
602
|
);
|
|
@@ -341,23 +615,67 @@ const otp2fa = (options) => {
|
|
|
341
615
|
*/
|
|
342
616
|
trustDevice: z.boolean().optional()
|
|
343
617
|
}),
|
|
344
|
-
use: [verifyTwoFactorMiddleware],
|
|
345
618
|
metadata: {
|
|
346
619
|
openapi: {
|
|
347
620
|
summary: "Verify two factor OTP",
|
|
348
621
|
description: "Verify two factor OTP",
|
|
349
622
|
responses: {
|
|
350
|
-
200: {
|
|
351
|
-
description: "
|
|
623
|
+
"200": {
|
|
624
|
+
description: "Two-factor OTP verified successfully",
|
|
352
625
|
content: {
|
|
353
626
|
"application/json": {
|
|
354
627
|
schema: {
|
|
355
628
|
type: "object",
|
|
356
629
|
properties: {
|
|
357
|
-
|
|
358
|
-
type: "
|
|
630
|
+
token: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "Session token for the authenticated session"
|
|
633
|
+
},
|
|
634
|
+
user: {
|
|
635
|
+
type: "object",
|
|
636
|
+
properties: {
|
|
637
|
+
id: {
|
|
638
|
+
type: "string",
|
|
639
|
+
description: "Unique identifier of the user"
|
|
640
|
+
},
|
|
641
|
+
email: {
|
|
642
|
+
type: "string",
|
|
643
|
+
format: "email",
|
|
644
|
+
nullable: true,
|
|
645
|
+
description: "User's email address"
|
|
646
|
+
},
|
|
647
|
+
emailVerified: {
|
|
648
|
+
type: "boolean",
|
|
649
|
+
nullable: true,
|
|
650
|
+
description: "Whether the email is verified"
|
|
651
|
+
},
|
|
652
|
+
name: {
|
|
653
|
+
type: "string",
|
|
654
|
+
nullable: true,
|
|
655
|
+
description: "User's name"
|
|
656
|
+
},
|
|
657
|
+
image: {
|
|
658
|
+
type: "string",
|
|
659
|
+
format: "uri",
|
|
660
|
+
nullable: true,
|
|
661
|
+
description: "User's profile image URL"
|
|
662
|
+
},
|
|
663
|
+
createdAt: {
|
|
664
|
+
type: "string",
|
|
665
|
+
format: "date-time",
|
|
666
|
+
description: "Timestamp when the user was created"
|
|
667
|
+
},
|
|
668
|
+
updatedAt: {
|
|
669
|
+
type: "string",
|
|
670
|
+
format: "date-time",
|
|
671
|
+
description: "Timestamp when the user was last updated"
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
required: ["id", "createdAt", "updatedAt"],
|
|
675
|
+
description: "The authenticated user object"
|
|
359
676
|
}
|
|
360
|
-
}
|
|
677
|
+
},
|
|
678
|
+
required: ["token", "user"]
|
|
361
679
|
}
|
|
362
680
|
}
|
|
363
681
|
}
|
|
@@ -367,13 +685,13 @@ const otp2fa = (options) => {
|
|
|
367
685
|
}
|
|
368
686
|
},
|
|
369
687
|
async (ctx) => {
|
|
370
|
-
const
|
|
688
|
+
const { session, key, valid, invalid } = await verifyTwoFactor(ctx);
|
|
371
689
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
372
690
|
model: twoFactorTable,
|
|
373
691
|
where: [
|
|
374
692
|
{
|
|
375
693
|
field: "userId",
|
|
376
|
-
value: user.id
|
|
694
|
+
value: session.user.id
|
|
377
695
|
}
|
|
378
696
|
]
|
|
379
697
|
});
|
|
@@ -383,38 +701,74 @@ const otp2fa = (options) => {
|
|
|
383
701
|
});
|
|
384
702
|
}
|
|
385
703
|
const toCheckOtp = await ctx.context.internalAdapter.findVerificationValue(
|
|
386
|
-
`2fa-otp-${
|
|
704
|
+
`2fa-otp-${key}`
|
|
387
705
|
);
|
|
706
|
+
const [otp, counter] = toCheckOtp?.value?.split("!") ?? [];
|
|
388
707
|
if (!toCheckOtp || toCheckOtp.expiresAt < /* @__PURE__ */ new Date()) {
|
|
708
|
+
await ctx.context.internalAdapter.deleteVerificationValue(
|
|
709
|
+
`2fa-otp-${key}`
|
|
710
|
+
);
|
|
389
711
|
throw new APIError("BAD_REQUEST", {
|
|
390
712
|
message: TWO_FACTOR_ERROR_CODES.OTP_HAS_EXPIRED
|
|
391
713
|
});
|
|
392
714
|
}
|
|
393
|
-
|
|
394
|
-
|
|
715
|
+
const allowedAttempts = options?.allowedAttempts || 5;
|
|
716
|
+
if (parseInt(counter) >= allowedAttempts) {
|
|
717
|
+
await ctx.context.internalAdapter.deleteVerificationValue(
|
|
718
|
+
`2fa-otp-${key}`
|
|
719
|
+
);
|
|
720
|
+
throw new APIError("BAD_REQUEST", {
|
|
721
|
+
message: TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (otp === ctx.body.code) {
|
|
725
|
+
if (!session.user.twoFactorEnabled) {
|
|
726
|
+
if (!session.session) {
|
|
727
|
+
throw new APIError("BAD_REQUEST", {
|
|
728
|
+
message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION
|
|
729
|
+
});
|
|
730
|
+
}
|
|
395
731
|
const updatedUser = await ctx.context.internalAdapter.updateUser(
|
|
396
|
-
user.id,
|
|
732
|
+
session.user.id,
|
|
397
733
|
{
|
|
398
734
|
twoFactorEnabled: true
|
|
399
735
|
}
|
|
400
736
|
);
|
|
401
737
|
const newSession = await ctx.context.internalAdapter.createSession(
|
|
402
|
-
user.id,
|
|
403
|
-
ctx.
|
|
738
|
+
session.user.id,
|
|
739
|
+
ctx.headers,
|
|
404
740
|
false,
|
|
405
|
-
|
|
741
|
+
session.session
|
|
406
742
|
);
|
|
407
743
|
await ctx.context.internalAdapter.deleteSession(
|
|
408
|
-
|
|
744
|
+
session.session.token
|
|
409
745
|
);
|
|
410
746
|
await setSessionCookie(ctx, {
|
|
411
747
|
session: newSession,
|
|
412
748
|
user: updatedUser
|
|
413
749
|
});
|
|
750
|
+
return ctx.json({
|
|
751
|
+
token: newSession.token,
|
|
752
|
+
user: {
|
|
753
|
+
id: updatedUser.id,
|
|
754
|
+
email: updatedUser.email,
|
|
755
|
+
emailVerified: updatedUser.emailVerified,
|
|
756
|
+
name: updatedUser.name,
|
|
757
|
+
image: updatedUser.image,
|
|
758
|
+
createdAt: updatedUser.createdAt,
|
|
759
|
+
updatedAt: updatedUser.updatedAt
|
|
760
|
+
}
|
|
761
|
+
});
|
|
414
762
|
}
|
|
415
|
-
return
|
|
763
|
+
return valid(ctx);
|
|
416
764
|
} else {
|
|
417
|
-
|
|
765
|
+
await ctx.context.internalAdapter.updateVerificationValue(
|
|
766
|
+
toCheckOtp.id,
|
|
767
|
+
{
|
|
768
|
+
value: `${otp}!${parseInt(counter) + 1}`
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
return invalid("INVALID_CODE");
|
|
418
772
|
}
|
|
419
773
|
}
|
|
420
774
|
);
|
|
@@ -438,7 +792,11 @@ const totp2fa = (options) => {
|
|
|
438
792
|
"/totp/generate",
|
|
439
793
|
{
|
|
440
794
|
method: "POST",
|
|
441
|
-
|
|
795
|
+
body: z.object({
|
|
796
|
+
secret: z.string({
|
|
797
|
+
description: "The secret to generate the TOTP code"
|
|
798
|
+
})
|
|
799
|
+
}),
|
|
442
800
|
metadata: {
|
|
443
801
|
openapi: {
|
|
444
802
|
summary: "Generate TOTP code",
|
|
@@ -460,7 +818,8 @@ const totp2fa = (options) => {
|
|
|
460
818
|
}
|
|
461
819
|
}
|
|
462
820
|
}
|
|
463
|
-
}
|
|
821
|
+
},
|
|
822
|
+
SERVER_ONLY: true
|
|
464
823
|
}
|
|
465
824
|
},
|
|
466
825
|
async (ctx) => {
|
|
@@ -472,22 +831,7 @@ const totp2fa = (options) => {
|
|
|
472
831
|
message: "totp isn't configured"
|
|
473
832
|
});
|
|
474
833
|
}
|
|
475
|
-
const
|
|
476
|
-
const twoFactor = await ctx.context.adapter.findOne({
|
|
477
|
-
model: twoFactorTable,
|
|
478
|
-
where: [
|
|
479
|
-
{
|
|
480
|
-
field: "userId",
|
|
481
|
-
value: user.id
|
|
482
|
-
}
|
|
483
|
-
]
|
|
484
|
-
});
|
|
485
|
-
if (!twoFactor) {
|
|
486
|
-
throw new APIError("BAD_REQUEST", {
|
|
487
|
-
message: TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
const code = await createOTP(twoFactor.secret, {
|
|
834
|
+
const code = await createOTP(ctx.body.secret, {
|
|
491
835
|
period: opts.period,
|
|
492
836
|
digits: opts.digits
|
|
493
837
|
}).totp();
|
|
@@ -583,7 +927,6 @@ const totp2fa = (options) => {
|
|
|
583
927
|
description: "If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time."
|
|
584
928
|
}).optional()
|
|
585
929
|
}),
|
|
586
|
-
use: [verifyTwoFactorMiddleware],
|
|
587
930
|
metadata: {
|
|
588
931
|
openapi: {
|
|
589
932
|
summary: "Verify two factor TOTP",
|
|
@@ -617,7 +960,8 @@ const totp2fa = (options) => {
|
|
|
617
960
|
message: "totp isn't configured"
|
|
618
961
|
});
|
|
619
962
|
}
|
|
620
|
-
const
|
|
963
|
+
const { session, valid, invalid } = await verifyTwoFactor(ctx);
|
|
964
|
+
const user = session.user;
|
|
621
965
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
622
966
|
model: twoFactorTable,
|
|
623
967
|
where: [
|
|
@@ -641,9 +985,14 @@ const totp2fa = (options) => {
|
|
|
641
985
|
digits: opts.digits
|
|
642
986
|
}).verify(ctx.body.code);
|
|
643
987
|
if (!status) {
|
|
644
|
-
return
|
|
988
|
+
return invalid("INVALID_CODE");
|
|
645
989
|
}
|
|
646
990
|
if (!user.twoFactorEnabled) {
|
|
991
|
+
if (!session.session) {
|
|
992
|
+
throw new APIError("BAD_REQUEST", {
|
|
993
|
+
message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION
|
|
994
|
+
});
|
|
995
|
+
}
|
|
647
996
|
const updatedUser = await ctx.context.internalAdapter.updateUser(
|
|
648
997
|
user.id,
|
|
649
998
|
{
|
|
@@ -651,23 +1000,16 @@ const totp2fa = (options) => {
|
|
|
651
1000
|
},
|
|
652
1001
|
ctx
|
|
653
1002
|
);
|
|
654
|
-
const newSession = await ctx.context.internalAdapter.createSession(
|
|
655
|
-
user.id,
|
|
656
|
-
ctx.request,
|
|
657
|
-
false,
|
|
658
|
-
ctx.context.session.session
|
|
659
|
-
).catch((e) => {
|
|
1003
|
+
const newSession = await ctx.context.internalAdapter.createSession(user.id, ctx.headers, false, session.session).catch((e) => {
|
|
660
1004
|
throw e;
|
|
661
1005
|
});
|
|
662
|
-
await ctx.context.internalAdapter.deleteSession(
|
|
663
|
-
ctx.context.session.session.token
|
|
664
|
-
);
|
|
1006
|
+
await ctx.context.internalAdapter.deleteSession(session.session.token);
|
|
665
1007
|
await setSessionCookie(ctx, {
|
|
666
1008
|
session: newSession,
|
|
667
1009
|
user: updatedUser
|
|
668
1010
|
});
|
|
669
1011
|
}
|
|
670
|
-
return
|
|
1012
|
+
return valid(ctx);
|
|
671
1013
|
}
|
|
672
1014
|
);
|
|
673
1015
|
return {
|
|
@@ -736,7 +1078,10 @@ const twoFactor = (options) => {
|
|
|
736
1078
|
body: z.object({
|
|
737
1079
|
password: z.string({
|
|
738
1080
|
description: "User password"
|
|
739
|
-
})
|
|
1081
|
+
}),
|
|
1082
|
+
issuer: z.string({
|
|
1083
|
+
description: "Custom issuer for the TOTP URI"
|
|
1084
|
+
}).optional()
|
|
740
1085
|
}),
|
|
741
1086
|
use: [sessionMiddleware],
|
|
742
1087
|
metadata: {
|
|
@@ -773,7 +1118,7 @@ const twoFactor = (options) => {
|
|
|
773
1118
|
},
|
|
774
1119
|
async (ctx) => {
|
|
775
1120
|
const user = ctx.context.session.user;
|
|
776
|
-
const { password } = ctx.body;
|
|
1121
|
+
const { password, issuer } = ctx.body;
|
|
777
1122
|
const isPasswordValid = await validatePassword(ctx, {
|
|
778
1123
|
password,
|
|
779
1124
|
userId: user.id
|
|
@@ -802,7 +1147,7 @@ const twoFactor = (options) => {
|
|
|
802
1147
|
);
|
|
803
1148
|
const newSession = await ctx.context.internalAdapter.createSession(
|
|
804
1149
|
updatedUser.id,
|
|
805
|
-
ctx.
|
|
1150
|
+
ctx.headers,
|
|
806
1151
|
false,
|
|
807
1152
|
ctx.context.session.session
|
|
808
1153
|
);
|
|
@@ -834,7 +1179,7 @@ const twoFactor = (options) => {
|
|
|
834
1179
|
const totpURI = createOTP(secret, {
|
|
835
1180
|
digits: options?.totpOptions?.digits || 6,
|
|
836
1181
|
period: options?.totpOptions?.period
|
|
837
|
-
}).url(options?.issuer || ctx.context.appName, user.email);
|
|
1182
|
+
}).url(issuer || options?.issuer || ctx.context.appName, user.email);
|
|
838
1183
|
return ctx.json({ totpURI, backupCodes: backupCodes.backupCodes });
|
|
839
1184
|
}
|
|
840
1185
|
),
|
|
@@ -902,7 +1247,7 @@ const twoFactor = (options) => {
|
|
|
902
1247
|
});
|
|
903
1248
|
const newSession = await ctx.context.internalAdapter.createSession(
|
|
904
1249
|
updatedUser.id,
|
|
905
|
-
ctx.
|
|
1250
|
+
ctx.headers,
|
|
906
1251
|
false,
|
|
907
1252
|
ctx.context.session.session
|
|
908
1253
|
);
|
|
@@ -961,16 +1306,22 @@ const twoFactor = (options) => {
|
|
|
961
1306
|
}
|
|
962
1307
|
deleteSessionCookie(ctx, true);
|
|
963
1308
|
await ctx.context.internalAdapter.deleteSession(data.session.token);
|
|
1309
|
+
const maxAge = options?.otpOptions?.period || 60 * 5;
|
|
964
1310
|
const twoFactorCookie = ctx.context.createAuthCookie(
|
|
965
1311
|
TWO_FACTOR_COOKIE_NAME,
|
|
966
1312
|
{
|
|
967
|
-
maxAge
|
|
968
|
-
// 10 minutes
|
|
1313
|
+
maxAge
|
|
969
1314
|
}
|
|
970
1315
|
);
|
|
1316
|
+
const identifier = `2fa-${generateRandomString(20)}`;
|
|
1317
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
1318
|
+
value: data.user.id,
|
|
1319
|
+
identifier,
|
|
1320
|
+
expiresAt: new Date(Date.now() + maxAge * 1e3)
|
|
1321
|
+
});
|
|
971
1322
|
await ctx.setSignedCookie(
|
|
972
1323
|
twoFactorCookie.name,
|
|
973
|
-
|
|
1324
|
+
identifier,
|
|
974
1325
|
ctx.context.secret,
|
|
975
1326
|
twoFactorCookie.attributes
|
|
976
1327
|
);
|