better-auth 1.6.8 → 1.6.10
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/README.md +14 -15
- package/dist/api/index.d.mts +0 -2
- package/dist/api/routes/callback.mjs +6 -5
- package/dist/api/routes/email-verification.mjs +2 -2
- package/dist/api/routes/error.mjs +1 -1
- package/dist/api/routes/sign-in.d.mts +0 -1
- package/dist/api/routes/sign-in.mjs +4 -11
- package/dist/api/routes/sign-up.mjs +1 -1
- package/dist/api/to-auth-endpoints.mjs +7 -1
- package/dist/client/plugins/index.d.mts +2 -1
- package/dist/cookies/cookie-utils.d.mts +10 -1
- package/dist/cookies/cookie-utils.mjs +19 -1
- package/dist/cookies/index.d.mts +2 -2
- package/dist/cookies/index.mjs +2 -2
- package/dist/db/internal-adapter.mjs +14 -6
- package/dist/integrations/cookie-plugin-guard.mjs +18 -0
- package/dist/integrations/next-js.mjs +6 -0
- package/dist/integrations/svelte-kit.mjs +6 -0
- package/dist/integrations/tanstack-start-solid.mjs +6 -0
- package/dist/integrations/tanstack-start.mjs +6 -0
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/client.d.mts +5 -0
- package/dist/plugins/admin/client.mjs +5 -0
- package/dist/plugins/bearer/index.mjs +2 -4
- package/dist/plugins/captcha/index.mjs +14 -1
- package/dist/plugins/email-otp/routes.mjs +3 -3
- package/dist/plugins/generic-oauth/routes.mjs +3 -3
- package/dist/plugins/one-tap/index.mjs +3 -2
- package/dist/plugins/organization/client.mjs +1 -1
- package/dist/plugins/organization/routes/crud-invites.d.mts +8 -1
- package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
- package/dist/plugins/organization/routes/crud-team.mjs +7 -2
- package/dist/plugins/siwe/client.d.mts +4 -0
- package/dist/plugins/siwe/client.mjs +5 -1
- package/dist/plugins/siwe/index.d.mts +13 -2
- package/dist/plugins/siwe/index.mjs +179 -165
- package/dist/plugins/username/index.d.mts +11 -0
- package/dist/plugins/username/index.mjs +18 -2
- package/dist/test-utils/test-instance.d.mts +0 -6
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
<
|
|
1
|
+
<div align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<source
|
|
4
|
-
<source
|
|
5
|
-
<img
|
|
3
|
+
<source srcset="https://github.com/better-auth/better-auth/blob/main/banner-dark.png?raw=true" media="(prefers-color-scheme: dark)"/>
|
|
4
|
+
<source srcset="https://github.com/better-auth/better-auth/blob/main/banner-light.png?raw=true" media="(prefers-color-scheme: light)"/>
|
|
5
|
+
<img src="https://github.com/better-auth/better-auth/blob/main/banner-light.png?raw=true" alt="Better Auth Logo"/>
|
|
6
6
|
</picture>
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
[](https://npm.chart.dev/better-auth?primary=neutral&gray=neutral&theme=dark)
|
|
9
|
+
[](https://www.npmjs.com/package/better-auth)
|
|
10
|
+
[](https://github.com/better-auth/better-auth/stargazers)
|
|
11
11
|
|
|
12
|
-
<p
|
|
13
|
-
The most comprehensive authentication framework for TypeScript
|
|
14
|
-
<br />
|
|
15
|
-
<a href="https://better-auth.com"><strong>Learn more »</strong></a>
|
|
16
|
-
<br />
|
|
17
|
-
<br />
|
|
12
|
+
<p>
|
|
18
13
|
<a href="https://discord.gg/better-auth">Discord</a>
|
|
19
14
|
·
|
|
20
15
|
<a href="https://better-auth.com">Website</a>
|
|
21
16
|
·
|
|
22
17
|
<a href="https://github.com/better-auth/better-auth/issues">Issues</a>
|
|
23
18
|
</p>
|
|
24
|
-
</
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
## Better Auth
|
|
22
|
+
|
|
23
|
+
Better Auth is a framework-agnostic authentication (and authorization) framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities with minimal code in a short amount of time. Whether you need 2FA, multi-tenant support, or other complex features, it lets you focus on building your actual application instead of reinventing the wheel.
|
|
25
24
|
|
|
26
25
|
## Getting Started
|
|
27
26
|
|
|
28
27
|
```bash
|
|
29
|
-
|
|
28
|
+
npm i better-auth
|
|
30
29
|
```
|
|
31
30
|
|
|
32
31
|
Read the [Installation Guide](https://better-auth.com/docs/installation) to
|
package/dist/api/index.d.mts
CHANGED
|
@@ -178,7 +178,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
178
178
|
};
|
|
179
179
|
redirect: {
|
|
180
180
|
type: string;
|
|
181
|
-
enum: boolean[];
|
|
182
181
|
};
|
|
183
182
|
};
|
|
184
183
|
required: string[];
|
|
@@ -2167,7 +2166,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2167
2166
|
};
|
|
2168
2167
|
redirect: {
|
|
2169
2168
|
type: string;
|
|
2170
|
-
enum: boolean[];
|
|
2171
2169
|
};
|
|
2172
2170
|
};
|
|
2173
2171
|
required: string[];
|
|
@@ -91,10 +91,11 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
91
91
|
...tokens,
|
|
92
92
|
user: parsedUserData ?? void 0
|
|
93
93
|
}).then((res) => res?.user);
|
|
94
|
-
if (!userInfo) {
|
|
94
|
+
if (!userInfo || userInfo.id === void 0 || userInfo.id === null) {
|
|
95
95
|
c.context.logger.error("Unable to get user info");
|
|
96
96
|
return redirectOnError("unable_to_get_user_info");
|
|
97
97
|
}
|
|
98
|
+
const providerAccountId = String(userInfo.id);
|
|
98
99
|
if (!callbackURL) {
|
|
99
100
|
c.context.logger.error("No callback URL found");
|
|
100
101
|
throw redirectOnError("no_callback_url");
|
|
@@ -105,7 +106,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
105
106
|
return redirectOnError("unable_to_link_account");
|
|
106
107
|
}
|
|
107
108
|
if (userInfo.email?.toLowerCase() !== link.email.toLowerCase() && c.context.options.account?.accountLinking?.allowDifferentEmails !== true) return redirectOnError("email_doesn't_match");
|
|
108
|
-
const existingAccount = await c.context.internalAdapter.findAccountByProviderId(
|
|
109
|
+
const existingAccount = await c.context.internalAdapter.findAccountByProviderId(providerAccountId, provider.id);
|
|
109
110
|
if (existingAccount) {
|
|
110
111
|
if (existingAccount.userId.toString() !== link.userId.toString()) return redirectOnError("account_already_linked_to_different_user");
|
|
111
112
|
const updateData = Object.fromEntries(Object.entries({
|
|
@@ -120,7 +121,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
120
121
|
} else if (!await c.context.internalAdapter.createAccount({
|
|
121
122
|
userId: link.userId,
|
|
122
123
|
providerId: provider.id,
|
|
123
|
-
accountId:
|
|
124
|
+
accountId: providerAccountId,
|
|
124
125
|
...tokens,
|
|
125
126
|
accessToken: await setTokenUtil(tokens.accessToken, c.context),
|
|
126
127
|
refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
|
|
@@ -140,14 +141,14 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
140
141
|
}
|
|
141
142
|
const accountData = {
|
|
142
143
|
providerId: provider.id,
|
|
143
|
-
accountId:
|
|
144
|
+
accountId: providerAccountId,
|
|
144
145
|
...tokens,
|
|
145
146
|
scope: tokens.scopes?.join(",")
|
|
146
147
|
};
|
|
147
148
|
const result = await handleOAuthUserInfo(c, {
|
|
148
149
|
userInfo: {
|
|
149
150
|
...userInfo,
|
|
150
|
-
id:
|
|
151
|
+
id: providerAccountId,
|
|
151
152
|
email: userInfo.email,
|
|
152
153
|
name: userInfo.name || ""
|
|
153
154
|
},
|
|
@@ -12,7 +12,7 @@ import { JWTExpired } from "jose/errors";
|
|
|
12
12
|
async function createEmailVerificationToken(secret, email, updateTo, expiresIn = 3600, extraPayload) {
|
|
13
13
|
return await signJWT({
|
|
14
14
|
email: email.toLowerCase(),
|
|
15
|
-
updateTo,
|
|
15
|
+
updateTo: updateTo?.toLowerCase(),
|
|
16
16
|
...extraPayload
|
|
17
17
|
}, secret, expiresIn);
|
|
18
18
|
}
|
|
@@ -101,7 +101,7 @@ const sendVerificationEmail = createAuthEndpoint("/send-verification-email", {
|
|
|
101
101
|
await sendVerificationEmailFn(ctx, user.user);
|
|
102
102
|
return ctx.json({ status: true });
|
|
103
103
|
}
|
|
104
|
-
if (session?.user.email !== email) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.EMAIL_MISMATCH);
|
|
104
|
+
if (session?.user.email.toLowerCase() !== email.toLowerCase()) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.EMAIL_MISMATCH);
|
|
105
105
|
if (session?.user.emailVerified) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.EMAIL_ALREADY_VERIFIED);
|
|
106
106
|
await sendVerificationEmailFn(ctx, session.user);
|
|
107
107
|
return ctx.json({ status: true });
|
|
@@ -282,7 +282,7 @@ ${custom?.disableBackgroundGrid ? "" : `
|
|
|
282
282
|
text-wrap: pretty;
|
|
283
283
|
"
|
|
284
284
|
>
|
|
285
|
-
${!description ? `We encountered an unexpected error. Please try again or return to the home page. If you're a developer, you can find
|
|
285
|
+
${!description ? `We encountered an unexpected error. Please try again or return to the home page. If you're a developer, you can find <a href='https://better-auth.com/docs/reference/errors/${encodeURIComponent(code)}' target='_blank' rel="noopener noreferrer" style='color: var(--foreground); text-decoration: underline;'>more information about the error</a>.` : description}
|
|
286
286
|
</p>
|
|
287
287
|
</div>
|
|
288
288
|
|
|
@@ -49,10 +49,10 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
|
|
|
49
49
|
description: "Sign in with a social provider",
|
|
50
50
|
operationId: "socialSignIn",
|
|
51
51
|
responses: { "200": {
|
|
52
|
-
description: "Success - Returns
|
|
52
|
+
description: "Success - Returns session details (idToken branch) or an authorize URL (redirect branch)",
|
|
53
53
|
content: { "application/json": { schema: {
|
|
54
54
|
type: "object",
|
|
55
|
-
description: "
|
|
55
|
+
description: "Returns session details when idToken is provided, or an authorize URL otherwise",
|
|
56
56
|
properties: {
|
|
57
57
|
token: { type: "string" },
|
|
58
58
|
user: {
|
|
@@ -60,16 +60,9 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
|
|
|
60
60
|
$ref: "#/components/schemas/User"
|
|
61
61
|
},
|
|
62
62
|
url: { type: "string" },
|
|
63
|
-
redirect: {
|
|
64
|
-
type: "boolean",
|
|
65
|
-
enum: [false]
|
|
66
|
-
}
|
|
63
|
+
redirect: { type: "boolean" }
|
|
67
64
|
},
|
|
68
|
-
required: [
|
|
69
|
-
"redirect",
|
|
70
|
-
"token",
|
|
71
|
-
"user"
|
|
72
|
-
]
|
|
65
|
+
required: ["redirect"]
|
|
73
66
|
} } }
|
|
74
67
|
} }
|
|
75
68
|
}
|
|
@@ -157,7 +157,7 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
|
|
|
157
157
|
ctx.context.logger.error("Password is too long");
|
|
158
158
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
|
|
159
159
|
}
|
|
160
|
-
const shouldReturnGenericDuplicateResponse = ctx.context.options.emailAndPassword.requireEmailVerification;
|
|
160
|
+
const shouldReturnGenericDuplicateResponse = ctx.context.options.emailAndPassword.requireEmailVerification || ctx.context.options.emailAndPassword.autoSignIn === false;
|
|
161
161
|
const shouldSkipAutoSignIn = ctx.context.options.emailAndPassword.autoSignIn === false || shouldReturnGenericDuplicateResponse;
|
|
162
162
|
const additionalUserFields = parseUserInput(ctx.context.options, rest, "create");
|
|
163
163
|
const normalizedEmail = email.toLowerCase();
|
|
@@ -117,7 +117,13 @@ function toAuthEndpoints(endpoints, ctx) {
|
|
|
117
117
|
* headers override while cookies accumulate.
|
|
118
118
|
*/
|
|
119
119
|
const ctxHeaders = e[kAPIErrorHeaderSymbol];
|
|
120
|
-
|
|
120
|
+
/**
|
|
121
|
+
* `c.redirect()` (and similar APIError throws) reuse
|
|
122
|
+
* `ctx.responseHeaders` as `e.headers`, so when both sources
|
|
123
|
+
* reference the same Headers, iterating both duplicates every
|
|
124
|
+
* `set-cookie`. Skip the `errHeaders` copy in that case.
|
|
125
|
+
*/
|
|
126
|
+
const errHeaders = e.headers && e.headers !== ctxHeaders ? new Headers(e.headers) : null;
|
|
121
127
|
let headers = null;
|
|
122
128
|
if (ctxHeaders || errHeaders) {
|
|
123
129
|
headers = new Headers();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FieldAttributeToObject, RemoveFieldsWithReturnedFalse } from "../../db/field.mjs";
|
|
1
2
|
import { ExtractPluginField, HasRequiredKeys, InferPluginFieldFromTuple, IsAny, OverrideMerge, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "../../types/helper.mjs";
|
|
2
3
|
import { InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferTeam, Invitation, InvitationInput, InvitationStatus, Member, MemberInput, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, Team, TeamInput, TeamMember, TeamMemberInput, defaultRolesSchema, invitationSchema, invitationStatus, memberSchema, organizationRoleSchema, organizationSchema, roleSchema, teamMemberSchema, teamSchema } from "../../plugins/organization/schema.mjs";
|
|
3
4
|
import { AdminOptions, InferAdminRolesFromOption, SessionWithImpersonatedBy, UserWithRole } from "../../plugins/admin/types.mjs";
|
|
@@ -52,4 +53,4 @@ import { phoneNumberClient } from "../../plugins/phone-number/client.mjs";
|
|
|
52
53
|
import { siweClient } from "../../plugins/siwe/client.mjs";
|
|
53
54
|
import { usernameClient } from "../../plugins/username/client.mjs";
|
|
54
55
|
import { InferServerPlugin } from "./infer-plugin.mjs";
|
|
55
|
-
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, 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, 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, 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 };
|
|
@@ -33,8 +33,17 @@ 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
|
+
* Add or replace a cookie in the request `Cookie` header.
|
|
38
|
+
*
|
|
39
|
+
* Cookie pairs are joined with `; `, but `headers.append("cookie", ...)`
|
|
40
|
+
* joins with `, ` in some runtimes (e.g. Deno, Cloudflare Workers) and
|
|
41
|
+
* breaks downstream cookie parsing. This builds the header value via
|
|
42
|
+
* parse-mutate-serialize.
|
|
43
|
+
*/
|
|
44
|
+
declare function setRequestCookie(headers: Headers, name: string, value: string): void;
|
|
36
45
|
declare function setCookieToHeader(headers: Headers): (context: {
|
|
37
46
|
response: Response;
|
|
38
47
|
}) => void;
|
|
39
48
|
//#endregion
|
|
40
|
-
export { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
49
|
+
export { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
@@ -102,6 +102,24 @@ function toCookieOptions(attributes) {
|
|
|
102
102
|
partitioned: attributes.partitioned
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Add or replace a cookie in the request `Cookie` header.
|
|
107
|
+
*
|
|
108
|
+
* Cookie pairs are joined with `; `, but `headers.append("cookie", ...)`
|
|
109
|
+
* joins with `, ` in some runtimes (e.g. Deno, Cloudflare Workers) and
|
|
110
|
+
* breaks downstream cookie parsing. This builds the header value via
|
|
111
|
+
* parse-mutate-serialize.
|
|
112
|
+
*/
|
|
113
|
+
function setRequestCookie(headers, name, value) {
|
|
114
|
+
const cookieMap = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const pair of (headers.get("cookie") || "").split(";")) {
|
|
116
|
+
const trimmed = pair.trim();
|
|
117
|
+
const eq = trimmed.indexOf("=");
|
|
118
|
+
if (eq > 0) cookieMap.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));
|
|
119
|
+
}
|
|
120
|
+
cookieMap.set(name, value);
|
|
121
|
+
headers.set("cookie", Array.from(cookieMap, ([k, v]) => `${k}=${v}`).join("; "));
|
|
122
|
+
}
|
|
105
123
|
function setCookieToHeader(headers) {
|
|
106
124
|
return (context) => {
|
|
107
125
|
const setCookieHeader = context.response.headers.get("set-cookie");
|
|
@@ -119,4 +137,4 @@ function setCookieToHeader(headers) {
|
|
|
119
137
|
};
|
|
120
138
|
}
|
|
121
139
|
//#endregion
|
|
122
|
-
export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
140
|
+
export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
package/dist/cookies/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Session, User } from "../types/models.mjs";
|
|
2
|
-
import { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
|
|
2
|
+
import { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
|
|
3
3
|
import { createSessionStore, getAccountCookie, getChunkedCookie } from "./session-store.mjs";
|
|
4
4
|
import { BetterAuthCookie, BetterAuthCookies, BetterAuthOptions, GenericEndpointContext } from "@better-auth/core";
|
|
5
5
|
import * as better_call0 from "better-call";
|
|
@@ -116,4 +116,4 @@ declare const getCookieCache: <S extends {
|
|
|
116
116
|
version?: string | ((session: Session & Record<string, any>, user: User & Record<string, any>) => string) | ((session: Session & Record<string, any>, user: User & Record<string, any>) => Promise<string>);
|
|
117
117
|
} | undefined) => Promise<S | null>;
|
|
118
118
|
//#endregion
|
|
119
|
-
export { CookieAttributes, EligibleCookies, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
119
|
+
export { CookieAttributes, EligibleCookies, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setRequestCookie, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
package/dist/cookies/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { parseUserOutput } from "../db/schema.mjs";
|
|
|
4
4
|
import { getDate } from "../utils/date.mjs";
|
|
5
5
|
import { isPromise } from "../utils/is-promise.mjs";
|
|
6
6
|
import { sec } from "../utils/time.mjs";
|
|
7
|
-
import { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
|
|
7
|
+
import { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
|
|
8
8
|
import { createAccountStore, createSessionStore, getAccountCookie, getChunkedCookie, setAccountCookie } from "./session-store.mjs";
|
|
9
9
|
import { env, isProduction } from "@better-auth/core/env";
|
|
10
10
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
@@ -258,4 +258,4 @@ const getCookieCache = async (request, config) => {
|
|
|
258
258
|
return null;
|
|
259
259
|
};
|
|
260
260
|
//#endregion
|
|
261
|
-
export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
261
|
+
export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setRequestCookie, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
|
|
@@ -41,7 +41,8 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
41
41
|
const createdUser = await createWithHooks({
|
|
42
42
|
createdAt: /* @__PURE__ */ new Date(),
|
|
43
43
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
44
|
-
...user
|
|
44
|
+
...user,
|
|
45
|
+
email: user.email?.toLowerCase()
|
|
45
46
|
}, "user", void 0);
|
|
46
47
|
return {
|
|
47
48
|
user: createdUser,
|
|
@@ -364,10 +365,10 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
364
365
|
value: userId
|
|
365
366
|
}], "account", void 0);
|
|
366
367
|
},
|
|
367
|
-
deleteAccount: async (
|
|
368
|
+
deleteAccount: async (id) => {
|
|
368
369
|
await deleteWithHooks([{
|
|
369
370
|
field: "id",
|
|
370
|
-
value:
|
|
371
|
+
value: id
|
|
371
372
|
}], "account", void 0);
|
|
372
373
|
},
|
|
373
374
|
deleteSessions: async (userIdOrSessionTokens) => {
|
|
@@ -475,7 +476,10 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
475
476
|
}, "account", void 0);
|
|
476
477
|
},
|
|
477
478
|
updateUser: async (userId, data) => {
|
|
478
|
-
const user = await updateWithHooks(
|
|
479
|
+
const user = await updateWithHooks({
|
|
480
|
+
...data,
|
|
481
|
+
...data.email ? { email: data.email.toLowerCase() } : {}
|
|
482
|
+
}, [{
|
|
479
483
|
field: "id",
|
|
480
484
|
value: userId
|
|
481
485
|
}], "user", void 0);
|
|
@@ -483,7 +487,10 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
483
487
|
return user;
|
|
484
488
|
},
|
|
485
489
|
updateUserByEmail: async (email, data) => {
|
|
486
|
-
const user = await updateWithHooks(
|
|
490
|
+
const user = await updateWithHooks({
|
|
491
|
+
...data,
|
|
492
|
+
...data.email ? { email: data.email.toLowerCase() } : {}
|
|
493
|
+
}, [{
|
|
487
494
|
field: "email",
|
|
488
495
|
value: email.toLowerCase()
|
|
489
496
|
}], "user", void 0);
|
|
@@ -634,7 +641,8 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
634
641
|
value: storedIdentifier
|
|
635
642
|
}], "verification", void 0);
|
|
636
643
|
return data;
|
|
637
|
-
}
|
|
644
|
+
},
|
|
645
|
+
refreshUserSessions
|
|
638
646
|
};
|
|
639
647
|
};
|
|
640
648
|
//#endregion
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/integrations/cookie-plugin-guard.ts
|
|
2
|
+
/**
|
|
3
|
+
* Warns when a cookie integration plugin is not effectively last.
|
|
4
|
+
*
|
|
5
|
+
* A plugin is considered misordered when there is at least one other plugin
|
|
6
|
+
* after it in the `plugins` array that declares `hooks.after`, since those
|
|
7
|
+
* hooks can set cookies that this integration will not see.
|
|
8
|
+
*/
|
|
9
|
+
function warnIfCookiePluginNotLast(ctx, pluginId) {
|
|
10
|
+
const plugins = ctx.options.plugins || [];
|
|
11
|
+
if (plugins.length === 0) return;
|
|
12
|
+
const index = plugins.findIndex((p) => p.id === pluginId);
|
|
13
|
+
if (index === -1) return;
|
|
14
|
+
if (!plugins.slice(index + 1).some((p) => p.hooks && Array.isArray(p.hooks.after) && p.hooks.after.length > 0)) return;
|
|
15
|
+
ctx.logger.warn(`[better-auth] Cookie integration plugin "${pluginId}" should be placed last in the plugins array. Plugins with \`hooks.after\` running after it may set cookies that are not forwarded to the framework cookie store. Move your cookie integration plugin to the end of the \`plugins\` array to avoid missing \`Set-Cookie\` headers.`);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { warnIfCookiePluginNotLast };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseSetCookieHeader, toCookieOptions } from "../cookies/cookie-utils.mjs";
|
|
2
2
|
import { setShouldSkipSessionRefresh } from "../api/state/should-session-refresh.mjs";
|
|
3
3
|
import { PACKAGE_VERSION } from "../version.mjs";
|
|
4
|
+
import { warnIfCookiePluginNotLast } from "./cookie-plugin-guard.mjs";
|
|
4
5
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
5
6
|
//#region src/integrations/next-js.ts
|
|
6
7
|
function toNextJsHandler(auth) {
|
|
@@ -16,6 +17,7 @@ function toNextJsHandler(auth) {
|
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
const nextCookies = () => {
|
|
20
|
+
let hasWarned = false;
|
|
19
21
|
return {
|
|
20
22
|
id: "next-cookies",
|
|
21
23
|
version: PACKAGE_VERSION,
|
|
@@ -25,6 +27,10 @@ const nextCookies = () => {
|
|
|
25
27
|
return ctx.path === "/get-session";
|
|
26
28
|
},
|
|
27
29
|
handler: createAuthMiddleware(async (ctx) => {
|
|
30
|
+
if (!hasWarned) {
|
|
31
|
+
warnIfCookiePluginNotLast(ctx.context, "next-cookies");
|
|
32
|
+
hasWarned = true;
|
|
33
|
+
}
|
|
28
34
|
if ("_flag" in ctx && ctx._flag === "router") return;
|
|
29
35
|
let headersStore;
|
|
30
36
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseSetCookieHeader, toCookieOptions } from "../cookies/cookie-utils.mjs";
|
|
2
2
|
import { PACKAGE_VERSION } from "../version.mjs";
|
|
3
|
+
import { warnIfCookiePluginNotLast } from "./cookie-plugin-guard.mjs";
|
|
3
4
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
4
5
|
//#region src/integrations/svelte-kit.ts
|
|
5
6
|
const toSvelteKitHandler = (auth) => {
|
|
@@ -20,6 +21,7 @@ function isAuthPath(url, options) {
|
|
|
20
21
|
return true;
|
|
21
22
|
}
|
|
22
23
|
const sveltekitCookies = (getRequestEvent) => {
|
|
24
|
+
let hasWarned = false;
|
|
23
25
|
return {
|
|
24
26
|
id: "sveltekit-cookies",
|
|
25
27
|
version: PACKAGE_VERSION,
|
|
@@ -28,6 +30,10 @@ const sveltekitCookies = (getRequestEvent) => {
|
|
|
28
30
|
return true;
|
|
29
31
|
},
|
|
30
32
|
handler: createAuthMiddleware(async (ctx) => {
|
|
33
|
+
if (!hasWarned) {
|
|
34
|
+
warnIfCookiePluginNotLast(ctx.context, "sveltekit-cookies");
|
|
35
|
+
hasWarned = true;
|
|
36
|
+
}
|
|
31
37
|
const returned = ctx.context.responseHeaders;
|
|
32
38
|
if ("_flag" in ctx && ctx._flag === "router") return;
|
|
33
39
|
if (returned instanceof Headers) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseSetCookieHeader, toCookieOptions } from "../cookies/cookie-utils.mjs";
|
|
2
2
|
import { PACKAGE_VERSION } from "../version.mjs";
|
|
3
|
+
import { warnIfCookiePluginNotLast } from "./cookie-plugin-guard.mjs";
|
|
3
4
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
4
5
|
//#region src/integrations/tanstack-start-solid.ts
|
|
5
6
|
/**
|
|
@@ -20,6 +21,7 @@ import { createAuthMiddleware } from "@better-auth/core/api";
|
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
22
23
|
const tanstackStartCookies = () => {
|
|
24
|
+
let hasWarned = false;
|
|
23
25
|
return {
|
|
24
26
|
id: "tanstack-start-cookies-solid",
|
|
25
27
|
version: PACKAGE_VERSION,
|
|
@@ -28,6 +30,10 @@ const tanstackStartCookies = () => {
|
|
|
28
30
|
return true;
|
|
29
31
|
},
|
|
30
32
|
handler: createAuthMiddleware(async (ctx) => {
|
|
33
|
+
if (!hasWarned) {
|
|
34
|
+
warnIfCookiePluginNotLast(ctx.context, "tanstack-start-cookies-solid");
|
|
35
|
+
hasWarned = true;
|
|
36
|
+
}
|
|
31
37
|
const returned = ctx.context.responseHeaders;
|
|
32
38
|
if ("_flag" in ctx && ctx._flag === "router") return;
|
|
33
39
|
if (returned instanceof Headers) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseSetCookieHeader, toCookieOptions } from "../cookies/cookie-utils.mjs";
|
|
2
2
|
import { PACKAGE_VERSION } from "../version.mjs";
|
|
3
|
+
import { warnIfCookiePluginNotLast } from "./cookie-plugin-guard.mjs";
|
|
3
4
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
4
5
|
//#region src/integrations/tanstack-start.ts
|
|
5
6
|
/**
|
|
@@ -20,6 +21,7 @@ import { createAuthMiddleware } from "@better-auth/core/api";
|
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
22
23
|
const tanstackStartCookies = () => {
|
|
24
|
+
let hasWarned = false;
|
|
23
25
|
return {
|
|
24
26
|
id: "tanstack-start-cookies",
|
|
25
27
|
version: PACKAGE_VERSION,
|
|
@@ -28,6 +30,10 @@ const tanstackStartCookies = () => {
|
|
|
28
30
|
return true;
|
|
29
31
|
},
|
|
30
32
|
handler: createAuthMiddleware(async (ctx) => {
|
|
33
|
+
if (!hasWarned) {
|
|
34
|
+
warnIfCookiePluginNotLast(ctx.context, "tanstack-start-cookies");
|
|
35
|
+
hasWarned = true;
|
|
36
|
+
}
|
|
31
37
|
const returned = ctx.context.responseHeaders;
|
|
32
38
|
if ("_flag" in ctx && ctx._flag === "router") return;
|
|
33
39
|
if (returned instanceof Headers) {
|
package/dist/package.mjs
CHANGED
|
@@ -44,8 +44,13 @@ declare const adminClient: <O extends AdminClientOptions>(options?: O | undefine
|
|
|
44
44
|
};
|
|
45
45
|
pathMethods: {
|
|
46
46
|
"/admin/list-users": "GET";
|
|
47
|
+
"/admin/impersonate-user": "POST";
|
|
47
48
|
"/admin/stop-impersonating": "POST";
|
|
48
49
|
};
|
|
50
|
+
atomListeners: {
|
|
51
|
+
matcher: (path: string) => path is "/admin/impersonate-user" | "/admin/stop-impersonating";
|
|
52
|
+
signal: "$sessionSignal";
|
|
53
|
+
}[];
|
|
49
54
|
$ERROR_CODES: {
|
|
50
55
|
USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL: _better_auth_core_utils_error_codes0.RawError<"USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL">;
|
|
51
56
|
FAILED_TO_CREATE_USER: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_CREATE_USER">;
|
|
@@ -25,8 +25,13 @@ const adminClient = (options) => {
|
|
|
25
25
|
} } }),
|
|
26
26
|
pathMethods: {
|
|
27
27
|
"/admin/list-users": "GET",
|
|
28
|
+
"/admin/impersonate-user": "POST",
|
|
28
29
|
"/admin/stop-impersonating": "POST"
|
|
29
30
|
},
|
|
31
|
+
atomListeners: [{
|
|
32
|
+
matcher: (path) => path === "/admin/impersonate-user" || path === "/admin/stop-impersonating",
|
|
33
|
+
signal: "$sessionSignal"
|
|
34
|
+
}],
|
|
30
35
|
$ERROR_CODES: ADMIN_ERROR_CODES
|
|
31
36
|
};
|
|
32
37
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
|
|
1
|
+
import { parseSetCookieHeader, setRequestCookie } from "../../cookies/cookie-utils.mjs";
|
|
2
2
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
3
3
|
import { serializeSignedCookie } from "better-call";
|
|
4
4
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
@@ -48,9 +48,7 @@ const bearer = (options) => {
|
|
|
48
48
|
}
|
|
49
49
|
const existingHeaders = c.request?.headers || c.headers;
|
|
50
50
|
const headers = new Headers({ ...Object.fromEntries(existingHeaders?.entries()) });
|
|
51
|
-
|
|
52
|
-
const newCookie = `${c.context.authCookies.sessionToken.name}=${signedToken}`;
|
|
53
|
-
headers.set("cookie", existingCookie ? `${existingCookie}; ${newCookie}` : newCookie);
|
|
51
|
+
setRequestCookie(headers, c.context.authCookies.sessionToken.name, signedToken);
|
|
54
52
|
return { context: { headers } };
|
|
55
53
|
})
|
|
56
54
|
}],
|
|
@@ -14,7 +14,20 @@ const captcha = (options) => ({
|
|
|
14
14
|
$ERROR_CODES: EXTERNAL_ERROR_CODES,
|
|
15
15
|
onRequest: async (request, ctx) => {
|
|
16
16
|
try {
|
|
17
|
-
|
|
17
|
+
const endpoints = options.endpoints?.length ? options.endpoints : defaultEndpoints;
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const basePath = ctx.options.basePath ?? "/api/auth";
|
|
20
|
+
let pathname = url.pathname.replace(basePath, "");
|
|
21
|
+
if (pathname.endsWith("//")) pathname = pathname.slice(0, -1);
|
|
22
|
+
if (pathname.startsWith("//")) pathname = pathname.slice(1);
|
|
23
|
+
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
24
|
+
const blockedPaths = ["/sign-in/email-otp"].reduce((acc, curr) => {
|
|
25
|
+
if (options.endpoints?.length && options.endpoints.includes(curr)) return acc;
|
|
26
|
+
return [...acc, curr];
|
|
27
|
+
}, []);
|
|
28
|
+
if (!endpoints.some((endpoint) => {
|
|
29
|
+
return pathname.includes(endpoint) && !blockedPaths.includes(endpoint);
|
|
30
|
+
})) return;
|
|
18
31
|
if (!options.secretKey) throw new Error(INTERNAL_ERROR_CODES.MISSING_SECRET_KEY.message);
|
|
19
32
|
const captchaResponse = request.headers.get("x-captcha-response");
|
|
20
33
|
const remoteUserIP = getIp(request, ctx.options) ?? void 0;
|
|
@@ -465,7 +465,7 @@ const requestPasswordResetEmailOTP = (opts) => createAuthEndpoint("/email-otp/re
|
|
|
465
465
|
} }
|
|
466
466
|
} }
|
|
467
467
|
}, async (ctx) => {
|
|
468
|
-
const email = ctx.body.email;
|
|
468
|
+
const email = ctx.body.email.toLowerCase();
|
|
469
469
|
const identifier = toOTPIdentifier("forget-password", email);
|
|
470
470
|
const otp = await resolveOTP(ctx, opts, email, "forget-password");
|
|
471
471
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
|
|
@@ -517,7 +517,7 @@ const forgetPasswordEmailOTP = (opts) => {
|
|
|
517
517
|
} }
|
|
518
518
|
}, async (ctx) => {
|
|
519
519
|
warnDeprecation();
|
|
520
|
-
const email = ctx.body.email;
|
|
520
|
+
const email = ctx.body.email.toLowerCase();
|
|
521
521
|
const identifier = toOTPIdentifier("forget-password", email);
|
|
522
522
|
const otp = await resolveOTP(ctx, opts, email, "forget-password");
|
|
523
523
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
|
|
@@ -567,7 +567,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
|
|
|
567
567
|
} }
|
|
568
568
|
} }
|
|
569
569
|
}, async (ctx) => {
|
|
570
|
-
const email = ctx.body.email;
|
|
570
|
+
const email = ctx.body.email.toLowerCase();
|
|
571
571
|
await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
|
|
572
572
|
const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
|
|
573
573
|
if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
@@ -132,7 +132,7 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
132
132
|
}
|
|
133
133
|
}, async (ctx) => {
|
|
134
134
|
const defaultErrorURL = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
135
|
-
if (ctx.query.error || !ctx.query.code) throw ctx.redirect(`${defaultErrorURL}?error=${ctx.query.error || "oAuth_code_missing"}&error_description=${ctx.query.error_description}`);
|
|
135
|
+
if (ctx.query.error || !ctx.query.code) throw ctx.redirect(`${defaultErrorURL}?error=${encodeURIComponent(ctx.query.error || "oAuth_code_missing")}&error_description=${encodeURIComponent(ctx.query.error_description || "")}`);
|
|
136
136
|
const providerId = ctx.params?.providerId;
|
|
137
137
|
if (!providerId) throw APIError$1.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.PROVIDER_ID_REQUIRED);
|
|
138
138
|
const providerConfig = options.config.find((p) => p.providerId === providerId);
|
|
@@ -143,8 +143,8 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
143
143
|
function redirectOnError(error) {
|
|
144
144
|
const defaultErrorURL = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
145
145
|
let url = errorURL || defaultErrorURL;
|
|
146
|
-
if (url.includes("?")) url = `${url}&error=${error}`;
|
|
147
|
-
else url = `${url}?error=${error}`;
|
|
146
|
+
if (url.includes("?")) url = `${url}&error=${encodeURIComponent(error)}`;
|
|
147
|
+
else url = `${url}?error=${encodeURIComponent(error)}`;
|
|
148
148
|
throw ctx.redirect(url);
|
|
149
149
|
}
|
|
150
150
|
let finalTokenUrl = providerConfig.tokenUrl;
|
|
@@ -45,8 +45,9 @@ const oneTap = (options) => ({
|
|
|
45
45
|
} catch {
|
|
46
46
|
throw new APIError("BAD_REQUEST", { message: "invalid id token" });
|
|
47
47
|
}
|
|
48
|
-
const { email, email_verified, name, picture, sub } = payload;
|
|
49
|
-
if (!
|
|
48
|
+
const { email: rawEmail, email_verified, name, picture, sub } = payload;
|
|
49
|
+
if (!rawEmail) return ctx.json({ error: "Email not available in token" });
|
|
50
|
+
const email = rawEmail.toLowerCase();
|
|
50
51
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
51
52
|
if (!user) {
|
|
52
53
|
if (options?.disableSignup) throw new APIError("BAD_GATEWAY", { message: "User not found" });
|