better-auth 1.6.1 → 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/callback.mjs +1 -1
- 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/integrations/next-js.mjs +21 -12
- package/dist/oauth2/state.d.mts +1 -0
- 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/oauth-proxy/index.mjs +4 -0
- 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 +13 -1
- package/dist/plugins/two-factor/client.mjs +1 -1
- 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 +26 -0
- package/dist/plugins/two-factor/index.mjs +67 -22
- package/dist/plugins/two-factor/otp/index.mjs +1 -1
- package/dist/plugins/two-factor/schema.d.mts +6 -0
- package/dist/plugins/two-factor/schema.mjs +6 -0
- package/dist/plugins/two-factor/totp/index.mjs +19 -10
- package/dist/plugins/two-factor/types.d.mts +1 -6
- package/dist/state.d.mts +6 -0
- package/dist/state.mjs +18 -2
- 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;
|
|
@@ -104,7 +104,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
104
104
|
return redirectOnError("unable_to_link_account");
|
|
105
105
|
}
|
|
106
106
|
if (userInfo.email?.toLowerCase() !== link.email.toLowerCase() && c.context.options.account?.accountLinking?.allowDifferentEmails !== true) return redirectOnError("email_doesn't_match");
|
|
107
|
-
const existingAccount = await c.context.internalAdapter.
|
|
107
|
+
const existingAccount = await c.context.internalAdapter.findAccountByProviderId(String(userInfo.id), provider.id);
|
|
108
108
|
if (existingAccount) {
|
|
109
109
|
if (existingAccount.userId.toString() !== link.userId.toString()) return redirectOnError("account_already_linked_to_different_user");
|
|
110
110
|
const updateData = Object.fromEntries(Object.entries({
|
|
@@ -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" }
|
|
@@ -24,20 +24,29 @@ const nextCookies = () => {
|
|
|
24
24
|
matcher(ctx) {
|
|
25
25
|
return ctx.path === "/get-session";
|
|
26
26
|
},
|
|
27
|
-
handler: createAuthMiddleware(async () => {
|
|
28
|
-
|
|
27
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
28
|
+
if ("_flag" in ctx && ctx._flag === "router") return;
|
|
29
|
+
let headersStore;
|
|
29
30
|
try {
|
|
30
|
-
const {
|
|
31
|
-
|
|
31
|
+
const { headers } = await import("next/headers.js");
|
|
32
|
+
headersStore = await headers();
|
|
32
33
|
} catch {
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Detect RSC via headers, NOT by probing cookies().set().
|
|
38
|
+
* In Next.js, cookies().set() unconditionally triggers router
|
|
39
|
+
* cache invalidation -- even if the value is unchanged.
|
|
40
|
+
*
|
|
41
|
+
* RSC sends `RSC: 1` without `next-action`. Only in that
|
|
42
|
+
* context cookies cannot be written -- skip session refresh
|
|
43
|
+
* to avoid DB/cookie mismatch.
|
|
44
|
+
*
|
|
45
|
+
* @see https://github.com/vercel/next.js/blob/8c5af211d580/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L112-L157
|
|
46
|
+
*/
|
|
47
|
+
const isRSC = headersStore.get("RSC") === "1";
|
|
48
|
+
const isServerAction = !!headersStore.get("next-action");
|
|
49
|
+
if (isRSC && !isServerAction) await setShouldSkipSessionRefresh(true);
|
|
41
50
|
})
|
|
42
51
|
}],
|
|
43
52
|
after: [{
|
|
@@ -51,12 +60,12 @@ const nextCookies = () => {
|
|
|
51
60
|
const setCookies = returned?.get("set-cookie");
|
|
52
61
|
if (!setCookies) return;
|
|
53
62
|
const parsed = parseSetCookieHeader(setCookies);
|
|
54
|
-
const { cookies } = await import("next/headers.js");
|
|
55
63
|
let cookieHelper;
|
|
56
64
|
try {
|
|
65
|
+
const { cookies } = await import("next/headers.js");
|
|
57
66
|
cookieHelper = await cookies();
|
|
58
67
|
} catch (error) {
|
|
59
|
-
if (error instanceof Error && error.message.startsWith("`cookies` was called outside a request scope.")) return;
|
|
68
|
+
if (error instanceof Error && (error.message.startsWith("`cookies` was called outside a request scope.") || error.message.includes("Cannot find module"))) return;
|
|
60
69
|
throw error;
|
|
61
70
|
}
|
|
62
71
|
parsed.forEach((value, key) => {
|
package/dist/oauth2/state.d.mts
CHANGED
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
|
} | {
|
|
@@ -164,6 +164,10 @@ const oAuthProxy = (opts) => {
|
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
166
|
const errorURL = stateData.errorURL || ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
167
|
+
if (stateData.oauthState !== void 0 && stateData.oauthState !== statePackage.state) {
|
|
168
|
+
ctx.context.logger.error("OAuth proxy state binding mismatch");
|
|
169
|
+
throw redirectOnError(ctx, errorURL, "state_mismatch");
|
|
170
|
+
}
|
|
167
171
|
if (error) throw redirectOnError(ctx, errorURL, error);
|
|
168
172
|
if (!code) {
|
|
169
173
|
ctx.context.logger.error("OAuth callback missing authorization code");
|
|
@@ -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",
|
|
@@ -19,8 +19,18 @@ declare const twoFactorClient: (options?: {
|
|
|
19
19
|
/**
|
|
20
20
|
* a redirect function to call if a user needs to verify
|
|
21
21
|
* their two factor
|
|
22
|
+
*
|
|
23
|
+
* @param context.twoFactorMethods - The list of
|
|
24
|
+
* enabled two factor providers (e.g. ["totp", "otp"]).
|
|
25
|
+
* Use this to determine which 2FA UI to show.
|
|
22
26
|
*/
|
|
23
|
-
onTwoFactorRedirect?: (
|
|
27
|
+
onTwoFactorRedirect?: (context: {
|
|
28
|
+
/**
|
|
29
|
+
* The list of enabled two factor providers
|
|
30
|
+
* for the user (e.g. ["totp", "otp"]).
|
|
31
|
+
*/
|
|
32
|
+
twoFactorMethods?: string[];
|
|
33
|
+
}) => void | Promise<void>;
|
|
24
34
|
} | undefined) => {
|
|
25
35
|
id: "two-factor";
|
|
26
36
|
version: string;
|
|
@@ -48,8 +58,10 @@ declare const twoFactorClient: (options?: {
|
|
|
48
58
|
}[];
|
|
49
59
|
$ERROR_CODES: {
|
|
50
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">;
|
|
51
62
|
OTP_HAS_EXPIRED: _better_auth_core_utils_error_codes0.RawError<"OTP_HAS_EXPIRED">;
|
|
52
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">;
|
|
53
65
|
TWO_FACTOR_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TWO_FACTOR_NOT_ENABLED">;
|
|
54
66
|
BACKUP_CODES_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"BACKUP_CODES_NOT_ENABLED">;
|
|
55
67
|
INVALID_BACKUP_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_BACKUP_CODE">;
|
|
@@ -26,7 +26,7 @@ const twoFactorClient = (options) => {
|
|
|
26
26
|
hooks: { async onSuccess(context) {
|
|
27
27
|
if (context.data?.twoFactorRedirect) {
|
|
28
28
|
if (options?.onTwoFactorRedirect) {
|
|
29
|
-
await options.onTwoFactorRedirect();
|
|
29
|
+
await options.onTwoFactorRedirect({ twoFactorMethods: context.data.twoFactorMethods });
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
if (options?.twoFactorPage && typeof window !== "undefined") window.location.href = options.twoFactorPage;
|
|
@@ -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
|
}>;
|
|
@@ -618,6 +635,7 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
618
635
|
matcher(context: _better_auth_core0.HookEndpointContext): boolean;
|
|
619
636
|
handler: (inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
620
637
|
twoFactorRedirect: boolean;
|
|
638
|
+
twoFactorMethods: string[];
|
|
621
639
|
} | undefined>;
|
|
622
640
|
}[];
|
|
623
641
|
};
|
|
@@ -655,6 +673,12 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
655
673
|
};
|
|
656
674
|
index: true;
|
|
657
675
|
};
|
|
676
|
+
verified: {
|
|
677
|
+
type: "boolean";
|
|
678
|
+
required: false;
|
|
679
|
+
defaultValue: true;
|
|
680
|
+
input: false;
|
|
681
|
+
};
|
|
658
682
|
};
|
|
659
683
|
};
|
|
660
684
|
};
|
|
@@ -665,8 +689,10 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
|
|
|
665
689
|
}[];
|
|
666
690
|
$ERROR_CODES: {
|
|
667
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">;
|
|
668
693
|
OTP_HAS_EXPIRED: _better_auth_core_utils_error_codes0.RawError<"OTP_HAS_EXPIRED">;
|
|
669
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">;
|
|
670
696
|
TWO_FACTOR_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"TWO_FACTOR_NOT_ENABLED">;
|
|
671
697
|
BACKUP_CODES_NOT_ENABLED: _better_auth_core_utils_error_codes0.RawError<"BACKUP_CODES_NOT_ENABLED">;
|
|
672
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,35 +96,46 @@ 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
|
}
|
|
106
|
-
await ctx.context.
|
|
110
|
+
const backupCodes = await generateBackupCodes(ctx.context.secretConfig, backupCodeOptions);
|
|
111
|
+
const existingTwoFactor = await ctx.context.adapter.findOne({
|
|
107
112
|
model: opts.twoFactorTable,
|
|
108
113
|
where: [{
|
|
109
114
|
field: "userId",
|
|
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({
|
|
128
|
+
model: opts.twoFactorTable,
|
|
129
|
+
update: totpData,
|
|
130
|
+
where: [{
|
|
131
|
+
field: "id",
|
|
132
|
+
value: existingTwoFactor.id
|
|
133
|
+
}]
|
|
134
|
+
});
|
|
135
|
+
else await ctx.context.adapter.create({
|
|
114
136
|
model: opts.twoFactorTable,
|
|
115
137
|
data: {
|
|
116
|
-
|
|
117
|
-
backupCodes: backupCodes.encryptedBackupCodes,
|
|
138
|
+
...totpData,
|
|
118
139
|
userId: user.id
|
|
119
140
|
}
|
|
120
141
|
});
|
|
@@ -123,6 +144,7 @@ const twoFactor = (options) => {
|
|
|
123
144
|
period: options?.totpOptions?.period
|
|
124
145
|
}).url(issuer || options?.issuer || ctx.context.appName, user.email);
|
|
125
146
|
return ctx.json({
|
|
147
|
+
method: "totp",
|
|
126
148
|
totpURI,
|
|
127
149
|
backupCodes: backupCodes.backupCodes
|
|
128
150
|
});
|
|
@@ -225,7 +247,30 @@ const twoFactor = (options) => {
|
|
|
225
247
|
expiresAt: new Date(Date.now() + maxAge * 1e3)
|
|
226
248
|
});
|
|
227
249
|
await ctx.setSignedCookie(twoFactorCookie.name, identifier, ctx.context.secret, twoFactorCookie.attributes);
|
|
228
|
-
|
|
250
|
+
const twoFactorMethods = [];
|
|
251
|
+
/**
|
|
252
|
+
* totp requires per-user setup, so we check
|
|
253
|
+
* that the user actually has a secret stored.
|
|
254
|
+
*/
|
|
255
|
+
if (!options?.totpOptions?.disable) {
|
|
256
|
+
const userTotpSecret = await ctx.context.adapter.findOne({
|
|
257
|
+
model: opts.twoFactorTable,
|
|
258
|
+
where: [{
|
|
259
|
+
field: "userId",
|
|
260
|
+
value: data.user.id
|
|
261
|
+
}]
|
|
262
|
+
});
|
|
263
|
+
if (userTotpSecret && userTotpSecret.verified !== false) twoFactorMethods.push("totp");
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* otp is server-level — if sendOTP is configured,
|
|
267
|
+
* any user with 2fa enabled can receive a code.
|
|
268
|
+
*/
|
|
269
|
+
if (options?.otpOptions?.sendOTP) twoFactorMethods.push("otp");
|
|
270
|
+
return ctx.json({
|
|
271
|
+
twoFactorRedirect: true,
|
|
272
|
+
twoFactorMethods
|
|
273
|
+
});
|
|
229
274
|
})
|
|
230
275
|
}] },
|
|
231
276
|
schema: mergeSchema(schema, {
|
|
@@ -175,11 +175,11 @@ const otp2fa = (options) => {
|
|
|
175
175
|
if (!session.session) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION);
|
|
176
176
|
const updatedUser = await ctx.context.internalAdapter.updateUser(session.user.id, { twoFactorEnabled: true });
|
|
177
177
|
const newSession = await ctx.context.internalAdapter.createSession(session.user.id, false, session.session);
|
|
178
|
-
await ctx.context.internalAdapter.deleteSession(session.session.token);
|
|
179
178
|
await setSessionCookie(ctx, {
|
|
180
179
|
session: newSession,
|
|
181
180
|
user: updatedUser
|
|
182
181
|
});
|
|
182
|
+
await ctx.context.internalAdapter.deleteSession(session.session.token);
|
|
183
183
|
return ctx.json({
|
|
184
184
|
token: newSession.token,
|
|
185
185
|
user: parseUserOutput(ctx.context.options, updatedUser)
|
|
@@ -124,6 +124,7 @@ const totp2fa = (options) => {
|
|
|
124
124
|
}
|
|
125
125
|
const { session, valid, invalid } = await verifyTwoFactor(ctx);
|
|
126
126
|
const user = session.user;
|
|
127
|
+
const isSignIn = !session.session;
|
|
127
128
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
128
129
|
model: twoFactorTable,
|
|
129
130
|
where: [{
|
|
@@ -132,6 +133,7 @@ const totp2fa = (options) => {
|
|
|
132
133
|
}]
|
|
133
134
|
});
|
|
134
135
|
if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED);
|
|
136
|
+
if (isSignIn && twoFactor.verified === false) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED);
|
|
135
137
|
if (!await createOTP(await symmetricDecrypt({
|
|
136
138
|
key: ctx.context.secretConfig,
|
|
137
139
|
data: twoFactor.secret
|
|
@@ -139,16 +141,23 @@ const totp2fa = (options) => {
|
|
|
139
141
|
period: opts.period,
|
|
140
142
|
digits: opts.digits
|
|
141
143
|
}).verify(ctx.body.code)) return invalid("INVALID_CODE");
|
|
142
|
-
if (
|
|
143
|
-
if (!
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
if (twoFactor.verified !== true) {
|
|
145
|
+
if (!user.twoFactorEnabled) {
|
|
146
|
+
const activeSession = session.session;
|
|
147
|
+
const updatedUser = await ctx.context.internalAdapter.updateUser(user.id, { twoFactorEnabled: true });
|
|
148
|
+
await setSessionCookie(ctx, {
|
|
149
|
+
session: await ctx.context.internalAdapter.createSession(user.id, false, activeSession),
|
|
150
|
+
user: updatedUser
|
|
151
|
+
});
|
|
152
|
+
await ctx.context.internalAdapter.deleteSession(activeSession.token);
|
|
153
|
+
}
|
|
154
|
+
await ctx.context.adapter.update({
|
|
155
|
+
model: twoFactorTable,
|
|
156
|
+
update: { verified: true },
|
|
157
|
+
where: [{
|
|
158
|
+
field: "id",
|
|
159
|
+
value: twoFactor.id
|
|
160
|
+
}]
|
|
152
161
|
});
|
|
153
162
|
}
|
|
154
163
|
return valid(ctx);
|
|
@@ -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).
|
|
@@ -80,7 +75,7 @@ interface TwoFactorTable {
|
|
|
80
75
|
userId: string;
|
|
81
76
|
secret: string;
|
|
82
77
|
backupCodes: string;
|
|
83
|
-
|
|
78
|
+
verified: boolean;
|
|
84
79
|
}
|
|
85
80
|
//#endregion
|
|
86
81
|
export { TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor };
|
package/dist/state.d.mts
CHANGED
|
@@ -9,6 +9,11 @@ declare const stateDataSchema: z.ZodObject<{
|
|
|
9
9
|
errorURL: z.ZodOptional<z.ZodString>;
|
|
10
10
|
newUserURL: z.ZodOptional<z.ZodString>;
|
|
11
11
|
expiresAt: z.ZodNumber;
|
|
12
|
+
/**
|
|
13
|
+
* CSRF nonce returned to the OAuth provider. When using cookie state storage,
|
|
14
|
+
* this must match the callback `state` query parameter.
|
|
15
|
+
*/
|
|
16
|
+
oauthState: z.ZodOptional<z.ZodString>;
|
|
12
17
|
link: z.ZodOptional<z.ZodObject<{
|
|
13
18
|
email: z.ZodString;
|
|
14
19
|
userId: z.ZodCoercedString<unknown>;
|
|
@@ -32,6 +37,7 @@ declare function parseGenericState(c: GenericEndpointContext, state: string, set
|
|
|
32
37
|
expiresAt: number;
|
|
33
38
|
errorURL?: string | undefined;
|
|
34
39
|
newUserURL?: string | undefined;
|
|
40
|
+
oauthState?: string | undefined;
|
|
35
41
|
link?: {
|
|
36
42
|
email: string;
|
|
37
43
|
userId: string;
|
package/dist/state.mjs
CHANGED
|
@@ -10,6 +10,7 @@ const stateDataSchema = z.looseObject({
|
|
|
10
10
|
errorURL: z.string().optional(),
|
|
11
11
|
newUserURL: z.string().optional(),
|
|
12
12
|
expiresAt: z.number(),
|
|
13
|
+
oauthState: z.string().optional(),
|
|
13
14
|
link: z.object({
|
|
14
15
|
email: z.string(),
|
|
15
16
|
userId: z.coerce.string()
|
|
@@ -28,9 +29,13 @@ var StateError = class extends BetterAuthError {
|
|
|
28
29
|
async function generateGenericState(c, stateData, settings) {
|
|
29
30
|
const state = generateRandomString(32);
|
|
30
31
|
if (c.context.oauthConfig.storeStateStrategy === "cookie") {
|
|
32
|
+
const payload = {
|
|
33
|
+
...stateData,
|
|
34
|
+
oauthState: state
|
|
35
|
+
};
|
|
31
36
|
const encryptedData = await symmetricEncrypt({
|
|
32
37
|
key: c.context.secretConfig,
|
|
33
|
-
data: JSON.stringify(
|
|
38
|
+
data: JSON.stringify(payload)
|
|
34
39
|
});
|
|
35
40
|
const stateCookie = c.context.createAuthCookie(settings?.cookieName ?? "oauth_state", { maxAge: 600 });
|
|
36
41
|
c.setCookie(stateCookie.name, encryptedData, stateCookie.attributes);
|
|
@@ -44,7 +49,10 @@ async function generateGenericState(c, stateData, settings) {
|
|
|
44
49
|
const expiresAt = /* @__PURE__ */ new Date();
|
|
45
50
|
expiresAt.setMinutes(expiresAt.getMinutes() + 10);
|
|
46
51
|
if (!await c.context.internalAdapter.createVerificationValue({
|
|
47
|
-
value: JSON.stringify(
|
|
52
|
+
value: JSON.stringify({
|
|
53
|
+
...stateData,
|
|
54
|
+
oauthState: state
|
|
55
|
+
}),
|
|
48
56
|
identifier: state,
|
|
49
57
|
expiresAt
|
|
50
58
|
})) throw new StateError("Unable to create verification. Make sure the database adapter is properly working and there is a verification table in the database", { code: "state_generation_error" });
|
|
@@ -76,6 +84,10 @@ async function parseGenericState(c, state, settings) {
|
|
|
76
84
|
cause: error
|
|
77
85
|
});
|
|
78
86
|
}
|
|
87
|
+
if (!parsedData.oauthState || parsedData.oauthState !== state) throw new StateError("State mismatch: OAuth state parameter does not match stored state", {
|
|
88
|
+
code: "state_security_mismatch",
|
|
89
|
+
details: { state }
|
|
90
|
+
});
|
|
79
91
|
expireCookie(c, stateCookie);
|
|
80
92
|
} else {
|
|
81
93
|
const data = await c.context.internalAdapter.findVerificationValue(state);
|
|
@@ -84,6 +96,10 @@ async function parseGenericState(c, state, settings) {
|
|
|
84
96
|
details: { state }
|
|
85
97
|
});
|
|
86
98
|
parsedData = stateDataSchema.parse(JSON.parse(data.value));
|
|
99
|
+
if (parsedData.oauthState !== void 0 && parsedData.oauthState !== state) throw new StateError("State mismatch: OAuth state parameter does not match stored state", {
|
|
100
|
+
code: "state_security_mismatch",
|
|
101
|
+
details: { state }
|
|
102
|
+
});
|
|
87
103
|
const stateCookie = c.context.createAuthCookie(settings?.cookieName ?? "state");
|
|
88
104
|
const stateCookieValue = await c.getSignedCookie(stateCookie.name, c.context.secret);
|
|
89
105
|
if (!(settings?.skipStateCookieCheck ?? c.context.oauthConfig.skipStateCookieCheck) && (!stateCookieValue || stateCookieValue !== state)) throw new StateError("State mismatch: State not persisted correctly", {
|
|
@@ -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",
|