@better-auth/core 1.7.0-beta.0 → 1.7.0-beta.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/dist/api/index.d.mts +44 -1
- package/dist/api/index.mjs +69 -4
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +91 -3
- package/dist/db/adapter/get-id-field.mjs +1 -1
- package/dist/db/adapter/index.d.mts +87 -3
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +2 -1
- package/dist/db/type.d.mts +17 -0
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +1 -0
- package/dist/error/codes.mjs +1 -0
- package/dist/error/index.d.mts +7 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/api.mjs +12 -0
- package/dist/instrumentation/noop.mjs +42 -0
- package/dist/instrumentation/pure.index.d.mts +7 -0
- package/dist/instrumentation/pure.index.mjs +7 -0
- package/dist/instrumentation/tracer.mjs +6 -3
- package/dist/oauth2/authorization-params.d.mts +12 -0
- package/dist/oauth2/authorization-params.mjs +12 -0
- package/dist/oauth2/basic-credentials.d.mts +30 -0
- package/dist/oauth2/basic-credentials.mjs +64 -0
- package/dist/oauth2/client-assertion.d.mts +38 -22
- package/dist/oauth2/client-assertion.mjs +63 -28
- package/dist/oauth2/client-credentials-token.d.mts +19 -40
- package/dist/oauth2/client-credentials-token.mjs +20 -31
- package/dist/oauth2/create-authorization-url.d.mts +11 -1
- package/dist/oauth2/create-authorization-url.mjs +27 -7
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +14 -9
- package/dist/oauth2/index.mjs +12 -8
- package/dist/oauth2/oauth-provider.d.mts +150 -10
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +38 -36
- package/dist/oauth2/reject-redirects.mjs +65 -0
- package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
- package/dist/oauth2/token-endpoint-auth.mjs +89 -0
- package/dist/oauth2/utils.d.mts +23 -1
- package/dist/oauth2/utils.mjs +48 -2
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +28 -34
- package/dist/oauth2/verify-id-token.d.mts +27 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +88 -15
- package/dist/oauth2/verify.mjs +203 -25
- package/dist/social-providers/apple.d.mts +16 -4
- package/dist/social-providers/apple.mjs +17 -24
- package/dist/social-providers/atlassian.d.mts +5 -1
- package/dist/social-providers/atlassian.mjs +6 -3
- package/dist/social-providers/cognito.d.mts +27 -3
- package/dist/social-providers/cognito.mjs +15 -25
- package/dist/social-providers/discord.d.mts +7 -3
- package/dist/social-providers/discord.mjs +16 -3
- package/dist/social-providers/dropbox.d.mts +5 -1
- package/dist/social-providers/dropbox.mjs +5 -4
- package/dist/social-providers/facebook.d.mts +24 -5
- package/dist/social-providers/facebook.mjs +54 -20
- package/dist/social-providers/figma.d.mts +5 -1
- package/dist/social-providers/figma.mjs +4 -3
- package/dist/social-providers/github.d.mts +6 -2
- package/dist/social-providers/github.mjs +5 -4
- package/dist/social-providers/gitlab.d.mts +5 -1
- package/dist/social-providers/gitlab.mjs +3 -2
- package/dist/social-providers/google.d.mts +53 -5
- package/dist/social-providers/google.mjs +69 -25
- package/dist/social-providers/huggingface.d.mts +5 -1
- package/dist/social-providers/huggingface.mjs +3 -2
- package/dist/social-providers/index.d.mts +221 -45
- package/dist/social-providers/index.mjs +2 -2
- package/dist/social-providers/kakao.d.mts +5 -1
- package/dist/social-providers/kakao.mjs +3 -2
- package/dist/social-providers/kick.d.mts +5 -1
- package/dist/social-providers/kick.mjs +3 -2
- package/dist/social-providers/line.d.mts +8 -2
- package/dist/social-providers/line.mjs +5 -6
- package/dist/social-providers/linear.d.mts +5 -1
- package/dist/social-providers/linear.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +7 -3
- package/dist/social-providers/linkedin.mjs +4 -3
- package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
- package/dist/social-providers/microsoft-entra-id.mjs +37 -28
- package/dist/social-providers/naver.d.mts +5 -1
- package/dist/social-providers/naver.mjs +3 -2
- package/dist/social-providers/notion.d.mts +5 -1
- package/dist/social-providers/notion.mjs +5 -2
- package/dist/social-providers/paybin.d.mts +5 -1
- package/dist/social-providers/paybin.mjs +4 -3
- package/dist/social-providers/paypal.d.mts +6 -2
- package/dist/social-providers/paypal.mjs +19 -14
- package/dist/social-providers/polar.d.mts +5 -1
- package/dist/social-providers/polar.mjs +3 -2
- package/dist/social-providers/railway.d.mts +5 -1
- package/dist/social-providers/railway.mjs +3 -2
- package/dist/social-providers/reddit.d.mts +5 -1
- package/dist/social-providers/reddit.mjs +7 -5
- package/dist/social-providers/roblox.d.mts +6 -2
- package/dist/social-providers/roblox.mjs +12 -2
- package/dist/social-providers/salesforce.d.mts +5 -1
- package/dist/social-providers/salesforce.mjs +4 -3
- package/dist/social-providers/slack.d.mts +6 -2
- package/dist/social-providers/slack.mjs +11 -8
- package/dist/social-providers/spotify.d.mts +5 -1
- package/dist/social-providers/spotify.mjs +3 -2
- package/dist/social-providers/tiktok.d.mts +5 -1
- package/dist/social-providers/tiktok.mjs +14 -2
- package/dist/social-providers/twitch.d.mts +5 -1
- package/dist/social-providers/twitch.mjs +3 -2
- package/dist/social-providers/twitter.d.mts +4 -1
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/vercel.d.mts +5 -1
- package/dist/social-providers/vercel.mjs +3 -2
- package/dist/social-providers/vk.d.mts +5 -1
- package/dist/social-providers/vk.mjs +3 -2
- package/dist/social-providers/wechat.d.mts +5 -1
- package/dist/social-providers/wechat.mjs +8 -2
- package/dist/social-providers/zoom.d.mts +5 -1
- package/dist/social-providers/zoom.mjs +10 -17
- package/dist/types/context.d.mts +55 -6
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +182 -12
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/async.d.mts +22 -0
- package/dist/utils/async.mjs +32 -0
- package/dist/utils/host.d.mts +147 -0
- package/dist/utils/host.mjs +298 -0
- package/dist/utils/ip.d.mts +28 -5
- package/dist/utils/ip.mjs +118 -4
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +5 -1
- package/dist/utils/string.mjs +20 -1
- package/dist/utils/url.d.mts +18 -1
- package/dist/utils/url.mjs +34 -4
- package/package.json +26 -16
- package/src/api/index.ts +121 -5
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +193 -7
- package/src/db/adapter/get-id-field.ts +2 -2
- package/src/db/adapter/index.ts +85 -2
- package/src/db/adapter/types.ts +2 -0
- package/src/db/get-tables.ts +9 -1
- package/src/db/schema/account.ts +4 -1
- package/src/db/schema/user.ts +3 -0
- package/src/db/type.ts +17 -0
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +1 -0
- package/src/error/index.ts +9 -0
- package/src/instrumentation/api.ts +17 -0
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/instrumentation/tracer.ts +8 -3
- package/src/oauth2/authorization-params.ts +28 -0
- package/src/oauth2/basic-credentials.ts +87 -0
- package/src/oauth2/client-assertion.ts +131 -58
- package/src/oauth2/client-credentials-token.ts +50 -74
- package/src/oauth2/create-authorization-url.ts +33 -7
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +81 -11
- package/src/oauth2/oauth-provider.ts +159 -11
- package/src/oauth2/refresh-access-token.ts +82 -83
- package/src/oauth2/reject-redirects.ts +75 -0
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +72 -5
- package/src/oauth2/validate-authorization-code.ts +69 -89
- package/src/oauth2/verify-id-token.ts +115 -0
- package/src/oauth2/verify.ts +409 -78
- package/src/social-providers/apple.ts +30 -40
- package/src/social-providers/atlassian.ts +8 -1
- package/src/social-providers/cognito.ts +36 -39
- package/src/social-providers/discord.ts +22 -18
- package/src/social-providers/dropbox.ts +7 -5
- package/src/social-providers/facebook.ts +108 -52
- package/src/social-providers/figma.ts +8 -1
- package/src/social-providers/github.ts +5 -3
- package/src/social-providers/gitlab.ts +2 -0
- package/src/social-providers/google.ts +141 -39
- package/src/social-providers/huggingface.ts +8 -1
- package/src/social-providers/kakao.ts +2 -1
- package/src/social-providers/kick.ts +8 -1
- package/src/social-providers/line.ts +25 -27
- package/src/social-providers/linear.ts +8 -1
- package/src/social-providers/linkedin.ts +5 -3
- package/src/social-providers/microsoft-entra-id.ts +100 -55
- package/src/social-providers/naver.ts +2 -1
- package/src/social-providers/notion.ts +8 -1
- package/src/social-providers/paybin.ts +2 -0
- package/src/social-providers/paypal.ts +28 -17
- package/src/social-providers/polar.ts +8 -1
- package/src/social-providers/railway.ts +8 -1
- package/src/social-providers/reddit.ts +9 -4
- package/src/social-providers/roblox.ts +16 -11
- package/src/social-providers/salesforce.ts +8 -1
- package/src/social-providers/slack.ts +15 -9
- package/src/social-providers/spotify.ts +8 -1
- package/src/social-providers/tiktok.ts +22 -9
- package/src/social-providers/twitch.ts +2 -1
- package/src/social-providers/twitter.ts +1 -0
- package/src/social-providers/vercel.ts +8 -1
- package/src/social-providers/vk.ts +8 -1
- package/src/social-providers/wechat.ts +17 -2
- package/src/social-providers/zoom.ts +15 -19
- package/src/types/context.ts +57 -5
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +204 -14
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/async.ts +53 -0
- package/src/utils/host.ts +425 -0
- package/src/utils/ip.ts +197 -13
- package/src/utils/is-api-error.ts +10 -0
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +38 -4
package/src/types/context.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { Awaitable, LiteralString } from "./helper";
|
|
|
16
16
|
import type {
|
|
17
17
|
BetterAuthOptions,
|
|
18
18
|
BetterAuthRateLimitOptions,
|
|
19
|
+
UserProvisioningSource,
|
|
19
20
|
} from "./init-options";
|
|
20
21
|
import type { BetterAuthPlugin } from "./plugin";
|
|
21
22
|
import type { SecretConfig } from "./secret";
|
|
@@ -97,6 +98,11 @@ export interface InternalAdapter<
|
|
|
97
98
|
user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> &
|
|
98
99
|
Partial<User> &
|
|
99
100
|
Record<string, any>,
|
|
101
|
+
/**
|
|
102
|
+
* Provisioning source. The creation seam adds `action: "create-user"` and
|
|
103
|
+
* runs the `user.validateUserInfo` gate.
|
|
104
|
+
*/
|
|
105
|
+
source: UserProvisioningSource,
|
|
100
106
|
): Promise<T & User>;
|
|
101
107
|
|
|
102
108
|
createAccount<T extends Record<string, any>>(
|
|
@@ -151,9 +157,22 @@ export interface InternalAdapter<
|
|
|
151
157
|
|
|
152
158
|
deleteAccounts(userId: string): Promise<void>;
|
|
153
159
|
|
|
154
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Delete an account by its primary key.
|
|
162
|
+
*
|
|
163
|
+
* @param id - The account row's primary key (the `id` column, not the `accountId` column).
|
|
164
|
+
*/
|
|
165
|
+
deleteAccount(id: string): Promise<void>;
|
|
155
166
|
|
|
156
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Delete every session belonging to a user.
|
|
169
|
+
*/
|
|
170
|
+
deleteUserSessions(userId: string): Promise<void>;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Delete sessions by their session tokens.
|
|
174
|
+
*/
|
|
175
|
+
deleteSessions(sessionTokens: string[]): Promise<void>;
|
|
157
176
|
|
|
158
177
|
findOAuthUser(
|
|
159
178
|
email: string,
|
|
@@ -191,8 +210,6 @@ export interface InternalAdapter<
|
|
|
191
210
|
|
|
192
211
|
findAccounts(userId: string): Promise<Account[]>;
|
|
193
212
|
|
|
194
|
-
findAccount(accountId: string): Promise<Account | null>;
|
|
195
|
-
|
|
196
213
|
findAccountByProviderId(
|
|
197
214
|
accountId: string,
|
|
198
215
|
providerId: string,
|
|
@@ -211,10 +228,45 @@ export interface InternalAdapter<
|
|
|
211
228
|
|
|
212
229
|
deleteVerificationByIdentifier(identifier: string): Promise<void>;
|
|
213
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Atomically consume a single-use verification row by `identifier` and
|
|
233
|
+
* return it. Only the first concurrent caller receives the latest row;
|
|
234
|
+
* subsequent callers receive `null`. Consuming one row invalidates the
|
|
235
|
+
* whole identifier so stale rows cannot be replayed. Rows past their
|
|
236
|
+
* `expiresAt` are treated as already invalid: the row is deleted but
|
|
237
|
+
* `null` is returned, so callers do not need to gate on `expiresAt`
|
|
238
|
+
* themselves. Callers MUST gate any state change (issue session, mint
|
|
239
|
+
* token, change password) on a non-null result.
|
|
240
|
+
*
|
|
241
|
+
* Replaces the racy `findVerificationValue` + `deleteVerificationByIdentifier`
|
|
242
|
+
* pair at single-use credential consumption sites.
|
|
243
|
+
*/
|
|
244
|
+
consumeVerificationValue(identifier: string): Promise<Verification | null>;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* First-writer-wins create keyed by a deterministic primary key derived from
|
|
248
|
+
* `identifier`. Returns `true` when this caller created the row and `false`
|
|
249
|
+
* when a row for the same identifier already existed.
|
|
250
|
+
*
|
|
251
|
+
* The dual of `consumeVerificationValue`: reserve races to create a marker
|
|
252
|
+
* exactly once, where consume races to delete one exactly once. Use it for
|
|
253
|
+
* replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
|
|
254
|
+
* wins. The database path is atomic via the primary key. Secondary-storage-only
|
|
255
|
+
* verification is not supported for reservation and runtime implementations
|
|
256
|
+
* should fail closed unless verification is backed by the database.
|
|
257
|
+
*/
|
|
258
|
+
reserveVerificationValue(data: {
|
|
259
|
+
identifier: string;
|
|
260
|
+
value: string;
|
|
261
|
+
expiresAt: Date;
|
|
262
|
+
}): Promise<boolean>;
|
|
263
|
+
|
|
214
264
|
updateVerificationByIdentifier(
|
|
215
265
|
identifier: string,
|
|
216
266
|
data: Partial<Verification>,
|
|
217
267
|
): Promise<Verification>;
|
|
268
|
+
|
|
269
|
+
refreshUserSessions(user: User): Promise<void>;
|
|
218
270
|
}
|
|
219
271
|
|
|
220
272
|
type CreateCookieGetterFn = (
|
|
@@ -299,7 +351,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
|
|
|
299
351
|
* - "cookie": Store state in an encrypted cookie (stateless)
|
|
300
352
|
* - "database": Store state in the database
|
|
301
353
|
*
|
|
302
|
-
* @default "cookie"
|
|
354
|
+
* @default "database" when `database` or `secondaryStorage` is configured, "cookie" otherwise
|
|
303
355
|
*/
|
|
304
356
|
storeStateStrategy: "database" | "cookie";
|
|
305
357
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -24,6 +24,13 @@ export type {
|
|
|
24
24
|
DynamicBaseURLConfig,
|
|
25
25
|
GenerateIdFn,
|
|
26
26
|
StoreIdentifierOption,
|
|
27
|
+
UserProvisioningSource,
|
|
28
|
+
ValidateUserInfoAction,
|
|
29
|
+
ValidateUserInfoMethod,
|
|
30
|
+
ValidateUserInfoOAuthInfo,
|
|
31
|
+
ValidateUserInfoResult,
|
|
32
|
+
ValidateUserInfoSource,
|
|
33
|
+
ValidateUserInfoSSOInfo,
|
|
27
34
|
} from "./init-options";
|
|
28
35
|
export type {
|
|
29
36
|
BetterAuthPlugin,
|
|
@@ -14,7 +14,6 @@ import type {
|
|
|
14
14
|
Account,
|
|
15
15
|
DBFieldAttribute,
|
|
16
16
|
ModelNames,
|
|
17
|
-
RateLimit,
|
|
18
17
|
SecondaryStorage,
|
|
19
18
|
Session,
|
|
20
19
|
User,
|
|
@@ -47,6 +46,98 @@ export type GenerateIdFn = (options: {
|
|
|
47
46
|
size?: number | undefined;
|
|
48
47
|
}) => string | false;
|
|
49
48
|
|
|
49
|
+
/**
|
|
50
|
+
* What Better Auth is about to do with an incoming identity when
|
|
51
|
+
* {@link BetterAuthOptions.user}'s `validateUserInfo` runs.
|
|
52
|
+
*
|
|
53
|
+
* - `create-user`: a brand-new user record is about to be created.
|
|
54
|
+
* - `link-account`: a new provider account is about to be linked to an
|
|
55
|
+
* already-existing user.
|
|
56
|
+
* - `sign-in`: an existing OAuth or SSO user is signing in again. This is the
|
|
57
|
+
* one case where the provider can assert *changed* data, so the hook receives
|
|
58
|
+
* the fresh provider email and profile (not the stored row), letting a domain
|
|
59
|
+
* or org policy reject a user whose provider identity moved out of bounds.
|
|
60
|
+
*
|
|
61
|
+
* Non-provider returning sign-ins are not re-validated: they carry only the
|
|
62
|
+
* stored row, which has not changed since `create-user` gated it. Use the admin
|
|
63
|
+
* plugin's ban controls or a `databaseHooks.session.create.before` hook to
|
|
64
|
+
* block those.
|
|
65
|
+
*/
|
|
66
|
+
export type ValidateUserInfoAction = "create-user" | "link-account" | "sign-in";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The authentication method that produced the incoming user info. The named
|
|
70
|
+
* methods cover Better Auth's built-ins; the open `string` keeps it extensible
|
|
71
|
+
* for plugins (for example `"scim"`).
|
|
72
|
+
*/
|
|
73
|
+
export type ValidateUserInfoMethod =
|
|
74
|
+
| "oauth"
|
|
75
|
+
| "sso-oidc"
|
|
76
|
+
| "sso-saml"
|
|
77
|
+
| "email-password"
|
|
78
|
+
| "magic-link"
|
|
79
|
+
| "email-otp"
|
|
80
|
+
| "anonymous"
|
|
81
|
+
| "siwe"
|
|
82
|
+
| "phone-number"
|
|
83
|
+
| "admin"
|
|
84
|
+
| (string & {});
|
|
85
|
+
|
|
86
|
+
/** OAuth-specific provisioning context; present only when `method` is `"oauth"`. */
|
|
87
|
+
export type ValidateUserInfoOAuthInfo = {
|
|
88
|
+
/** The social or generic OAuth provider id (e.g. `"google"`). */
|
|
89
|
+
providerId: string;
|
|
90
|
+
/** The raw provider profile (userinfo or id-token claims), unmapped. */
|
|
91
|
+
profile?: Record<string, unknown> | undefined;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** SSO-specific provisioning context; present for OIDC and SAML SSO methods. */
|
|
95
|
+
export type ValidateUserInfoSSOInfo = {
|
|
96
|
+
/** The configured SSO provider id. */
|
|
97
|
+
providerId: string;
|
|
98
|
+
/** The raw OIDC claims or SAML assertion attributes, unmapped. */
|
|
99
|
+
profile?: Record<string, unknown> | undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Provisioning origin passed to `createUser`; the creation seam adds `action: "create-user"` to build {@link ValidateUserInfoSource}. */
|
|
103
|
+
export type UserProvisioningSource = {
|
|
104
|
+
method: ValidateUserInfoMethod;
|
|
105
|
+
/** Provider id and raw profile; present iff `method` is `"oauth"`. */
|
|
106
|
+
oauth?: ValidateUserInfoOAuthInfo | undefined;
|
|
107
|
+
/** Provider id and raw profile; present iff `method` is `"sso-oidc"` or `"sso-saml"`. */
|
|
108
|
+
sso?: ValidateUserInfoSSOInfo | undefined;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The context passed to `validateUserInfo`: the lifecycle
|
|
113
|
+
* {@link ValidateUserInfoAction}, the {@link ValidateUserInfoMethod}, and (for
|
|
114
|
+
* OAuth/SSO provider methods) protocol-specific provider metadata.
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* // Scope to one OAuth provider:
|
|
118
|
+
* if (source.oauth?.providerId !== "google") return;
|
|
119
|
+
* // Branch on the method:
|
|
120
|
+
* if (source.method === "anonymous") return { error: "no_anonymous" };
|
|
121
|
+
* // Inspect SSO claims:
|
|
122
|
+
* if (source.method === "sso-saml" && source.sso?.profile?.department !== "eng") {
|
|
123
|
+
* return { error: "invalid_department" };
|
|
124
|
+
* }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export type ValidateUserInfoSource = UserProvisioningSource & {
|
|
128
|
+
action: ValidateUserInfoAction;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type ValidateUserInfoResult = {
|
|
132
|
+
/** A short, machine-readable rejection code, surfaced to the client. */
|
|
133
|
+
error: string;
|
|
134
|
+
/**
|
|
135
|
+
* A human-readable reason, surfaced to the client. Do not put sensitive
|
|
136
|
+
* details here.
|
|
137
|
+
*/
|
|
138
|
+
errorDescription?: string | undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
50
141
|
/**
|
|
51
142
|
* Configuration for dynamic base URL resolution.
|
|
52
143
|
* Allows Better Auth to work with multiple domains (e.g., Vercel preview deployments).
|
|
@@ -95,12 +186,27 @@ export type DynamicBaseURLConfig = {
|
|
|
95
186
|
export type BaseURLConfig = string | DynamicBaseURLConfig;
|
|
96
187
|
|
|
97
188
|
export interface BetterAuthRateLimitStorage {
|
|
98
|
-
|
|
99
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Atomically records one request against `key` within the rolling `window`
|
|
191
|
+
* (in seconds) and reports whether it is allowed.
|
|
192
|
+
*
|
|
193
|
+
* When `allowed` is true the count was incremented within the active window,
|
|
194
|
+
* or the window had elapsed and was reset to start at 1. When `allowed` is
|
|
195
|
+
* false the limit was already reached and `retryAfter` is the number of
|
|
196
|
+
* seconds until the window frees up.
|
|
197
|
+
*
|
|
198
|
+
* Performing the check and the increment in a single step closes the
|
|
199
|
+
* concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
|
|
200
|
+
* requests can no longer all pass a stale read before any increment lands.
|
|
201
|
+
*
|
|
202
|
+
* Custom storages must implement this operation directly. Better Auth no
|
|
203
|
+
* longer accepts separate `get`/`set` rate-limit storage because that shape
|
|
204
|
+
* cannot enforce a distributed limit under concurrent requests.
|
|
205
|
+
*/
|
|
206
|
+
consume: (
|
|
100
207
|
key: string,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
) => Promise<void>;
|
|
208
|
+
rule: { window: number; max: number },
|
|
209
|
+
) => Promise<{ allowed: boolean; retryAfter: number | null }>;
|
|
104
210
|
}
|
|
105
211
|
|
|
106
212
|
export type BetterAuthRateLimitRule = {
|
|
@@ -197,7 +303,7 @@ export type BetterAuthAdvancedOptions = {
|
|
|
197
303
|
* @example ["x-client-ip", "x-forwarded-for", "cf-connecting-ip"]
|
|
198
304
|
*
|
|
199
305
|
* @default
|
|
200
|
-
* @link https://github.com/better-auth/better-auth/blob/main/packages/
|
|
306
|
+
* @link https://github.com/better-auth/better-auth/blob/main/packages/core/src/utils/ip.ts
|
|
201
307
|
*/
|
|
202
308
|
ipAddressHeaders?: string[];
|
|
203
309
|
/**
|
|
@@ -207,12 +313,28 @@ export type BetterAuthAdvancedOptions = {
|
|
|
207
313
|
*/
|
|
208
314
|
disableIpTracking?: boolean;
|
|
209
315
|
/**
|
|
210
|
-
* IPv6
|
|
211
|
-
*
|
|
316
|
+
* IPv6 prefix length used to collapse addresses before rate-limit keying.
|
|
317
|
+
* Any integer from 0 to 128 is accepted; common values are 32, 48, 56, 64, 128.
|
|
318
|
+
* Out-of-range values fall back to safe behavior (negative -> mask all, > 128 -> no mask).
|
|
212
319
|
*
|
|
213
320
|
* @default 64
|
|
214
321
|
*/
|
|
215
|
-
ipv6Subnet?:
|
|
322
|
+
ipv6Subnet?: number;
|
|
323
|
+
/**
|
|
324
|
+
* Trusted reverse-proxy IPs or CIDR ranges. When set, a forwarded IP
|
|
325
|
+
* chain is walked right to left, trusted hops are skipped, and the
|
|
326
|
+
* first untrusted address is the client IP. Unset trusts only
|
|
327
|
+
* single-value IP headers. Use the actual address or subnet of your
|
|
328
|
+
* proxies, not a broad private range that also covers clients.
|
|
329
|
+
*
|
|
330
|
+
* This only interprets the forwarded header chain and cannot verify
|
|
331
|
+
* the direct sender. It is safe only when your origin is reachable
|
|
332
|
+
* through these proxies and clients cannot set forwarded headers
|
|
333
|
+
* directly.
|
|
334
|
+
*
|
|
335
|
+
* @example ["192.0.2.10", "10.0.0.0/24"]
|
|
336
|
+
*/
|
|
337
|
+
trustedProxies?: string[];
|
|
216
338
|
}
|
|
217
339
|
| undefined;
|
|
218
340
|
/**
|
|
@@ -776,6 +898,33 @@ export type BetterAuthOptions = {
|
|
|
776
898
|
*/
|
|
777
899
|
user?:
|
|
778
900
|
| (BetterAuthDBOptions<"user", keyof BaseUser> & {
|
|
901
|
+
/**
|
|
902
|
+
* Gate which identities Better Auth admits. Called just before
|
|
903
|
+
* `create-user`, `link-account`, and (for OAuth) `sign-in`, across
|
|
904
|
+
* every authentication method, including stateless setups with no
|
|
905
|
+
* persistent database. On `sign-in` the hook receives the *fresh*
|
|
906
|
+
* provider email and profile, so a domain policy can reject a user
|
|
907
|
+
* whose provider identity moved out of bounds.
|
|
908
|
+
*
|
|
909
|
+
* Non-provider returning sign-ins are not re-validated; use the admin
|
|
910
|
+
* plugin's ban controls or a `databaseHooks.session.create.before`
|
|
911
|
+
* hook for those.
|
|
912
|
+
*
|
|
913
|
+
* Return nothing to allow; return `{ error }` to reject. Browser flows
|
|
914
|
+
* redirect to the configured error URL; programmatic flows surface a
|
|
915
|
+
* `403`.
|
|
916
|
+
*
|
|
917
|
+
* TODO: rename to `validateUser` (and the `ValidateUserInfo*` types).
|
|
918
|
+
* "UserInfo" is the OIDC term and misleads for the email/password,
|
|
919
|
+
* SIWE, phone, and admin methods.
|
|
920
|
+
*/
|
|
921
|
+
validateUserInfo?: (
|
|
922
|
+
data: {
|
|
923
|
+
user: Partial<User> & Record<string, unknown>;
|
|
924
|
+
source: ValidateUserInfoSource;
|
|
925
|
+
},
|
|
926
|
+
context: GenericEndpointContext,
|
|
927
|
+
) => Awaitable<void | ValidateUserInfoResult>;
|
|
779
928
|
/**
|
|
780
929
|
* Changing email configuration
|
|
781
930
|
*/
|
|
@@ -925,6 +1074,20 @@ export type BetterAuthOptions = {
|
|
|
925
1074
|
* @default "compact"
|
|
926
1075
|
*/
|
|
927
1076
|
strategy?: "compact" | "jwt" | "jwe";
|
|
1077
|
+
/**
|
|
1078
|
+
* JWT-specific configuration for `strategy: "jwt"`.
|
|
1079
|
+
*/
|
|
1080
|
+
jwt?: {
|
|
1081
|
+
/**
|
|
1082
|
+
* Which signing key is used for cookie-cache JWTs.
|
|
1083
|
+
*
|
|
1084
|
+
* - `"secret"`: uses the Better Auth secret with HS256.
|
|
1085
|
+
* - `"jwt-plugin"`: uses the installed `jwt()` plugin's asymmetric signing keys.
|
|
1086
|
+
*
|
|
1087
|
+
* @default "secret"
|
|
1088
|
+
*/
|
|
1089
|
+
signingKey?: "secret" | "jwt-plugin";
|
|
1090
|
+
};
|
|
928
1091
|
/**
|
|
929
1092
|
* Controls stateless cookie cache refresh behavior.
|
|
930
1093
|
*
|
|
@@ -1020,6 +1183,25 @@ export type BetterAuthOptions = {
|
|
|
1020
1183
|
* @default false
|
|
1021
1184
|
*/
|
|
1022
1185
|
disableImplicitLinking?: boolean;
|
|
1186
|
+
/**
|
|
1187
|
+
* Require the existing local user row to have
|
|
1188
|
+
* `emailVerified: true` before implicit account linking
|
|
1189
|
+
* uses the IdP's `email_verified` claim as ownership
|
|
1190
|
+
* proof. Defaults to `true` so an attacker who
|
|
1191
|
+
* pre-registers an unverified account at a victim's
|
|
1192
|
+
* email cannot have the victim's OAuth identity linked
|
|
1193
|
+
* into the attacker-owned row on first sign-in. Set to
|
|
1194
|
+
* `false` for backward compatibility on apps whose
|
|
1195
|
+
* users sign up via OAuth without verifying their email
|
|
1196
|
+
* locally; understand the takeover risk before doing
|
|
1197
|
+
* so.
|
|
1198
|
+
*
|
|
1199
|
+
* @default true
|
|
1200
|
+
*
|
|
1201
|
+
* @deprecated The option will be removed on the next
|
|
1202
|
+
* minor; the gate will become unconditional.
|
|
1203
|
+
*/
|
|
1204
|
+
requireLocalEmailVerified?: boolean;
|
|
1023
1205
|
/**
|
|
1024
1206
|
* List of trusted providers. Can be a static array or a function
|
|
1025
1207
|
* that returns providers dynamically. The function is called
|
|
@@ -1073,7 +1255,11 @@ export type BetterAuthOptions = {
|
|
|
1073
1255
|
*/
|
|
1074
1256
|
allowUnlinkingAll?: boolean;
|
|
1075
1257
|
/**
|
|
1076
|
-
*
|
|
1258
|
+
* When enabled, linking an account copies the provider's profile onto
|
|
1259
|
+
* the local user, matching the fields persisted on sign-up (`name`,
|
|
1260
|
+
* `image`, and any `mapProfileToUser` fields). The local `email` and
|
|
1261
|
+
* `emailVerified` are never changed, so a link cannot rebind the
|
|
1262
|
+
* account's identity.
|
|
1077
1263
|
*
|
|
1078
1264
|
* @default false
|
|
1079
1265
|
*/
|
|
@@ -1106,13 +1292,17 @@ export type BetterAuthOptions = {
|
|
|
1106
1292
|
* - "cookie": Store state in an encrypted cookie (stateless)
|
|
1107
1293
|
* - "database": Store state in the database
|
|
1108
1294
|
*
|
|
1109
|
-
* @default "cookie"
|
|
1295
|
+
* @default "database" when `database` or `secondaryStorage` is configured, "cookie" otherwise
|
|
1110
1296
|
*/
|
|
1111
1297
|
storeStateStrategy?: "database" | "cookie";
|
|
1112
1298
|
/**
|
|
1113
|
-
* Store account data after
|
|
1299
|
+
* Store provider account data after an OAuth flow in an encrypted
|
|
1300
|
+
* cookie. This includes OAuth token material such as access tokens,
|
|
1301
|
+
* refresh tokens, ID tokens, scopes, and token expiry.
|
|
1114
1302
|
*
|
|
1115
|
-
* This is useful for database-less
|
|
1303
|
+
* This is useful for database-less flows, but large provider tokens can
|
|
1304
|
+
* still hit browser or proxy cookie/header limits even though Better Auth
|
|
1305
|
+
* chunks oversized account cookies.
|
|
1116
1306
|
*
|
|
1117
1307
|
* @default false
|
|
1118
1308
|
*
|
|
@@ -6,7 +6,21 @@ import type {
|
|
|
6
6
|
import type { Atom, WritableAtom } from "nanostores";
|
|
7
7
|
import type { LiteralString } from "./helper";
|
|
8
8
|
import type { BetterAuthOptions } from "./init-options";
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
type InferableServerPlugin = {
|
|
11
|
+
id?: LiteralString | undefined;
|
|
12
|
+
endpoints?: Record<string, unknown> | undefined;
|
|
13
|
+
schema?: Record<string, { fields: Record<string, unknown> }> | undefined;
|
|
14
|
+
$ERROR_CODES?:
|
|
15
|
+
| Record<
|
|
16
|
+
string,
|
|
17
|
+
{
|
|
18
|
+
readonly code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
>
|
|
22
|
+
| undefined;
|
|
23
|
+
};
|
|
10
24
|
|
|
11
25
|
export interface ClientStore {
|
|
12
26
|
notify: (signal: string) => void;
|
|
@@ -84,7 +98,7 @@ export interface BetterAuthClientPlugin {
|
|
|
84
98
|
* only used for type inference. don't pass the
|
|
85
99
|
* actual plugin
|
|
86
100
|
*/
|
|
87
|
-
$InferServerPlugin?:
|
|
101
|
+
$InferServerPlugin?: InferableServerPlugin | undefined;
|
|
88
102
|
/**
|
|
89
103
|
* Custom actions
|
|
90
104
|
*/
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Awaitable } from "../types/helper";
|
|
2
|
+
|
|
3
|
+
export interface MapConcurrentOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Max in-flight mappers. Non-integer values are floored, then clamped
|
|
6
|
+
* to the range `[1, items.length]`. `NaN` falls back to 1.
|
|
7
|
+
*/
|
|
8
|
+
concurrency: number;
|
|
9
|
+
/**
|
|
10
|
+
* Rejects with `signal.reason` when aborted. In-flight mappers keep
|
|
11
|
+
* running but their results are not returned.
|
|
12
|
+
*/
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run an async mapper over items with bounded concurrency.
|
|
18
|
+
* Preserves input order in the result. Fails fast on the first rejection.
|
|
19
|
+
*/
|
|
20
|
+
export async function mapConcurrent<T, R>(
|
|
21
|
+
items: readonly T[],
|
|
22
|
+
fn: (item: T, index: number) => Awaitable<R>,
|
|
23
|
+
options: MapConcurrentOptions,
|
|
24
|
+
): Promise<R[]> {
|
|
25
|
+
const n = items.length;
|
|
26
|
+
if (n === 0) return [];
|
|
27
|
+
|
|
28
|
+
const { signal } = options;
|
|
29
|
+
if (signal?.aborted) throw signal.reason;
|
|
30
|
+
|
|
31
|
+
const raw = Math.floor(options.concurrency);
|
|
32
|
+
const width = Math.min(n, raw >= 1 ? raw : 1);
|
|
33
|
+
|
|
34
|
+
const results = new Array<R>(n);
|
|
35
|
+
let idx = 0;
|
|
36
|
+
let failed = false;
|
|
37
|
+
|
|
38
|
+
const worker = async (): Promise<void> => {
|
|
39
|
+
while (!failed && idx < n) {
|
|
40
|
+
if (signal?.aborted) throw signal.reason;
|
|
41
|
+
const i = idx++;
|
|
42
|
+
try {
|
|
43
|
+
results[i] = await fn(items[i] as T, i);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
failed = true;
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
await Promise.all(Array.from({ length: width }, worker));
|
|
52
|
+
return results;
|
|
53
|
+
}
|