@better-auth/core 1.6.16 → 1.6.17
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/api/index.mjs +36 -0
- package/dist/context/global.mjs +1 -1
- package/dist/db/adapter/factory.mjs +82 -0
- package/dist/db/adapter/index.d.mts +51 -1
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/type.d.mts +15 -0
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/verify.d.mts +15 -6
- package/dist/oauth2/verify.mjs +97 -13
- package/dist/social-providers/microsoft-entra-id.d.mts +1 -1
- package/dist/social-providers/microsoft-entra-id.mjs +13 -1
- package/dist/social-providers/reddit.mjs +1 -1
- package/dist/social-providers/wechat.mjs +1 -1
- package/dist/types/context.d.mts +16 -0
- package/dist/types/init-options.d.mts +29 -0
- package/dist/utils/host.mjs +4 -0
- package/dist/utils/url.mjs +4 -3
- package/package.json +3 -3
- package/src/api/index.ts +45 -0
- package/src/db/adapter/factory.ts +152 -0
- package/src/db/adapter/index.ts +51 -0
- package/src/db/adapter/types.ts +1 -0
- package/src/db/type.ts +15 -0
- package/src/oauth2/verify.ts +168 -49
- package/src/social-providers/microsoft-entra-id.ts +40 -1
- package/src/social-providers/reddit.ts +5 -1
- package/src/social-providers/wechat.ts +8 -1
- package/src/types/context.ts +17 -0
- package/src/types/init-options.ts +26 -0
- package/src/utils/host.ts +15 -0
- package/src/utils/url.ts +10 -4
|
@@ -200,7 +200,14 @@ export const wechat = (options: WeChatOptions) => {
|
|
|
200
200
|
user: {
|
|
201
201
|
id: profile.unionid || profile.openid || openid,
|
|
202
202
|
name: profile.nickname,
|
|
203
|
-
|
|
203
|
+
// WeChat does not return an email, and the OAuth callback rejects a
|
|
204
|
+
// missing one, so the default sign-in would always fail. Synthesize a
|
|
205
|
+
// stable, non-routable placeholder (RFC 2606 `.invalid`) keyed to the
|
|
206
|
+
// user's WeChat id, left unverified. Applications that collect a real
|
|
207
|
+
// email override it via `mapProfileToUser`.
|
|
208
|
+
email:
|
|
209
|
+
profile.email ||
|
|
210
|
+
`${profile.unionid || profile.openid || openid}@wechat.invalid`,
|
|
204
211
|
image: profile.headimgurl,
|
|
205
212
|
emailVerified: false,
|
|
206
213
|
...userMap,
|
package/src/types/context.ts
CHANGED
|
@@ -237,6 +237,23 @@ export interface InternalAdapter<
|
|
|
237
237
|
*/
|
|
238
238
|
consumeVerificationValue(identifier: string): Promise<Verification | null>;
|
|
239
239
|
|
|
240
|
+
/**
|
|
241
|
+
* First-writer-wins create keyed by a deterministic primary key derived from
|
|
242
|
+
* `identifier`. Returns `true` when this caller created the row and `false`
|
|
243
|
+
* when a row for the same identifier already existed.
|
|
244
|
+
*
|
|
245
|
+
* The dual of `consumeVerificationValue`: reserve races to create a marker
|
|
246
|
+
* exactly once, where consume races to delete one exactly once. Use it for
|
|
247
|
+
* replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
|
|
248
|
+
* wins. The database path is atomic via the primary key; the
|
|
249
|
+
* secondary-storage-only path is best-effort under concurrency.
|
|
250
|
+
*/
|
|
251
|
+
reserveVerificationValue(data: {
|
|
252
|
+
identifier: string;
|
|
253
|
+
value: string;
|
|
254
|
+
expiresAt: Date;
|
|
255
|
+
}): Promise<boolean>;
|
|
256
|
+
|
|
240
257
|
updateVerificationByIdentifier(
|
|
241
258
|
identifier: string,
|
|
242
259
|
data: Partial<Verification>,
|
|
@@ -101,6 +101,32 @@ export interface BetterAuthRateLimitStorage {
|
|
|
101
101
|
value: RateLimit,
|
|
102
102
|
update?: boolean | undefined,
|
|
103
103
|
) => Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Atomically records one request against `key` within the `window`
|
|
106
|
+
* (in seconds) and reports whether it is allowed.
|
|
107
|
+
*
|
|
108
|
+
* When `allowed` is true the request was counted within the active window;
|
|
109
|
+
* when `allowed` is false the limit was already reached and `retryAfter` is
|
|
110
|
+
* the number of seconds until the window frees up. Whether the window slides
|
|
111
|
+
* or is fixed depends on the backing storage: the database backend resets
|
|
112
|
+
* once the window elapses, while secondary storage uses a fixed time-to-live
|
|
113
|
+
* set when the window first opens.
|
|
114
|
+
*
|
|
115
|
+
* Performing the check and the increment in a single step closes the
|
|
116
|
+
* concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
|
|
117
|
+
* requests can no longer all pass a stale read before any increment lands.
|
|
118
|
+
*
|
|
119
|
+
* Optional for backwards compatibility. A storage without it falls back to
|
|
120
|
+
* the legacy non-atomic `get`/`set` path, which is best-effort under
|
|
121
|
+
* concurrency.
|
|
122
|
+
*
|
|
123
|
+
* TODO(rate-limit-consume-required): make this the sole required member on
|
|
124
|
+
* `next`, dropping `get`/`set` and the non-atomic fallback.
|
|
125
|
+
*/
|
|
126
|
+
consume?: (
|
|
127
|
+
key: string,
|
|
128
|
+
rule: { window: number; max: number },
|
|
129
|
+
) => Promise<{ allowed: boolean; retryAfter: number | null }>;
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
export type BetterAuthRateLimitRule = {
|
package/src/utils/host.ts
CHANGED
|
@@ -235,6 +235,10 @@ function classifyIPv6(expanded: string): HostKind {
|
|
|
235
235
|
|
|
236
236
|
if (expanded.startsWith("2001:0db8:")) return "documentation";
|
|
237
237
|
|
|
238
|
+
// 2001:2::/48 — Benchmarking (RFC 5180). A specific non-globally-reachable
|
|
239
|
+
// block inside the otherwise-mixed 2001::/23 protocol-assignments space.
|
|
240
|
+
if (expanded.startsWith("2001:0002:0000:")) return "benchmarking";
|
|
241
|
+
|
|
238
242
|
if (expanded.startsWith("2002:")) {
|
|
239
243
|
const embedded = extractEmbeddedIPv4(expanded, 1);
|
|
240
244
|
if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
|
|
@@ -247,6 +251,10 @@ function classifyIPv6(expanded: string): HostKind {
|
|
|
247
251
|
return "reserved";
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
// 64:ff9b:1::/48 — Local-Use IPv4/IPv6 Translation (RFC 8215). Distinct from
|
|
255
|
+
// the well-known NAT64 /96 prefix above and not globally reachable.
|
|
256
|
+
if (expanded.startsWith("0064:ff9b:0001:")) return "reserved";
|
|
257
|
+
|
|
250
258
|
if (expanded.startsWith("2001:0000:")) {
|
|
251
259
|
const embedded = extractEmbeddedIPv4(expanded, 6, { xor: true });
|
|
252
260
|
if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
|
|
@@ -255,6 +263,13 @@ function classifyIPv6(expanded: string): HostKind {
|
|
|
255
263
|
|
|
256
264
|
if (expanded.startsWith("0100:0000:0000:0000:")) return "reserved";
|
|
257
265
|
|
|
266
|
+
// 3fff::/20 — Documentation (RFC 9637). The /20 fixes the first 16 bits to
|
|
267
|
+
// `3fff` and the next nibble to 0, so only `3fff:0xxx` is in range.
|
|
268
|
+
if (expanded.startsWith("3fff:0")) return "documentation";
|
|
269
|
+
|
|
270
|
+
// 5f00::/16 — SRv6 SIDs (RFC 9602), not globally reachable.
|
|
271
|
+
if (expanded.startsWith("5f00:")) return "reserved";
|
|
272
|
+
|
|
258
273
|
return "public";
|
|
259
274
|
}
|
|
260
275
|
|
package/src/utils/url.ts
CHANGED
|
@@ -25,18 +25,24 @@ export function normalizePathname(
|
|
|
25
25
|
return "/";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Canonicalize the basePath the same way as the request pathname. A baseURL
|
|
29
|
+
// with a trailing slash yields a basePath like "/api/auth/"; without this it
|
|
30
|
+
// would never match the slash-stripped pathname and the prefix would leak
|
|
31
|
+
// through to disabledPaths and rate-limit special-rule matching.
|
|
32
|
+
const normalizedBasePath = basePath.replace(/\/+$/, "");
|
|
33
|
+
|
|
34
|
+
if (normalizedBasePath === "") {
|
|
29
35
|
return pathname;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
// Check for exact match or proper path boundary (basePath followed by "/" or end)
|
|
33
39
|
// This prevents "/api/auth" from matching "/api/authevil/..."
|
|
34
|
-
if (pathname ===
|
|
40
|
+
if (pathname === normalizedBasePath) {
|
|
35
41
|
return "/";
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
if (pathname.startsWith(
|
|
39
|
-
return pathname.slice(
|
|
44
|
+
if (pathname.startsWith(normalizedBasePath + "/")) {
|
|
45
|
+
return pathname.slice(normalizedBasePath.length).replace(/\/+$/, "") || "/";
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
return pathname;
|