better-auth 1.6.2 → 1.7.0-beta.0
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/api/index.d.mts +0 -2
- package/dist/api/routes/password.mjs +1 -1
- package/dist/api/routes/session.d.mts +0 -1
- package/dist/api/routes/session.mjs +1 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/admin.d.mts +1 -3
- package/dist/plugins/admin/routes.mjs +1 -7
- package/dist/plugins/generic-oauth/index.mjs +10 -1
- package/dist/plugins/generic-oauth/routes.mjs +4 -0
- package/dist/plugins/generic-oauth/types.d.mts +7 -2
- package/dist/plugins/jwt/utils.d.mts +1 -1
- package/dist/plugins/organization/organization.d.mts +1 -3
- package/dist/plugins/organization/organization.mjs +1 -7
- package/dist/plugins/two-factor/client.d.mts +2 -0
- package/dist/plugins/two-factor/error-code.d.mts +2 -0
- package/dist/plugins/two-factor/error-code.mjs +2 -0
- package/dist/plugins/two-factor/index.d.mts +19 -0
- package/dist/plugins/two-factor/index.mjs +39 -25
- package/dist/plugins/two-factor/types.d.mts +0 -5
- package/dist/test-utils/test-instance.d.mts +0 -6
- package/package.json +8 -8
package/dist/api/index.d.mts
CHANGED
|
@@ -249,7 +249,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
249
249
|
"application/json": {
|
|
250
250
|
schema: {
|
|
251
251
|
type: "object";
|
|
252
|
-
nullable: boolean;
|
|
253
252
|
properties: {
|
|
254
253
|
session: {
|
|
255
254
|
$ref: string;
|
|
@@ -2239,7 +2238,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2239
2238
|
"application/json": {
|
|
2240
2239
|
schema: {
|
|
2241
2240
|
type: "object";
|
|
2242
|
-
nullable: boolean;
|
|
2243
2241
|
properties: {
|
|
2244
2242
|
session: {
|
|
2245
2243
|
$ref: string;
|
|
@@ -82,7 +82,7 @@ const requestPasswordReset = createAuthEndpoint("/request-password-reset", {
|
|
|
82
82
|
});
|
|
83
83
|
const requestPasswordResetCallback = createAuthEndpoint("/reset-password/:token", {
|
|
84
84
|
method: "GET",
|
|
85
|
-
operationId: "
|
|
85
|
+
operationId: "resetPasswordCallback",
|
|
86
86
|
query: z.object({ callbackURL: z.string().meta({ description: "The URL to redirect the user to reset their password" }) }),
|
|
87
87
|
use: [originCheck((ctx) => ctx.query.callbackURL)],
|
|
88
88
|
metadata: { openapi: {
|
|
@@ -24,8 +24,7 @@ const getSession = () => createAuthEndpoint("/get-session", {
|
|
|
24
24
|
responses: { "200": {
|
|
25
25
|
description: "Success",
|
|
26
26
|
content: { "application/json": { schema: {
|
|
27
|
-
type: "object",
|
|
28
|
-
nullable: true,
|
|
27
|
+
type: ["object", "null"],
|
|
29
28
|
properties: {
|
|
30
29
|
session: { $ref: "#/components/schemas/Session" },
|
|
31
30
|
user: { $ref: "#/components/schemas/User" }
|
package/dist/package.mjs
CHANGED
|
@@ -775,11 +775,9 @@ declare const admin: <O extends AdminOptions>(options?: O | undefined) => {
|
|
|
775
775
|
body: zod.ZodIntersection<zod.ZodObject<{
|
|
776
776
|
userId: zod.ZodOptional<zod.ZodCoercedString<unknown>>;
|
|
777
777
|
role: zod.ZodOptional<zod.ZodString>;
|
|
778
|
-
}, zod_v4_core0.$strip>, zod.
|
|
778
|
+
}, zod_v4_core0.$strip>, zod.ZodXor<readonly [zod.ZodObject<{
|
|
779
779
|
permission: zod.ZodRecord<zod.ZodString, zod.ZodArray<zod.ZodString>>;
|
|
780
|
-
permissions: zod.ZodUndefined;
|
|
781
780
|
}, zod_v4_core0.$strip>, zod.ZodObject<{
|
|
782
|
-
permission: zod.ZodUndefined;
|
|
783
781
|
permissions: zod.ZodRecord<zod.ZodString, zod.ZodArray<zod.ZodString>>;
|
|
784
782
|
}, zod_v4_core0.$strip>]>>;
|
|
785
783
|
metadata: {
|
|
@@ -766,13 +766,7 @@ const setUserPassword = (opts) => createAuthEndpoint("/admin/set-user-password",
|
|
|
766
766
|
const userHasPermissionBodySchema = z.object({
|
|
767
767
|
userId: z.coerce.string().optional().meta({ description: `The user id. Eg: "user-id"` }),
|
|
768
768
|
role: z.string().optional().meta({ description: `The role to check permission for. Eg: "admin"` })
|
|
769
|
-
}).and(z.
|
|
770
|
-
permission: z.record(z.string(), z.array(z.string())),
|
|
771
|
-
permissions: z.undefined()
|
|
772
|
-
}), z.object({
|
|
773
|
-
permission: z.undefined(),
|
|
774
|
-
permissions: z.record(z.string(), z.array(z.string()))
|
|
775
|
-
})]));
|
|
769
|
+
}).and(z.xor([z.object({ permission: z.record(z.string(), z.array(z.string())) }), z.object({ permissions: z.record(z.string(), z.array(z.string())) })]));
|
|
776
770
|
/**
|
|
777
771
|
* ### Endpoint
|
|
778
772
|
*
|
|
@@ -14,6 +14,13 @@ import { APIError } from "@better-auth/core/error";
|
|
|
14
14
|
import { createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
15
15
|
import { betterFetch } from "@better-fetch/fetch";
|
|
16
16
|
//#region src/plugins/generic-oauth/index.ts
|
|
17
|
+
function buildClientAssertion(config, tokenEndpoint) {
|
|
18
|
+
if (config.authentication !== "private_key_jwt" || !config.clientAssertion) return;
|
|
19
|
+
return {
|
|
20
|
+
...config.clientAssertion,
|
|
21
|
+
tokenEndpoint
|
|
22
|
+
};
|
|
23
|
+
}
|
|
17
24
|
/**
|
|
18
25
|
* A generic OAuth plugin that can be used to add OAuth support to any provider
|
|
19
26
|
*/
|
|
@@ -87,7 +94,8 @@ const genericOAuth = (options) => {
|
|
|
87
94
|
redirectURI: c.redirectURI
|
|
88
95
|
},
|
|
89
96
|
tokenEndpoint: finalTokenUrl,
|
|
90
|
-
authentication: c.authentication
|
|
97
|
+
authentication: c.authentication,
|
|
98
|
+
clientAssertion: buildClientAssertion(c, finalTokenUrl)
|
|
91
99
|
});
|
|
92
100
|
},
|
|
93
101
|
async refreshAccessToken(refreshToken) {
|
|
@@ -107,6 +115,7 @@ const genericOAuth = (options) => {
|
|
|
107
115
|
clientSecret: c.clientSecret
|
|
108
116
|
},
|
|
109
117
|
authentication: c.authentication,
|
|
118
|
+
clientAssertion: buildClientAssertion(c, finalTokenUrl),
|
|
110
119
|
tokenEndpoint: finalTokenUrl
|
|
111
120
|
});
|
|
112
121
|
},
|
|
@@ -195,6 +195,10 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
195
195
|
},
|
|
196
196
|
tokenEndpoint: finalTokenUrl,
|
|
197
197
|
authentication: providerConfig.authentication,
|
|
198
|
+
clientAssertion: providerConfig.authentication === "private_key_jwt" && providerConfig.clientAssertion ? {
|
|
199
|
+
...providerConfig.clientAssertion,
|
|
200
|
+
tokenEndpoint: finalTokenUrl
|
|
201
|
+
} : void 0,
|
|
198
202
|
additionalParams
|
|
199
203
|
});
|
|
200
204
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GenericEndpointContext } from "@better-auth/core";
|
|
2
2
|
import { User } from "@better-auth/core/db";
|
|
3
|
-
import { OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2";
|
|
3
|
+
import { ClientAssertionConfig, OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2";
|
|
4
4
|
|
|
5
5
|
//#region src/plugins/generic-oauth/types.d.ts
|
|
6
6
|
interface GenericOAuthOptions {
|
|
@@ -134,7 +134,12 @@ interface GenericOAuthConfig {
|
|
|
134
134
|
* Authentication method for token requests.
|
|
135
135
|
* @default "post"
|
|
136
136
|
*/
|
|
137
|
-
authentication?: ("basic" | "post") | undefined;
|
|
137
|
+
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Client assertion config for `private_key_jwt` authentication.
|
|
140
|
+
* Required when `authentication` is `"private_key_jwt"`.
|
|
141
|
+
*/
|
|
142
|
+
clientAssertion?: ClientAssertionConfig | undefined;
|
|
138
143
|
/**
|
|
139
144
|
* Custom headers to include in the discovery request.
|
|
140
145
|
* Useful for providers like Epic that require specific headers (e.g., Epic-Client-ID).
|
|
@@ -16,7 +16,7 @@ declare function toExpJWT(expirationTime: number | Date | string, iat: number):
|
|
|
16
16
|
declare function generateExportedKeyPair(options?: JwtOptions | undefined): Promise<{
|
|
17
17
|
publicWebKey: jose.JWK;
|
|
18
18
|
privateWebKey: jose.JWK;
|
|
19
|
-
alg: "
|
|
19
|
+
alg: "RS256" | "PS256" | "ES256" | "ES512" | "EdDSA";
|
|
20
20
|
cfg: {
|
|
21
21
|
crv?: "Ed25519" | undefined;
|
|
22
22
|
} | {
|
|
@@ -99,11 +99,9 @@ declare const createHasPermission: <O extends OrganizationOptions>(options: O) =
|
|
|
99
99
|
requireHeaders: true;
|
|
100
100
|
body: z.ZodIntersection<z.ZodObject<{
|
|
101
101
|
organizationId: z.ZodOptional<z.ZodString>;
|
|
102
|
-
}, z.core.$strip>, z.
|
|
102
|
+
}, z.core.$strip>, z.ZodXor<readonly [z.ZodObject<{
|
|
103
103
|
permission: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
104
|
-
permissions: z.ZodUndefined;
|
|
105
104
|
}, z.core.$strip>, z.ZodObject<{
|
|
106
|
-
permission: z.ZodUndefined;
|
|
107
105
|
permissions: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
108
106
|
}, z.core.$strip>]>>;
|
|
109
107
|
use: ((inputContext: better_call0.MiddlewareInputContext<{
|
|
@@ -18,13 +18,7 @@ import * as z from "zod";
|
|
|
18
18
|
function parseRoles(roles) {
|
|
19
19
|
return Array.isArray(roles) ? roles.join(",") : roles;
|
|
20
20
|
}
|
|
21
|
-
const createHasPermissionBodySchema = z.object({ organizationId: z.string().optional() }).and(z.
|
|
22
|
-
permission: z.record(z.string(), z.array(z.string())),
|
|
23
|
-
permissions: z.undefined()
|
|
24
|
-
}), z.object({
|
|
25
|
-
permission: z.undefined(),
|
|
26
|
-
permissions: z.record(z.string(), z.array(z.string()))
|
|
27
|
-
})]));
|
|
21
|
+
const createHasPermissionBodySchema = z.object({ organizationId: z.string().optional() }).and(z.xor([z.object({ permission: z.record(z.string(), z.array(z.string())) }), z.object({ permissions: z.record(z.string(), z.array(z.string())) })]));
|
|
28
22
|
const createHasPermission = (options) => {
|
|
29
23
|
return createAuthEndpoint("/organization/has-permission", {
|
|
30
24
|
method: "POST",
|
|
@@ -58,8 +58,10 @@ declare const twoFactorClient: (options?: {
|
|
|
58
58
|
}[];
|
|
59
59
|
$ERROR_CODES: {
|
|
60
60
|
OTP_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"OTP_NOT_ENABLED">;
|
|
61
|
+
OTP_NOT_CONFIGURED: _better_auth_core_utils_error_codes0.RawError<"OTP_NOT_CONFIGURED">;
|
|
61
62
|
OTP_HAS_EXPIRED: _better_auth_core_utils_error_codes0.RawError<"OTP_HAS_EXPIRED">;
|
|
62
63
|
TOTP_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TOTP_NOT_ENABLED">;
|
|
64
|
+
TOTP_NOT_CONFIGURED: _better_auth_core_utils_error_codes0.RawError<"TOTP_NOT_CONFIGURED">;
|
|
63
65
|
TWO_FACTOR_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TWO_FACTOR_NOT_ENABLED">;
|
|
64
66
|
BACKUP_CODES_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"BACKUP_CODES_NOT_ENABLED">;
|
|
65
67
|
INVALID_BACKUP_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_BACKUP_CODE">;
|
|
@@ -3,8 +3,10 @@ import * as _better_auth_core_utils_error_codes0 from "@better-auth/core/utils/e
|
|
|
3
3
|
//#region src/plugins/two-factor/error-code.d.ts
|
|
4
4
|
declare const TWO_FACTOR_ERROR_CODES: {
|
|
5
5
|
OTP_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"OTP_NOT_ENABLED">;
|
|
6
|
+
OTP_NOT_CONFIGURED: _better_auth_core_utils_error_codes0.RawError<"OTP_NOT_CONFIGURED">;
|
|
6
7
|
OTP_HAS_EXPIRED: _better_auth_core_utils_error_codes0.RawError<"OTP_HAS_EXPIRED">;
|
|
7
8
|
TOTP_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TOTP_NOT_ENABLED">;
|
|
9
|
+
TOTP_NOT_CONFIGURED: _better_auth_core_utils_error_codes0.RawError<"TOTP_NOT_CONFIGURED">;
|
|
8
10
|
TWO_FACTOR_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TWO_FACTOR_NOT_ENABLED">;
|
|
9
11
|
BACKUP_CODES_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"BACKUP_CODES_NOT_ENABLED">;
|
|
10
12
|
INVALID_BACKUP_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_BACKUP_CODE">;
|
|
@@ -2,8 +2,10 @@ import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
|
|
|
2
2
|
//#region src/plugins/two-factor/error-code.ts
|
|
3
3
|
const TWO_FACTOR_ERROR_CODES = defineErrorCodes({
|
|
4
4
|
OTP_NOT_ENABLED: "OTP not enabled",
|
|
5
|
+
OTP_NOT_CONFIGURED: "OTP is not available",
|
|
5
6
|
OTP_HAS_EXPIRED: "OTP has expired",
|
|
6
7
|
TOTP_NOT_ENABLED: "TOTP not enabled",
|
|
8
|
+
TOTP_NOT_CONFIGURED: "TOTP is not available",
|
|
7
9
|
TWO_FACTOR_NOT_ENABLED: "Two factor isn't enabled",
|
|
8
10
|
BACKUP_CODES_NOT_ENABLED: "Backup codes aren't enabled",
|
|
9
11
|
INVALID_BACKUP_CODE: "Invalid backup code",
|
|
@@ -40,9 +40,17 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
40
40
|
method: "POST";
|
|
41
41
|
body: z.ZodObject<{
|
|
42
42
|
password: z.ZodOptional<z.ZodString>;
|
|
43
|
+
method: z.ZodDefault<z.ZodEnum<{
|
|
44
|
+
otp: "otp";
|
|
45
|
+
totp: "totp";
|
|
46
|
+
}>>;
|
|
43
47
|
issuer: z.ZodOptional<z.ZodString>;
|
|
44
48
|
}, z.core.$strip> | z.ZodObject<{
|
|
45
49
|
password: z.ZodString;
|
|
50
|
+
method: z.ZodDefault<z.ZodEnum<{
|
|
51
|
+
otp: "otp";
|
|
52
|
+
totp: "totp";
|
|
53
|
+
}>>;
|
|
46
54
|
issuer: z.ZodOptional<z.ZodString>;
|
|
47
55
|
}, z.core.$strip>;
|
|
48
56
|
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
@@ -80,6 +88,11 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
80
88
|
schema: {
|
|
81
89
|
type: "object";
|
|
82
90
|
properties: {
|
|
91
|
+
method: {
|
|
92
|
+
type: string;
|
|
93
|
+
enum: string[];
|
|
94
|
+
description: string;
|
|
95
|
+
};
|
|
83
96
|
totpURI: {
|
|
84
97
|
type: string;
|
|
85
98
|
description: string;
|
|
@@ -92,6 +105,7 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
92
105
|
description: string;
|
|
93
106
|
};
|
|
94
107
|
};
|
|
108
|
+
required: string[];
|
|
95
109
|
};
|
|
96
110
|
};
|
|
97
111
|
};
|
|
@@ -100,6 +114,9 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
100
114
|
};
|
|
101
115
|
};
|
|
102
116
|
}, {
|
|
117
|
+
method: "otp";
|
|
118
|
+
} | {
|
|
119
|
+
method: "totp";
|
|
103
120
|
totpURI: string;
|
|
104
121
|
backupCodes: string[];
|
|
105
122
|
}>;
|
|
@@ -672,8 +689,10 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
672
689
|
}[];
|
|
673
690
|
$ERROR_CODES: {
|
|
674
691
|
OTP_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"OTP_NOT_ENABLED">;
|
|
692
|
+
OTP_NOT_CONFIGURED: _better_auth_core_utils_error_codes0.RawError<"OTP_NOT_CONFIGURED">;
|
|
675
693
|
OTP_HAS_EXPIRED: _better_auth_core_utils_error_codes0.RawError<"OTP_HAS_EXPIRED">;
|
|
676
694
|
TOTP_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TOTP_NOT_ENABLED">;
|
|
695
|
+
TOTP_NOT_CONFIGURED: _better_auth_core_utils_error_codes0.RawError<"TOTP_NOT_CONFIGURED">;
|
|
677
696
|
TWO_FACTOR_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TWO_FACTOR_NOT_ENABLED">;
|
|
678
697
|
BACKUP_CODES_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"BACKUP_CODES_NOT_ENABLED">;
|
|
679
698
|
INVALID_BACKUP_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_BACKUP_CODE">;
|
|
@@ -36,12 +36,16 @@ const twoFactor = (options) => {
|
|
|
36
36
|
});
|
|
37
37
|
const otp = otp2fa(options?.otpOptions);
|
|
38
38
|
const passwordSchema = z.string().meta({ description: "User password" });
|
|
39
|
+
const methodField = z.enum(["otp", "totp"]).default("totp").meta({ description: "The 2FA method to enable. 'totp' generates an authenticator app secret (requires verification). 'otp' enables email/SMS-based codes immediately." });
|
|
40
|
+
const issuerField = z.string().meta({ description: "Custom issuer for the TOTP URI" }).optional();
|
|
39
41
|
const enableTwoFactorBodySchema = allowPasswordless ? z.object({
|
|
40
42
|
password: passwordSchema.optional(),
|
|
41
|
-
|
|
43
|
+
method: methodField,
|
|
44
|
+
issuer: issuerField
|
|
42
45
|
}) : z.object({
|
|
43
46
|
password: passwordSchema,
|
|
44
|
-
|
|
47
|
+
method: methodField,
|
|
48
|
+
issuer: issuerField
|
|
45
49
|
});
|
|
46
50
|
const disableTwoFactorBodySchema = allowPasswordless ? z.object({ password: passwordSchema.optional() }) : z.object({ password: passwordSchema });
|
|
47
51
|
return {
|
|
@@ -57,28 +61,34 @@ const twoFactor = (options) => {
|
|
|
57
61
|
use: [sessionMiddleware],
|
|
58
62
|
metadata: { openapi: {
|
|
59
63
|
summary: "Enable two factor authentication",
|
|
60
|
-
description: "
|
|
64
|
+
description: "Enable two factor authentication. Pass method 'totp' (default) to set up an authenticator app (returns TOTP URI and backup codes), or 'otp' to enable email/SMS-based codes immediately.",
|
|
61
65
|
responses: { 200: {
|
|
62
66
|
description: "Successful response",
|
|
63
67
|
content: { "application/json": { schema: {
|
|
64
68
|
type: "object",
|
|
65
69
|
properties: {
|
|
70
|
+
method: {
|
|
71
|
+
type: "string",
|
|
72
|
+
enum: ["otp", "totp"],
|
|
73
|
+
description: "The 2FA method that was enabled."
|
|
74
|
+
},
|
|
66
75
|
totpURI: {
|
|
67
76
|
type: "string",
|
|
68
|
-
description: "TOTP URI"
|
|
77
|
+
description: "TOTP URI for authenticator app setup. Only present when method is 'totp'."
|
|
69
78
|
},
|
|
70
79
|
backupCodes: {
|
|
71
80
|
type: "array",
|
|
72
81
|
items: { type: "string" },
|
|
73
|
-
description: "
|
|
82
|
+
description: "Recovery backup codes. Only present when method is 'totp'."
|
|
74
83
|
}
|
|
75
|
-
}
|
|
84
|
+
},
|
|
85
|
+
required: ["method"]
|
|
76
86
|
} } }
|
|
77
87
|
} }
|
|
78
88
|
} }
|
|
79
89
|
}, async (ctx) => {
|
|
80
90
|
const user = ctx.context.session.user;
|
|
81
|
-
const { password, issuer } = ctx.body;
|
|
91
|
+
const { password, issuer, method } = ctx.body;
|
|
82
92
|
if (await shouldRequirePassword(ctx, user.id, allowPasswordless)) {
|
|
83
93
|
if (!password) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_PASSWORD);
|
|
84
94
|
if (!await validatePassword(ctx, {
|
|
@@ -86,23 +96,18 @@ const twoFactor = (options) => {
|
|
|
86
96
|
userId: user.id
|
|
87
97
|
})) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_PASSWORD);
|
|
88
98
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
data: secret
|
|
93
|
-
});
|
|
94
|
-
const backupCodes = await generateBackupCodes(ctx.context.secretConfig, backupCodeOptions);
|
|
95
|
-
if (options?.skipVerificationOnEnable) {
|
|
99
|
+
if (method === "otp" && !options?.otpOptions?.sendOTP) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.OTP_NOT_CONFIGURED);
|
|
100
|
+
if (method === "totp" && options?.totpOptions?.disable) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOTP_NOT_CONFIGURED);
|
|
101
|
+
if (method === "otp") {
|
|
96
102
|
const updatedUser = await ctx.context.internalAdapter.updateUser(user.id, { twoFactorEnabled: true });
|
|
97
|
-
/**
|
|
98
|
-
* Update the session cookie with the new user data
|
|
99
|
-
*/
|
|
100
103
|
await setSessionCookie(ctx, {
|
|
101
104
|
session: await ctx.context.internalAdapter.createSession(updatedUser.id, false, ctx.context.session.session),
|
|
102
105
|
user: updatedUser
|
|
103
106
|
});
|
|
104
107
|
await ctx.context.internalAdapter.deleteSession(ctx.context.session.session.token);
|
|
108
|
+
return ctx.json({ method: "otp" });
|
|
105
109
|
}
|
|
110
|
+
const backupCodes = await generateBackupCodes(ctx.context.secretConfig, backupCodeOptions);
|
|
106
111
|
const existingTwoFactor = await ctx.context.adapter.findOne({
|
|
107
112
|
model: opts.twoFactorTable,
|
|
108
113
|
where: [{
|
|
@@ -110,20 +115,28 @@ const twoFactor = (options) => {
|
|
|
110
115
|
value: user.id
|
|
111
116
|
}]
|
|
112
117
|
});
|
|
113
|
-
|
|
118
|
+
const secret = generateRandomString(32);
|
|
119
|
+
const totpData = {
|
|
120
|
+
secret: await symmetricEncrypt({
|
|
121
|
+
key: ctx.context.secretConfig,
|
|
122
|
+
data: secret
|
|
123
|
+
}),
|
|
124
|
+
backupCodes: backupCodes.encryptedBackupCodes,
|
|
125
|
+
verified: existingTwoFactor != null && existingTwoFactor.verified === true
|
|
126
|
+
};
|
|
127
|
+
if (existingTwoFactor) await ctx.context.adapter.update({
|
|
114
128
|
model: opts.twoFactorTable,
|
|
129
|
+
update: totpData,
|
|
115
130
|
where: [{
|
|
116
|
-
field: "
|
|
117
|
-
value:
|
|
131
|
+
field: "id",
|
|
132
|
+
value: existingTwoFactor.id
|
|
118
133
|
}]
|
|
119
134
|
});
|
|
120
|
-
await ctx.context.adapter.create({
|
|
135
|
+
else await ctx.context.adapter.create({
|
|
121
136
|
model: opts.twoFactorTable,
|
|
122
137
|
data: {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
userId: user.id,
|
|
126
|
-
verified: existingTwoFactor != null && existingTwoFactor.verified !== false || !!options?.skipVerificationOnEnable
|
|
138
|
+
...totpData,
|
|
139
|
+
userId: user.id
|
|
127
140
|
}
|
|
128
141
|
});
|
|
129
142
|
const totpURI = createOTP(secret, {
|
|
@@ -131,6 +144,7 @@ const twoFactor = (options) => {
|
|
|
131
144
|
period: options?.totpOptions?.period
|
|
132
145
|
}).url(issuer || options?.issuer || ctx.context.appName, user.email);
|
|
133
146
|
return ctx.json({
|
|
147
|
+
method: "totp",
|
|
134
148
|
totpURI,
|
|
135
149
|
backupCodes: backupCodes.backupCodes
|
|
136
150
|
});
|
|
@@ -31,11 +31,6 @@ interface TwoFactorOptions {
|
|
|
31
31
|
* Backup code options
|
|
32
32
|
*/
|
|
33
33
|
backupCodeOptions?: BackupCodeOptions | undefined;
|
|
34
|
-
/**
|
|
35
|
-
* Skip verification on enabling two factor authentication.
|
|
36
|
-
* @default false
|
|
37
|
-
*/
|
|
38
|
-
skipVerificationOnEnable?: boolean | undefined;
|
|
39
34
|
/**
|
|
40
35
|
* Allow enabling and managing 2FA without a password when the user does not
|
|
41
36
|
* have a credential account (e.g. passkey-only users).
|
|
@@ -261,7 +261,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
|
|
|
261
261
|
"application/json": {
|
|
262
262
|
schema: {
|
|
263
263
|
type: "object";
|
|
264
|
-
nullable: boolean;
|
|
265
264
|
properties: {
|
|
266
265
|
session: {
|
|
267
266
|
$ref: string;
|
|
@@ -2265,7 +2264,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
|
|
|
2265
2264
|
"application/json": {
|
|
2266
2265
|
schema: {
|
|
2267
2266
|
type: "object";
|
|
2268
|
-
nullable: boolean;
|
|
2269
2267
|
properties: {
|
|
2270
2268
|
session: {
|
|
2271
2269
|
$ref: string;
|
|
@@ -4272,7 +4270,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
|
|
|
4272
4270
|
"application/json": {
|
|
4273
4271
|
schema: {
|
|
4274
4272
|
type: "object";
|
|
4275
|
-
nullable: boolean;
|
|
4276
4273
|
properties: {
|
|
4277
4274
|
session: {
|
|
4278
4275
|
$ref: string;
|
|
@@ -6276,7 +6273,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
|
|
|
6276
6273
|
"application/json": {
|
|
6277
6274
|
schema: {
|
|
6278
6275
|
type: "object";
|
|
6279
|
-
nullable: boolean;
|
|
6280
6276
|
properties: {
|
|
6281
6277
|
session: {
|
|
6282
6278
|
$ref: string;
|
|
@@ -8354,7 +8350,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
|
|
|
8354
8350
|
"application/json": {
|
|
8355
8351
|
schema: {
|
|
8356
8352
|
type: "object";
|
|
8357
|
-
nullable: boolean;
|
|
8358
8353
|
properties: {
|
|
8359
8354
|
session: {
|
|
8360
8355
|
$ref: string;
|
|
@@ -10358,7 +10353,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
|
|
|
10358
10353
|
"application/json": {
|
|
10359
10354
|
schema: {
|
|
10360
10355
|
type: "object";
|
|
10361
|
-
nullable: boolean;
|
|
10362
10356
|
properties: {
|
|
10363
10357
|
session: {
|
|
10364
10358
|
$ref: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0-beta.0",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -489,13 +489,13 @@
|
|
|
489
489
|
"kysely": "^0.28.14",
|
|
490
490
|
"nanostores": "^1.1.1",
|
|
491
491
|
"zod": "^4.3.6",
|
|
492
|
-
"@better-auth/core": "1.
|
|
493
|
-
"@better-auth/drizzle-adapter": "1.
|
|
494
|
-
"@better-auth/kysely-adapter": "1.
|
|
495
|
-
"@better-auth/memory-adapter": "1.
|
|
496
|
-
"@better-auth/mongo-adapter": "1.
|
|
497
|
-
"@better-auth/prisma-adapter": "1.
|
|
498
|
-
"@better-auth/telemetry": "1.
|
|
492
|
+
"@better-auth/core": "1.7.0-beta.0",
|
|
493
|
+
"@better-auth/drizzle-adapter": "1.7.0-beta.0",
|
|
494
|
+
"@better-auth/kysely-adapter": "1.7.0-beta.0",
|
|
495
|
+
"@better-auth/memory-adapter": "1.7.0-beta.0",
|
|
496
|
+
"@better-auth/mongo-adapter": "1.7.0-beta.0",
|
|
497
|
+
"@better-auth/prisma-adapter": "1.7.0-beta.0",
|
|
498
|
+
"@better-auth/telemetry": "1.7.0-beta.0"
|
|
499
499
|
},
|
|
500
500
|
"devDependencies": {
|
|
501
501
|
"@lynx-js/react": "^0.116.3",
|