@better-auth/core 1.7.1 → 1.7.3
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 +3 -0
- package/dist/context/endpoint-context.d.mts +19 -5
- package/dist/context/endpoint-context.mjs +35 -16
- package/dist/context/global.mjs +5 -2
- package/dist/context/index.d.mts +2 -2
- package/dist/context/index.mjs +2 -2
- package/dist/context/transaction.mjs +3 -0
- package/dist/db/adapter/atomic-fallback.mjs +134 -0
- package/dist/db/adapter/factory.mjs +22 -4
- package/dist/db/adapter/index.d.mts +15 -11
- package/dist/db/get-tables.mjs +1 -9
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +2 -2
- package/dist/db/internal.d.mts +3 -1
- package/dist/db/internal.mjs +3 -1
- package/dist/db/schema/account.d.mts +2 -13
- package/dist/db/schema/account.mjs +1 -19
- package/dist/db/schema-check.d.mts +48 -0
- package/dist/db/schema-check.mjs +80 -0
- package/dist/db/schema-diff.d.mts +104 -0
- package/dist/db/schema-diff.mjs +154 -0
- package/dist/env/logger.mjs +16 -1
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/index.d.mts +2 -2
- package/dist/oauth2/oauth-provider.d.mts +0 -10
- package/dist/oauth2/token-endpoint-auth.d.mts +26 -2
- package/dist/oauth2/token-endpoint-auth.mjs +11 -0
- package/dist/social-providers/apple.d.mts +0 -1
- package/dist/social-providers/apple.mjs +0 -1
- package/dist/social-providers/cloudflare.d.mts +132 -0
- package/dist/social-providers/cloudflare.mjs +85 -0
- package/dist/social-providers/cognito.d.mts +0 -1
- package/dist/social-providers/cognito.mjs +0 -1
- package/dist/social-providers/facebook.d.mts +0 -1
- package/dist/social-providers/facebook.mjs +0 -1
- package/dist/social-providers/google.d.mts +0 -1
- package/dist/social-providers/google.mjs +0 -1
- package/dist/social-providers/index.d.mts +53 -21
- package/dist/social-providers/index.mjs +3 -1
- package/dist/social-providers/line.d.mts +0 -1
- package/dist/social-providers/line.mjs +0 -1
- package/dist/social-providers/microsoft-entra-id.d.mts +0 -3
- package/dist/social-providers/microsoft-entra-id.mjs +0 -1
- package/dist/social-providers/paybin.d.mts +0 -1
- package/dist/social-providers/paybin.mjs +0 -1
- package/dist/social-providers/paypal.d.mts +3 -11
- package/dist/social-providers/paypal.mjs +20 -47
- package/dist/social-providers/reddit.mjs +22 -23
- package/dist/social-providers/roblox.mjs +5 -1
- package/dist/social-providers/tiktok.d.mts +1 -0
- package/dist/social-providers/tiktok.mjs +19 -10
- package/dist/social-providers/twitter.mjs +5 -1
- package/dist/social-providers/wechat.mjs +6 -1
- package/dist/types/context.d.mts +11 -0
- package/dist/types/init-options.d.mts +11 -0
- package/dist/utils/ip.mjs +11 -9
- package/dist/utils/url.d.mts +10 -1
- package/dist/utils/url.mjs +21 -1
- package/package.json +3 -3
- package/src/context/endpoint-context.ts +46 -21
- package/src/context/global.ts +7 -0
- package/src/context/index.ts +2 -0
- package/src/context/transaction.ts +5 -0
- package/src/db/adapter/atomic-fallback.ts +237 -0
- package/src/db/adapter/factory.ts +33 -17
- package/src/db/adapter/index.ts +15 -11
- package/src/db/get-tables.ts +1 -14
- package/src/db/index.ts +0 -2
- package/src/db/internal.ts +19 -0
- package/src/db/schema/account.ts +3 -22
- package/src/db/schema/user.ts +1 -1
- package/src/db/schema-check.ts +107 -0
- package/src/db/schema-diff.ts +270 -0
- package/src/env/logger.ts +22 -1
- package/src/oauth2/index.ts +2 -0
- package/src/oauth2/oauth-provider.ts +0 -10
- package/src/oauth2/token-endpoint-auth.ts +39 -6
- package/src/social-providers/apple.ts +0 -1
- package/src/social-providers/cloudflare.ts +221 -0
- package/src/social-providers/cognito.ts +0 -1
- package/src/social-providers/facebook.ts +0 -1
- package/src/social-providers/google.ts +0 -1
- package/src/social-providers/index.ts +3 -0
- package/src/social-providers/line.ts +0 -1
- package/src/social-providers/microsoft-entra-id.ts +0 -1
- package/src/social-providers/paybin.ts +0 -1
- package/src/social-providers/paypal.ts +30 -71
- package/src/social-providers/reddit.ts +34 -37
- package/src/social-providers/roblox.ts +5 -3
- package/src/social-providers/tiktok.ts +25 -14
- package/src/social-providers/twitter.ts +8 -2
- package/src/social-providers/wechat.ts +6 -6
- package/src/types/context.ts +11 -0
- package/src/types/init-options.ts +11 -0
- package/src/utils/ip.ts +13 -9
- package/src/utils/url.ts +43 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
OAuthProvider,
|
|
4
|
+
ProviderOptions,
|
|
5
|
+
TokenEndpointAuth,
|
|
6
|
+
} from "../oauth2";
|
|
3
7
|
import {
|
|
4
8
|
RESERVED_AUTHORIZATION_PARAMS_SET,
|
|
5
9
|
refreshAccessToken,
|
|
6
10
|
validateAuthorizationCode,
|
|
7
11
|
} from "../oauth2";
|
|
12
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)
|
|
@@ -132,6 +137,13 @@ export interface TiktokOptions extends ProviderOptions<TiktokProfile> {
|
|
|
132
137
|
|
|
133
138
|
export const tiktok = (options: TiktokOptions) => {
|
|
134
139
|
const tokenEndpoint = "https://open.tiktokapis.com/v2/oauth/token/";
|
|
140
|
+
const tokenEndpointAuth = {
|
|
141
|
+
method: "custom",
|
|
142
|
+
customizeRequest({ body }) {
|
|
143
|
+
body.set("client_key", options.clientKey);
|
|
144
|
+
body.set("client_secret", options.clientSecret);
|
|
145
|
+
},
|
|
146
|
+
} satisfies TokenEndpointAuth;
|
|
135
147
|
return {
|
|
136
148
|
id: "tiktok",
|
|
137
149
|
name: "TikTok",
|
|
@@ -158,15 +170,14 @@ export const tiktok = (options: TiktokOptions) => {
|
|
|
158
170
|
return url;
|
|
159
171
|
},
|
|
160
172
|
|
|
161
|
-
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
173
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
162
174
|
return validateAuthorizationCode({
|
|
163
175
|
code,
|
|
176
|
+
codeVerifier,
|
|
164
177
|
redirectURI: options.redirectURI || redirectURI,
|
|
165
|
-
options: {
|
|
166
|
-
clientKey: options.clientKey,
|
|
167
|
-
clientSecret: options.clientSecret,
|
|
168
|
-
},
|
|
178
|
+
options: {},
|
|
169
179
|
tokenEndpoint,
|
|
180
|
+
tokenEndpointAuth,
|
|
170
181
|
});
|
|
171
182
|
},
|
|
172
183
|
refreshAccessToken: options.refreshAccessToken
|
|
@@ -174,14 +185,9 @@ export const tiktok = (options: TiktokOptions) => {
|
|
|
174
185
|
: async (refreshToken) => {
|
|
175
186
|
return refreshAccessToken({
|
|
176
187
|
refreshToken,
|
|
177
|
-
options: {
|
|
178
|
-
clientSecret: options.clientSecret,
|
|
179
|
-
},
|
|
188
|
+
options: {},
|
|
180
189
|
tokenEndpoint,
|
|
181
|
-
|
|
182
|
-
extraParams: {
|
|
183
|
-
client_key: options.clientKey,
|
|
184
|
-
},
|
|
190
|
+
tokenEndpointAuth,
|
|
185
191
|
});
|
|
186
192
|
},
|
|
187
193
|
async getUserInfo(token) {
|
|
@@ -210,7 +216,12 @@ export const tiktok = (options: TiktokOptions) => {
|
|
|
210
216
|
|
|
211
217
|
return {
|
|
212
218
|
user: {
|
|
213
|
-
email:
|
|
219
|
+
email:
|
|
220
|
+
profile.data.user.email ||
|
|
221
|
+
createPlaceholderEmail({
|
|
222
|
+
identifier: profile.data.user.open_id,
|
|
223
|
+
namespace: "tiktok",
|
|
224
|
+
}),
|
|
214
225
|
name:
|
|
215
226
|
profile.data.user.display_name || profile.data.user.username || "",
|
|
216
227
|
image: profile.data.user.avatar_large_url,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
refreshAccessToken,
|
|
6
6
|
validateAuthorizationCode,
|
|
7
7
|
} from "../oauth2";
|
|
8
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
8
9
|
|
|
9
10
|
export interface TwitterProfile {
|
|
10
11
|
data: {
|
|
@@ -187,9 +188,14 @@ export const twitter = (options: TwitterOption) => {
|
|
|
187
188
|
return {
|
|
188
189
|
user: {
|
|
189
190
|
name: profile.data.name,
|
|
190
|
-
email:
|
|
191
|
+
email:
|
|
192
|
+
profile.data.email ||
|
|
193
|
+
createPlaceholderEmail({
|
|
194
|
+
identifier: profile.data.id,
|
|
195
|
+
namespace: "twitter",
|
|
196
|
+
}),
|
|
191
197
|
image: profile.data.profile_image_url,
|
|
192
|
-
emailVerified
|
|
198
|
+
emailVerified,
|
|
193
199
|
...userMap,
|
|
194
200
|
},
|
|
195
201
|
data: profile,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
2
|
import type { OAuth2Tokens, OAuthProvider, ProviderOptions } from "../oauth2";
|
|
3
3
|
import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2";
|
|
4
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* WeChat user profile information
|
|
@@ -205,17 +206,16 @@ export const wechat = (options: WeChatOptions) => {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
209
|
+
const userId = profile.unionid || profile.openid || openid;
|
|
208
210
|
return {
|
|
209
211
|
user: {
|
|
210
212
|
name: profile.nickname,
|
|
211
|
-
// WeChat does not return an email, and the OAuth callback rejects a
|
|
212
|
-
// missing one, so the default sign-in would always fail. Synthesize a
|
|
213
|
-
// stable, non-routable placeholder (RFC 2606 `.invalid`) keyed to the
|
|
214
|
-
// user's WeChat id, left unverified. Applications that collect a real
|
|
215
|
-
// email override it via `mapProfileToUser`.
|
|
216
213
|
email:
|
|
217
214
|
profile.email ||
|
|
218
|
-
|
|
215
|
+
createPlaceholderEmail({
|
|
216
|
+
identifier: userId,
|
|
217
|
+
namespace: "wechat",
|
|
218
|
+
}),
|
|
219
219
|
image: profile.headimgurl,
|
|
220
220
|
emailVerified: false,
|
|
221
221
|
...userMap,
|
package/src/types/context.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
} from "../db";
|
|
11
11
|
import type { DBAdapter, Where } from "../db/adapter";
|
|
12
12
|
import type { AccountKey } from "../db/schema/account";
|
|
13
|
+
import type { SchemaCheck } from "../db/schema-check";
|
|
13
14
|
import type { createLogger } from "../env";
|
|
14
15
|
import type { OAuthProvider } from "../oauth2";
|
|
15
16
|
import type {
|
|
@@ -417,6 +418,16 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
|
|
|
417
418
|
"enabled" | "window" | "max" | "storage"
|
|
418
419
|
>;
|
|
419
420
|
adapter: DBAdapter<Options>;
|
|
421
|
+
/**
|
|
422
|
+
* Confirms the database can hold what this configuration writes.
|
|
423
|
+
*
|
|
424
|
+
* Shared by initialization and requests for this adapter instance;
|
|
425
|
+
* returns nothing once the schema is known to be clean.
|
|
426
|
+
* Context construction does not await the verdict, so migration
|
|
427
|
+
* tooling can still use a context whose schema needs repair.
|
|
428
|
+
* Absent when the check is disabled or the adapter registers none.
|
|
429
|
+
*/
|
|
430
|
+
checkSchema?: SchemaCheck | undefined;
|
|
420
431
|
internalAdapter: InternalAdapter<Options>;
|
|
421
432
|
createAuthCookie: CreateCookieGetterFn;
|
|
422
433
|
secret: string;
|
|
@@ -471,6 +471,17 @@ export type BetterAuthAdvancedOptions = {
|
|
|
471
471
|
* @default false
|
|
472
472
|
*/
|
|
473
473
|
joins?: boolean;
|
|
474
|
+
/**
|
|
475
|
+
* Validate the schema during initialization and report problems
|
|
476
|
+
* through the configured logger. Authentication requests await
|
|
477
|
+
* the same check and fail when the schema does not match.
|
|
478
|
+
* Kysely introspects the database; Drizzle and Prisma inspect
|
|
479
|
+
* local schema metadata without opening a connection.
|
|
480
|
+
* Set `false` to disable runtime schema validation.
|
|
481
|
+
*
|
|
482
|
+
* @default true
|
|
483
|
+
*/
|
|
484
|
+
validateSchema?: boolean;
|
|
474
485
|
}
|
|
475
486
|
| undefined;
|
|
476
487
|
/**
|
package/src/utils/ip.ts
CHANGED
|
@@ -2,6 +2,9 @@ import * as z from "zod";
|
|
|
2
2
|
import { isDevelopment, isTest } from "../env";
|
|
3
3
|
import type { BetterAuthOptions } from "../types";
|
|
4
4
|
|
|
5
|
+
const ipv4Schema = z.ipv4();
|
|
6
|
+
const ipv6Schema = z.ipv6();
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Normalizes an IP address for consistent rate limiting.
|
|
7
10
|
*
|
|
@@ -27,14 +30,15 @@ interface NormalizeIPOptions {
|
|
|
27
30
|
* Checks if an IP is valid IPv4 or IPv6
|
|
28
31
|
*/
|
|
29
32
|
export function isValidIP(ip: string): boolean {
|
|
30
|
-
return
|
|
33
|
+
return isIPv4(ip) || isIPv6(ip);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isIPv4(ip: string): boolean {
|
|
37
|
+
return z.validate(ipv4Schema, ip);
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
/**
|
|
34
|
-
* Checks if an IP is IPv6
|
|
35
|
-
*/
|
|
36
40
|
function isIPv6(ip: string): boolean {
|
|
37
|
-
return z.
|
|
41
|
+
return z.validate(ipv6Schema, ip);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -48,7 +52,7 @@ function extractIPv4FromMapped(ipv6: string): string | null {
|
|
|
48
52
|
if (lower.startsWith("::ffff:")) {
|
|
49
53
|
const ipv4Part = lower.substring(7);
|
|
50
54
|
// Check if it's a valid IPv4
|
|
51
|
-
if (
|
|
55
|
+
if (isIPv4(ipv4Part)) {
|
|
52
56
|
return ipv4Part;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -57,7 +61,7 @@ function extractIPv4FromMapped(ipv6: string): string | null {
|
|
|
57
61
|
const parts = ipv6.split(":");
|
|
58
62
|
if (parts.length === 7 && parts[5]?.toLowerCase() === "ffff") {
|
|
59
63
|
const ipv4Part = parts[6];
|
|
60
|
-
if (ipv4Part &&
|
|
64
|
+
if (ipv4Part && isIPv4(ipv4Part)) {
|
|
61
65
|
return ipv4Part;
|
|
62
66
|
}
|
|
63
67
|
}
|
|
@@ -176,7 +180,7 @@ export function normalizeIP(
|
|
|
176
180
|
options: NormalizeIPOptions = {},
|
|
177
181
|
): string {
|
|
178
182
|
// IPv4 addresses are already normalized
|
|
179
|
-
if (
|
|
183
|
+
if (isIPv4(ip)) {
|
|
180
184
|
return ip.toLowerCase();
|
|
181
185
|
}
|
|
182
186
|
|
|
@@ -201,7 +205,7 @@ export function normalizeIP(
|
|
|
201
205
|
* Raw bytes of an IP for CIDR comparison. Returns `null` for an invalid IP.
|
|
202
206
|
*/
|
|
203
207
|
function ipToBytes(ip: string): Uint8Array | null {
|
|
204
|
-
if (
|
|
208
|
+
if (isIPv4(ip)) {
|
|
205
209
|
return Uint8Array.from(ip.split(".").map((octet) => Number(octet)));
|
|
206
210
|
}
|
|
207
211
|
if (!isIPv6(ip)) {
|
package/src/utils/url.ts
CHANGED
|
@@ -48,6 +48,49 @@ export function normalizePathname(
|
|
|
48
48
|
return pathname;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const URL_REFERENCE_ORIGIN = "https://better-auth.invalid";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Appends query parameters before the fragment of an absolute or root-relative URL.
|
|
55
|
+
* Existing query text is retained without parsing it into name-value pairs.
|
|
56
|
+
*
|
|
57
|
+
* This function only composes URLs. Callers must validate untrusted input.
|
|
58
|
+
*
|
|
59
|
+
* @throws TypeError if parsing fails or a relative input changes authority.
|
|
60
|
+
*/
|
|
61
|
+
export function appendQueryParams(
|
|
62
|
+
input: string,
|
|
63
|
+
params: URLSearchParams,
|
|
64
|
+
): string {
|
|
65
|
+
const relative = input.startsWith("/");
|
|
66
|
+
const hasAuthorityPrefix = input.startsWith("//") || input.startsWith("/\\");
|
|
67
|
+
if (hasAuthorityPrefix) {
|
|
68
|
+
throw new TypeError("Expected an absolute or root-relative URL");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const parsedURL = relative
|
|
72
|
+
? new URL(input, URL_REFERENCE_ORIGIN)
|
|
73
|
+
: new URL(input);
|
|
74
|
+
|
|
75
|
+
if (relative && parsedURL.origin !== URL_REFERENCE_ORIGIN) {
|
|
76
|
+
throw new TypeError("Expected an absolute or root-relative URL");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const query = params.toString();
|
|
80
|
+
if (!query) {
|
|
81
|
+
return input;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const separator = parsedURL.search.endsWith("&") ? "" : "&";
|
|
85
|
+
parsedURL.search = parsedURL.search
|
|
86
|
+
? `${parsedURL.search}${separator}${query}`
|
|
87
|
+
: query;
|
|
88
|
+
|
|
89
|
+
return relative
|
|
90
|
+
? parsedURL.href.slice(parsedURL.origin.length)
|
|
91
|
+
: parsedURL.href;
|
|
92
|
+
}
|
|
93
|
+
|
|
51
94
|
/**
|
|
52
95
|
* Schemes that execute or embed code when navigated to or accepted as a
|
|
53
96
|
* redirect target. These are never safe as an OAuth `redirect_uri` or as a
|