better-auth 1.6.11 → 1.6.12
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 +8 -2
- package/dist/api/routes/callback.d.mts +1 -1
- package/dist/api/routes/callback.mjs +36 -40
- package/dist/api/routes/email-verification.d.mts +1 -0
- package/dist/api/routes/email-verification.mjs +4 -3
- package/dist/api/routes/session.mjs +14 -9
- package/dist/api/routes/sign-in.d.mts +1 -0
- package/dist/api/routes/sign-in.mjs +2 -1
- package/dist/api/routes/sign-up.d.mts +1 -0
- package/dist/api/routes/sign-up.mjs +9 -7
- package/dist/api/routes/update-user.mjs +4 -4
- package/dist/client/parser.mjs +0 -1
- package/dist/client/plugins/index.d.mts +3 -3
- package/dist/client/proxy.mjs +2 -1
- package/dist/context/helpers.mjs +3 -2
- package/dist/cookies/cookie-utils.d.mts +24 -1
- package/dist/cookies/cookie-utils.mjs +85 -22
- package/dist/cookies/index.d.mts +2 -3
- package/dist/cookies/index.mjs +39 -11
- package/dist/cookies/session-store.mjs +4 -23
- package/dist/db/get-migration.mjs +4 -4
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/internal-adapter.mjs +37 -30
- package/dist/db/schema.d.mts +15 -2
- package/dist/db/schema.mjs +26 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/oauth2/errors.mjs +16 -1
- package/dist/oauth2/link-account.mjs +3 -3
- package/dist/oauth2/state.mjs +8 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/access/access.mjs +11 -6
- package/dist/plugins/admin/admin.mjs +0 -4
- package/dist/plugins/admin/client.d.mts +1 -1
- package/dist/plugins/bearer/index.mjs +4 -9
- package/dist/plugins/captcha/index.mjs +2 -2
- package/dist/plugins/generic-oauth/index.d.mts +1 -1
- package/dist/plugins/generic-oauth/index.mjs +6 -6
- package/dist/plugins/generic-oauth/routes.mjs +34 -32
- package/dist/plugins/generic-oauth/types.d.mts +7 -0
- package/dist/plugins/last-login-method/client.mjs +2 -2
- package/dist/plugins/magic-link/index.mjs +0 -1
- package/dist/plugins/mcp/index.mjs +0 -4
- package/dist/plugins/multi-session/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/index.mjs +44 -31
- package/dist/plugins/oauth-proxy/utils.mjs +3 -10
- package/dist/plugins/oidc-provider/index.mjs +0 -4
- package/dist/plugins/open-api/generator.mjs +16 -5
- package/dist/plugins/organization/adapter.mjs +61 -56
- package/dist/plugins/organization/client.d.mts +2 -1
- package/dist/plugins/organization/error-codes.d.mts +1 -0
- package/dist/plugins/organization/error-codes.mjs +2 -1
- package/dist/plugins/organization/routes/crud-invites.mjs +3 -0
- package/dist/plugins/two-factor/index.mjs +3 -2
- package/dist/plugins/username/index.d.mts +24 -2
- package/dist/plugins/username/index.mjs +49 -3
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +18 -4
- package/dist/test-utils/headers.mjs +2 -7
- package/dist/test-utils/test-instance.d.mts +24 -6
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/url.d.mts +2 -1
- package/dist/utils/url.mjs +9 -3
- package/package.json +15 -14
package/dist/api/index.d.mts
CHANGED
|
@@ -228,7 +228,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
228
228
|
allowedMediaTypes: string[];
|
|
229
229
|
scope: "server";
|
|
230
230
|
};
|
|
231
|
-
},
|
|
231
|
+
}, never>;
|
|
232
232
|
readonly getSession: better_call0.StrictEndpoint<"/get-session", {
|
|
233
233
|
method: ("GET" | "POST")[];
|
|
234
234
|
operationId: string;
|
|
@@ -327,6 +327,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
327
327
|
callbackURL: zod.ZodOptional<zod.ZodString>;
|
|
328
328
|
rememberMe: zod.ZodOptional<zod.ZodBoolean>;
|
|
329
329
|
}, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
|
|
330
|
+
cloneRequest: true;
|
|
330
331
|
metadata: {
|
|
331
332
|
allowedMediaTypes: string[];
|
|
332
333
|
$Infer: {
|
|
@@ -493,6 +494,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
493
494
|
method: "POST";
|
|
494
495
|
operationId: string;
|
|
495
496
|
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
|
|
497
|
+
cloneRequest: true;
|
|
496
498
|
body: zod.ZodObject<{
|
|
497
499
|
email: zod.ZodString;
|
|
498
500
|
password: zod.ZodString;
|
|
@@ -724,6 +726,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
724
726
|
readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
|
|
725
727
|
method: "POST";
|
|
726
728
|
operationId: string;
|
|
729
|
+
cloneRequest: true;
|
|
727
730
|
body: zod.ZodObject<{
|
|
728
731
|
email: zod.ZodEmail;
|
|
729
732
|
callbackURL: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2216,7 +2219,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2216
2219
|
allowedMediaTypes: string[];
|
|
2217
2220
|
scope: "server";
|
|
2218
2221
|
};
|
|
2219
|
-
},
|
|
2222
|
+
}, never>;
|
|
2220
2223
|
readonly getSession: better_call0.StrictEndpoint<"/get-session", {
|
|
2221
2224
|
method: ("GET" | "POST")[];
|
|
2222
2225
|
operationId: string;
|
|
@@ -2315,6 +2318,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2315
2318
|
callbackURL: zod.ZodOptional<zod.ZodString>;
|
|
2316
2319
|
rememberMe: zod.ZodOptional<zod.ZodBoolean>;
|
|
2317
2320
|
}, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
|
|
2321
|
+
cloneRequest: true;
|
|
2318
2322
|
metadata: {
|
|
2319
2323
|
allowedMediaTypes: string[];
|
|
2320
2324
|
$Infer: {
|
|
@@ -2481,6 +2485,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2481
2485
|
method: "POST";
|
|
2482
2486
|
operationId: string;
|
|
2483
2487
|
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
|
|
2488
|
+
cloneRequest: true;
|
|
2484
2489
|
body: zod.ZodObject<{
|
|
2485
2490
|
email: zod.ZodString;
|
|
2486
2491
|
password: zod.ZodString;
|
|
@@ -2712,6 +2717,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2712
2717
|
readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
|
|
2713
2718
|
method: "POST";
|
|
2714
2719
|
operationId: string;
|
|
2720
|
+
cloneRequest: true;
|
|
2715
2721
|
body: zod.ZodObject<{
|
|
2716
2722
|
email: zod.ZodEmail;
|
|
2717
2723
|
callbackURL: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isAPIError } from "../../utils/is-api-error.mjs";
|
|
1
2
|
import { setSessionCookie } from "../../cookies/index.mjs";
|
|
2
3
|
import { getAwaitableValue } from "../../context/helpers.mjs";
|
|
3
|
-
import { missingEmailLogMessage } from "../../oauth2/errors.mjs";
|
|
4
|
+
import { missingEmailLogMessage, redirectOnError } from "../../oauth2/errors.mjs";
|
|
4
5
|
import { parseState } from "../../oauth2/state.mjs";
|
|
5
6
|
import { setTokenUtil } from "../../oauth2/utils.mjs";
|
|
6
7
|
import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
|
|
@@ -47,31 +48,20 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
47
48
|
else throw new Error("Unsupported method");
|
|
48
49
|
} catch (e) {
|
|
49
50
|
c.context.logger.error("INVALID_CALLBACK_REQUEST", e);
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
const { code, error, state, error_description, device_id, user: userData } = queryOrBody;
|
|
53
|
-
if (!state) {
|
|
54
|
-
c.context.logger.error("State not found", error);
|
|
55
|
-
const url = `${defaultErrorURL}${defaultErrorURL.includes("?") ? "&" : "?"}state=state_not_found`;
|
|
56
|
-
throw c.redirect(url);
|
|
51
|
+
redirectOnError(c, defaultErrorURL, "invalid_callback_request");
|
|
57
52
|
}
|
|
53
|
+
const { code, error, error_description, device_id, user: userData } = queryOrBody;
|
|
58
54
|
const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp } = await parseState(c);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const params = new URLSearchParams({ error });
|
|
62
|
-
if (description) params.set("error_description", description);
|
|
63
|
-
const url = `${baseURL}${baseURL.includes("?") ? "&" : "?"}${params.toString()}`;
|
|
64
|
-
throw c.redirect(url);
|
|
65
|
-
}
|
|
66
|
-
if (error) redirectOnError(error, error_description);
|
|
55
|
+
const resolvedErrorURL = errorURL ?? defaultErrorURL;
|
|
56
|
+
if (error) redirectOnError(c, resolvedErrorURL, error, error_description);
|
|
67
57
|
if (!code) {
|
|
68
58
|
c.context.logger.error("Code not found");
|
|
69
|
-
|
|
59
|
+
redirectOnError(c, resolvedErrorURL, "no_code");
|
|
70
60
|
}
|
|
71
61
|
const provider = await getAwaitableValue(c.context.socialProviders, { value: c.params.id });
|
|
72
62
|
if (!provider) {
|
|
73
63
|
c.context.logger.error("Oauth provider with id", c.params.id, "not found");
|
|
74
|
-
|
|
64
|
+
redirectOnError(c, resolvedErrorURL, "oauth_provider_not_found");
|
|
75
65
|
}
|
|
76
66
|
let tokens;
|
|
77
67
|
try {
|
|
@@ -83,9 +73,9 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
83
73
|
});
|
|
84
74
|
} catch (e) {
|
|
85
75
|
c.context.logger.error("", e);
|
|
86
|
-
|
|
76
|
+
redirectOnError(c, resolvedErrorURL, "invalid_code");
|
|
87
77
|
}
|
|
88
|
-
if (!tokens)
|
|
78
|
+
if (!tokens) redirectOnError(c, resolvedErrorURL, "invalid_code");
|
|
89
79
|
const parsedUserData = userData ? safeJSONParse(userData) : null;
|
|
90
80
|
const userInfo = await provider.getUserInfo({
|
|
91
81
|
...tokens,
|
|
@@ -93,22 +83,22 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
93
83
|
}).then((res) => res?.user);
|
|
94
84
|
if (!userInfo || userInfo.id === void 0 || userInfo.id === null) {
|
|
95
85
|
c.context.logger.error("Unable to get user info");
|
|
96
|
-
|
|
86
|
+
redirectOnError(c, resolvedErrorURL, "unable_to_get_user_info");
|
|
97
87
|
}
|
|
98
88
|
const providerAccountId = String(userInfo.id);
|
|
99
89
|
if (!callbackURL) {
|
|
100
90
|
c.context.logger.error("No callback URL found");
|
|
101
|
-
|
|
91
|
+
redirectOnError(c, resolvedErrorURL, "no_callback_url");
|
|
102
92
|
}
|
|
103
93
|
if (link) {
|
|
104
94
|
if (!c.context.trustedProviders.includes(provider.id) && !userInfo.emailVerified || c.context.options.account?.accountLinking?.enabled === false) {
|
|
105
95
|
c.context.logger.error("Unable to link account - untrusted provider");
|
|
106
|
-
|
|
96
|
+
redirectOnError(c, resolvedErrorURL, "unable_to_link_account");
|
|
107
97
|
}
|
|
108
|
-
if (userInfo.email?.toLowerCase() !== link.email.toLowerCase() && c.context.options.account?.accountLinking?.allowDifferentEmails !== true)
|
|
98
|
+
if (userInfo.email?.toLowerCase() !== link.email.toLowerCase() && c.context.options.account?.accountLinking?.allowDifferentEmails !== true) redirectOnError(c, resolvedErrorURL, "email_doesn't_match");
|
|
109
99
|
const existingAccount = await c.context.internalAdapter.findAccountByProviderId(providerAccountId, provider.id);
|
|
110
100
|
if (existingAccount) {
|
|
111
|
-
if (existingAccount.userId.toString() !== link.userId.toString())
|
|
101
|
+
if (existingAccount.userId.toString() !== link.userId.toString()) redirectOnError(c, resolvedErrorURL, "account_already_linked_to_different_user");
|
|
112
102
|
const updateData = Object.fromEntries(Object.entries({
|
|
113
103
|
accessToken: await setTokenUtil(tokens.accessToken, c.context),
|
|
114
104
|
refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
|
|
@@ -126,7 +116,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
126
116
|
accessToken: await setTokenUtil(tokens.accessToken, c.context),
|
|
127
117
|
refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
|
|
128
118
|
scope: tokens.scopes?.join(",")
|
|
129
|
-
}))
|
|
119
|
+
})) redirectOnError(c, resolvedErrorURL, "unable_to_link_account");
|
|
130
120
|
let toRedirectTo;
|
|
131
121
|
try {
|
|
132
122
|
toRedirectTo = callbackURL.toString();
|
|
@@ -137,7 +127,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
137
127
|
}
|
|
138
128
|
if (!userInfo.email) {
|
|
139
129
|
c.context.logger.error(missingEmailLogMessage(provider.id));
|
|
140
|
-
|
|
130
|
+
redirectOnError(c, resolvedErrorURL, "email_not_found");
|
|
141
131
|
}
|
|
142
132
|
const accountData = {
|
|
143
133
|
providerId: provider.id,
|
|
@@ -145,21 +135,27 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
145
135
|
...tokens,
|
|
146
136
|
scope: tokens.scopes?.join(",")
|
|
147
137
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
138
|
+
let result;
|
|
139
|
+
try {
|
|
140
|
+
result = await handleOAuthUserInfo(c, {
|
|
141
|
+
userInfo: {
|
|
142
|
+
...userInfo,
|
|
143
|
+
id: providerAccountId,
|
|
144
|
+
email: userInfo.email,
|
|
145
|
+
name: userInfo.name || ""
|
|
146
|
+
},
|
|
147
|
+
account: accountData,
|
|
148
|
+
callbackURL,
|
|
149
|
+
disableSignUp: provider.disableImplicitSignUp && !requestSignUp || provider.options?.disableSignUp,
|
|
150
|
+
overrideUserInfo: provider.options?.overrideUserInfoOnSignIn
|
|
151
|
+
});
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (isAPIError(e) && e.body?.code) redirectOnError(c, resolvedErrorURL, e.body.code, e.body.message);
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
160
156
|
if (result.error) {
|
|
161
157
|
c.context.logger.error(result.error.split(" ").join("_"));
|
|
162
|
-
|
|
158
|
+
redirectOnError(c, resolvedErrorURL, result.error.split(" ").join("_"));
|
|
163
159
|
}
|
|
164
160
|
const { session, user } = result.data;
|
|
165
161
|
await setSessionCookie(c, {
|
|
@@ -27,6 +27,7 @@ declare function sendVerificationEmailFn(ctx: GenericEndpointContext, user: User
|
|
|
27
27
|
declare const sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
|
|
28
28
|
method: "POST";
|
|
29
29
|
operationId: string;
|
|
30
|
+
cloneRequest: true;
|
|
30
31
|
body: z.ZodObject<{
|
|
31
32
|
email: z.ZodEmail;
|
|
32
33
|
callbackURL: z.ZodOptional<z.ZodString>;
|
|
@@ -31,11 +31,12 @@ async function sendVerificationEmailFn(ctx, user) {
|
|
|
31
31
|
user,
|
|
32
32
|
url,
|
|
33
33
|
token
|
|
34
|
-
}, ctx.request));
|
|
34
|
+
}, ctx.request?.clone()));
|
|
35
35
|
}
|
|
36
36
|
const sendVerificationEmail = createAuthEndpoint("/send-verification-email", {
|
|
37
37
|
method: "POST",
|
|
38
38
|
operationId: "sendVerificationEmail",
|
|
39
|
+
cloneRequest: true,
|
|
39
40
|
body: z.object({
|
|
40
41
|
email: z.email().meta({ description: "The email to send the verification email to" }),
|
|
41
42
|
callbackURL: z.string().meta({ description: "The URL to use for email verification callback" }).optional()
|
|
@@ -185,7 +186,7 @@ const verifyEmail = createAuthEndpoint("/verify-email", {
|
|
|
185
186
|
},
|
|
186
187
|
url,
|
|
187
188
|
token: newToken
|
|
188
|
-
}, ctx.request));
|
|
189
|
+
}, ctx.request?.clone()));
|
|
189
190
|
if (ctx.query.callbackURL) throw ctx.redirect(ctx.query.callbackURL);
|
|
190
191
|
return ctx.json({ status: true });
|
|
191
192
|
}
|
|
@@ -238,7 +239,7 @@ const verifyEmail = createAuthEndpoint("/verify-email", {
|
|
|
238
239
|
user: updatedUser,
|
|
239
240
|
url: `${ctx.context.baseURL}/verify-email?token=${newToken}&callbackURL=${updateCallbackURL}`,
|
|
240
241
|
token: newToken
|
|
241
|
-
}, ctx.request));
|
|
242
|
+
}, ctx.request?.clone()));
|
|
242
243
|
await setSessionCookie(ctx, {
|
|
243
244
|
session: activeSession.session,
|
|
244
245
|
user: {
|
|
@@ -129,12 +129,8 @@ const getSession = () => createAuthEndpoint("/get-session", {
|
|
|
129
129
|
const updateAge = cookieRefreshCache.updateAge * 1e3;
|
|
130
130
|
const shouldSkipSessionRefresh = await getShouldSkipSessionRefresh();
|
|
131
131
|
if (timeUntilExpiry < updateAge && !shouldSkipSessionRefresh) {
|
|
132
|
-
const newExpiresAt = getDate(ctx.context.options.session?.cookieCache?.maxAge || 300, "sec");
|
|
133
132
|
const refreshedSession = {
|
|
134
|
-
session: {
|
|
135
|
-
...session.session,
|
|
136
|
-
expiresAt: newExpiresAt
|
|
137
|
-
},
|
|
133
|
+
session: { ...session.session },
|
|
138
134
|
user: session.user,
|
|
139
135
|
updatedAt: Date.now()
|
|
140
136
|
};
|
|
@@ -276,17 +272,26 @@ const getSessionFromCtx = async (ctx, config) => {
|
|
|
276
272
|
method: "GET",
|
|
277
273
|
asResponse: false,
|
|
278
274
|
headers: ctx.headers,
|
|
279
|
-
returnHeaders:
|
|
275
|
+
returnHeaders: true,
|
|
280
276
|
returnStatus: false,
|
|
281
277
|
query: {
|
|
282
278
|
...config,
|
|
283
279
|
...ctx.query
|
|
284
280
|
}
|
|
285
|
-
}).catch((
|
|
281
|
+
}).catch(() => {
|
|
286
282
|
return null;
|
|
287
283
|
});
|
|
288
|
-
|
|
289
|
-
|
|
284
|
+
if (!session) {
|
|
285
|
+
ctx.context.session = null;
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
if (session.headers) session.headers.forEach((value, key) => {
|
|
289
|
+
if (!ctx.context.responseHeaders) ctx.context.responseHeaders = new Headers({ [key]: value });
|
|
290
|
+
else if (key.toLowerCase() === "set-cookie") ctx.context.responseHeaders.append(key, value);
|
|
291
|
+
else ctx.context.responseHeaders.set(key, value);
|
|
292
|
+
});
|
|
293
|
+
ctx.context.session = session.response;
|
|
294
|
+
return session.response;
|
|
290
295
|
};
|
|
291
296
|
/**
|
|
292
297
|
* The middleware forces the endpoint to require a valid session.
|
|
@@ -114,6 +114,7 @@ declare const signInEmail: <O extends BetterAuthOptions>() => better_call0.Stric
|
|
|
114
114
|
method: "POST";
|
|
115
115
|
operationId: string;
|
|
116
116
|
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
|
|
117
|
+
cloneRequest: true;
|
|
117
118
|
body: z.ZodObject<{
|
|
118
119
|
email: z.ZodString;
|
|
119
120
|
password: z.ZodString;
|
|
@@ -144,6 +144,7 @@ const signInEmail = () => createAuthEndpoint("/sign-in/email", {
|
|
|
144
144
|
method: "POST",
|
|
145
145
|
operationId: "signInEmail",
|
|
146
146
|
use: [formCsrfMiddleware],
|
|
147
|
+
cloneRequest: true,
|
|
147
148
|
body: z.object({
|
|
148
149
|
email: z.string().meta({ description: "Email of the user" }),
|
|
149
150
|
password: z.string().meta({ description: "Password of the user" }),
|
|
@@ -236,7 +237,7 @@ const signInEmail = () => createAuthEndpoint("/sign-in/email", {
|
|
|
236
237
|
user: user.user,
|
|
237
238
|
url,
|
|
238
239
|
token
|
|
239
|
-
}, ctx.request));
|
|
240
|
+
}, ctx.request?.clone()));
|
|
240
241
|
}
|
|
241
242
|
throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.EMAIL_NOT_VERIFIED);
|
|
242
243
|
}
|
|
@@ -16,6 +16,7 @@ declare const signUpEmail: <O extends BetterAuthOptions>() => better_call0.Stric
|
|
|
16
16
|
callbackURL: z.ZodOptional<z.ZodString>;
|
|
17
17
|
rememberMe: z.ZodOptional<z.ZodBoolean>;
|
|
18
18
|
}, z.core.$strip>, z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
19
|
+
cloneRequest: true;
|
|
19
20
|
metadata: {
|
|
20
21
|
allowedMediaTypes: string[];
|
|
21
22
|
$Infer: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isAPIError } from "../../utils/is-api-error.mjs";
|
|
2
2
|
import { formCsrfMiddleware } from "../middlewares/origin-check.mjs";
|
|
3
|
-
import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
|
|
3
|
+
import { buildSyntheticUserOutput, parseUserInput, parseUserOutput } from "../../db/schema.mjs";
|
|
4
4
|
import { setSessionCookie } from "../../cookies/index.mjs";
|
|
5
5
|
import { createEmailVerificationToken } from "./email-verification.mjs";
|
|
6
6
|
import { runWithTransaction } from "@better-auth/core/context";
|
|
@@ -23,6 +23,7 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
|
|
|
23
23
|
operationId: "signUpWithEmailAndPassword",
|
|
24
24
|
use: [formCsrfMiddleware],
|
|
25
25
|
body: signUpEmailBodySchema,
|
|
26
|
+
cloneRequest: true,
|
|
26
27
|
metadata: {
|
|
27
28
|
allowedMediaTypes: ["application/x-www-form-urlencoded", "application/json"],
|
|
28
29
|
$Infer: {
|
|
@@ -170,14 +171,14 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
|
|
|
170
171
|
* between existing and non-existing emails.
|
|
171
172
|
*/
|
|
172
173
|
await ctx.context.password.hash(password);
|
|
173
|
-
if (ctx.context.options.emailAndPassword?.onExistingUserSignUp) await ctx.context.runInBackgroundOrAwait(ctx.context.options.emailAndPassword.onExistingUserSignUp({ user: dbUser.user }, ctx.request));
|
|
174
|
+
if (ctx.context.options.emailAndPassword?.onExistingUserSignUp) await ctx.context.runInBackgroundOrAwait(ctx.context.options.emailAndPassword.onExistingUserSignUp({ user: dbUser.user }, ctx.request?.clone()));
|
|
174
175
|
const now = /* @__PURE__ */ new Date();
|
|
175
176
|
const generatedId = ctx.context.generateId({ model: "user" }) || generateId();
|
|
176
177
|
const coreFields = {
|
|
177
178
|
name,
|
|
178
179
|
email: normalizedEmail,
|
|
179
180
|
emailVerified: false,
|
|
180
|
-
image: image
|
|
181
|
+
image: image ?? null,
|
|
181
182
|
createdAt: now,
|
|
182
183
|
updatedAt: now
|
|
183
184
|
};
|
|
@@ -187,16 +188,17 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
|
|
|
187
188
|
const additionalFieldKeys = Object.keys(ctx.context.options.user?.additionalFields ?? {});
|
|
188
189
|
const additionalFields = {};
|
|
189
190
|
for (const key of additionalFieldKeys) if (key in additionalUserFields) additionalFields[key] = additionalUserFields[key];
|
|
190
|
-
|
|
191
|
+
const customResult = customSyntheticUser({
|
|
191
192
|
coreFields,
|
|
192
193
|
additionalFields,
|
|
193
194
|
id: generatedId
|
|
194
195
|
});
|
|
195
|
-
|
|
196
|
+
syntheticUser = buildSyntheticUserOutput(ctx.context.options, customResult);
|
|
197
|
+
} else syntheticUser = buildSyntheticUserOutput(ctx.context.options, {
|
|
196
198
|
...coreFields,
|
|
197
199
|
...additionalUserFields,
|
|
198
200
|
id: generatedId
|
|
199
|
-
};
|
|
201
|
+
});
|
|
200
202
|
return ctx.json({
|
|
201
203
|
token: null,
|
|
202
204
|
user: parseUserOutput(ctx.context.options, syntheticUser)
|
|
@@ -244,7 +246,7 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
|
|
|
244
246
|
user: createdUser,
|
|
245
247
|
url,
|
|
246
248
|
token
|
|
247
|
-
}, ctx.request));
|
|
249
|
+
}, ctx.request?.clone()));
|
|
248
250
|
}
|
|
249
251
|
if (shouldSkipAutoSignIn) return ctx.json({
|
|
250
252
|
token: null,
|
|
@@ -424,8 +424,8 @@ const changeEmail = createAuthEndpoint("/change-email", {
|
|
|
424
424
|
* email would later throw 400, leaking email existence.
|
|
425
425
|
*/
|
|
426
426
|
const canUpdateWithoutVerification = ctx.context.session.user.emailVerified !== true && ctx.context.options.user.changeEmail.updateEmailWithoutVerification;
|
|
427
|
-
const canSendConfirmation = ctx.context.session.user.emailVerified && ctx.context.options.user.changeEmail.sendChangeEmailConfirmation;
|
|
428
427
|
const canSendVerification = ctx.context.options.emailVerification?.sendVerificationEmail;
|
|
428
|
+
const canSendConfirmation = canSendVerification && ctx.context.session.user.emailVerified && ctx.context.options.user.changeEmail.sendChangeEmailConfirmation;
|
|
429
429
|
if (!canUpdateWithoutVerification && !canSendConfirmation && !canSendVerification) {
|
|
430
430
|
ctx.context.logger.error("Verification email isn't enabled.");
|
|
431
431
|
throw APIError.fromStatus("BAD_REQUEST", { message: "Verification email isn't enabled" });
|
|
@@ -449,7 +449,7 @@ const changeEmail = createAuthEndpoint("/change-email", {
|
|
|
449
449
|
});
|
|
450
450
|
if (canSendVerification) {
|
|
451
451
|
const token = await createEmailVerificationToken(ctx.context.secret, newEmail, void 0, ctx.context.options.emailVerification?.expiresIn);
|
|
452
|
-
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || "/"}`;
|
|
452
|
+
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(ctx.body.callbackURL || "/")}`;
|
|
453
453
|
await ctx.context.runInBackgroundOrAwait(canSendVerification({
|
|
454
454
|
user: {
|
|
455
455
|
...ctx.context.session.user,
|
|
@@ -466,7 +466,7 @@ const changeEmail = createAuthEndpoint("/change-email", {
|
|
|
466
466
|
*/
|
|
467
467
|
if (canSendConfirmation) {
|
|
468
468
|
const token = await createEmailVerificationToken(ctx.context.secret, ctx.context.session.user.email, newEmail, ctx.context.options.emailVerification?.expiresIn, { requestType: "change-email-confirmation" });
|
|
469
|
-
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || "/"}`;
|
|
469
|
+
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(ctx.body.callbackURL || "/")}`;
|
|
470
470
|
await ctx.context.runInBackgroundOrAwait(canSendConfirmation({
|
|
471
471
|
user: ctx.context.session.user,
|
|
472
472
|
newEmail,
|
|
@@ -480,7 +480,7 @@ const changeEmail = createAuthEndpoint("/change-email", {
|
|
|
480
480
|
throw APIError.fromStatus("BAD_REQUEST", { message: "Verification email isn't enabled" });
|
|
481
481
|
}
|
|
482
482
|
const token = await createEmailVerificationToken(ctx.context.secret, ctx.context.session.user.email, newEmail, ctx.context.options.emailVerification?.expiresIn, { requestType: "change-email-verification" });
|
|
483
|
-
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || "/"}`;
|
|
483
|
+
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(ctx.body.callbackURL || "/")}`;
|
|
484
484
|
await ctx.context.runInBackgroundOrAwait(canSendVerification({
|
|
485
485
|
user: {
|
|
486
486
|
...ctx.context.session.user,
|
package/dist/client/parser.mjs
CHANGED
|
@@ -34,7 +34,6 @@ function betterJSONParse(value, options = {}) {
|
|
|
34
34
|
const { strict = false, warnings = false, reviver, parseDates = true } = options;
|
|
35
35
|
if (typeof value !== "string") return value;
|
|
36
36
|
const trimmed = value.trim();
|
|
37
|
-
if (trimmed.length > 0 && trimmed[0] === "\"" && trimmed.endsWith("\"") && !trimmed.slice(1, -1).includes("\"")) return trimmed.slice(1, -1);
|
|
38
37
|
const lowerValue = trimmed.toLowerCase();
|
|
39
38
|
if (lowerValue.length <= 9 && lowerValue in SPECIAL_VALUES) return SPECIAL_VALUES[lowerValue];
|
|
40
39
|
if (!JSON_SIGNATURE.test(trimmed)) {
|
|
@@ -31,7 +31,7 @@ import { USERNAME_ERROR_CODES } from "../../plugins/username/error-codes.mjs";
|
|
|
31
31
|
import { ORGANIZATION_ERROR_CODES } from "../../plugins/organization/error-codes.mjs";
|
|
32
32
|
import { inferAdditionalFields } from "../../plugins/additional-fields/client.mjs";
|
|
33
33
|
import { ADMIN_ERROR_CODES } from "../../plugins/admin/error-codes.mjs";
|
|
34
|
-
import { adminClient } from "../../plugins/admin/client.mjs";
|
|
34
|
+
import { AdminClientOptions, adminClient } from "../../plugins/admin/client.mjs";
|
|
35
35
|
import { ANONYMOUS_ERROR_CODES } from "../../plugins/anonymous/error-codes.mjs";
|
|
36
36
|
import { anonymousClient } from "../../plugins/anonymous/client.mjs";
|
|
37
37
|
import { customSessionClient } from "../../plugins/custom-session/client.mjs";
|
|
@@ -47,10 +47,10 @@ import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
|
|
|
47
47
|
import { OidcClientPlugin, oidcClient } from "../../plugins/oidc-provider/client.mjs";
|
|
48
48
|
import { GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, oneTapClient } from "../../plugins/one-tap/client.mjs";
|
|
49
49
|
import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
|
|
50
|
-
import { clientSideHasPermission, inferOrgAdditionalFields, organizationClient } from "../../plugins/organization/client.mjs";
|
|
50
|
+
import { OrganizationClientOptions, clientSideHasPermission, inferOrgAdditionalFields, organizationClient } from "../../plugins/organization/client.mjs";
|
|
51
51
|
import { PHONE_NUMBER_ERROR_CODES } from "../../plugins/phone-number/error-codes.mjs";
|
|
52
52
|
import { phoneNumberClient } from "../../plugins/phone-number/client.mjs";
|
|
53
53
|
import { siweClient } from "../../plugins/siwe/client.mjs";
|
|
54
54
|
import { usernameClient } from "../../plugins/username/client.mjs";
|
|
55
55
|
import { InferServerPlugin } from "./infer-plugin.mjs";
|
|
56
|
-
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
|
|
56
|
+
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
|
package/dist/client/proxy.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAtom } from "../utils/is-atom.mjs";
|
|
2
|
+
import { toKebabCase } from "@better-auth/core/utils/string";
|
|
2
3
|
//#region src/client/proxy.ts
|
|
3
4
|
function getMethod(path, knownPathMethods, args) {
|
|
4
5
|
const method = knownPathMethods[path];
|
|
@@ -26,7 +27,7 @@ function createDynamicPathProxy(routes, client, knownPathMethods, atoms, atomLis
|
|
|
26
27
|
return createProxy(fullPath);
|
|
27
28
|
},
|
|
28
29
|
apply: async (_, __, args) => {
|
|
29
|
-
const routePath = "/" + path.map(
|
|
30
|
+
const routePath = "/" + path.map(toKebabCase).join("/");
|
|
30
31
|
const arg = args[0] || {};
|
|
31
32
|
const fetchOptions = args[1] || {};
|
|
32
33
|
const { query, fetchOptions: argFetchOptions, ...body } = arg;
|
package/dist/context/helpers.mjs
CHANGED
|
@@ -61,9 +61,10 @@ async function getTrustedOrigins(options, request) {
|
|
|
61
61
|
const trustedOrigins = [];
|
|
62
62
|
if (isDynamicBaseURLConfig(options.baseURL)) {
|
|
63
63
|
const allowedHosts = options.baseURL.allowedHosts;
|
|
64
|
+
const proto = options.baseURL.protocol;
|
|
64
65
|
for (const host of allowedHosts) if (!host.includes("://")) {
|
|
65
|
-
trustedOrigins.push(`https://${host}`);
|
|
66
|
-
if (isLoopbackHost(host)) trustedOrigins.push(`http://${host}`);
|
|
66
|
+
if (!proto || proto === "https" || proto === "auto") trustedOrigins.push(`https://${host}`);
|
|
67
|
+
if (proto === "http" || proto === "auto" || isLoopbackHost(host)) trustedOrigins.push(`http://${host}`);
|
|
67
68
|
} else trustedOrigins.push(host);
|
|
68
69
|
if (options.baseURL.fallback) try {
|
|
69
70
|
trustedOrigins.push(new URL(options.baseURL.fallback).origin);
|
|
@@ -33,6 +33,20 @@ declare function stripSecureCookiePrefix(cookieName: string): string;
|
|
|
33
33
|
declare function splitSetCookieHeader(setCookie: string): string[];
|
|
34
34
|
declare function parseSetCookieHeader(setCookie: string): Map<string, CookieAttributes>;
|
|
35
35
|
declare function toCookieOptions(attributes: CookieAttributes): ParsedCookieOptions;
|
|
36
|
+
/**
|
|
37
|
+
* Cookie-name token char set per RFC 7230 §3.2.6.
|
|
38
|
+
*
|
|
39
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
|
40
|
+
*/
|
|
41
|
+
declare const cookieNameRegex: RegExp;
|
|
42
|
+
/**
|
|
43
|
+
* Tolerates `;` separators without the SP that RFC 6265 §4.2.1 mandates,
|
|
44
|
+
* since proxies and runtimes commonly strip it. Silently drops entries
|
|
45
|
+
* whose name violates RFC 7230 token or whose value violates RFC 6265
|
|
46
|
+
* cookie-octet (plus space and comma). Strips optional surrounding
|
|
47
|
+
* double-quotes per RFC 6265 §4.1.1.
|
|
48
|
+
*/
|
|
49
|
+
declare function parseCookies(cookie: string): Map<string, string>;
|
|
36
50
|
/**
|
|
37
51
|
* Add or replace a cookie in the request `Cookie` header.
|
|
38
52
|
*
|
|
@@ -42,8 +56,17 @@ declare function toCookieOptions(attributes: CookieAttributes): ParsedCookieOpti
|
|
|
42
56
|
* parse-mutate-serialize.
|
|
43
57
|
*/
|
|
44
58
|
declare function setRequestCookie(headers: Headers, name: string, value: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Merge `Set-Cookie` header values into the target's `Cookie` header.
|
|
61
|
+
* Mutates `target`.
|
|
62
|
+
*
|
|
63
|
+
* Name/value-level merge only. RFC 6265 §5 user-agent semantics
|
|
64
|
+
* (expiration, domain/path scoping, ordering) are out of scope. Suitable
|
|
65
|
+
* for single-request proxy, middleware, and test contexts.
|
|
66
|
+
*/
|
|
67
|
+
declare function applySetCookies(target: Headers, setCookieValues: Iterable<string>): void;
|
|
45
68
|
declare function setCookieToHeader(headers: Headers): (context: {
|
|
46
69
|
response: Response;
|
|
47
70
|
}) => void;
|
|
48
71
|
//#endregion
|
|
49
|
-
export { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
72
|
+
export { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, applySetCookies, cookieNameRegex, parseCookies, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|