@better-auth/infra 0.2.14 → 0.3.1
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/CHANGELOG.md +50 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +65 -49
- package/dist/{constants-CLYqEwMV.mjs → constants-Cjw_ECs7.mjs} +1 -1
- package/dist/crypto-DuXBYsfj.mjs +51 -0
- package/dist/{dash-client-CZzJyRrV.d.mts → dash-client-CImS7uA_.d.mts} +3 -33
- package/dist/email.mjs +1 -1
- package/dist/index.d.mts +1941 -154
- package/dist/index.mjs +675 -596
- package/dist/native.d.mts +1 -1
- package/dist/native.mjs +62 -43
- package/dist/{pow-retry-D2RRftJr.mjs → pow-retry-DyWb0par.mjs} +4 -18
- package/package.json +8 -9
- package/dist/fetch-Bl0S3xUi.mjs +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,54 @@ All notable changes to `@better-auth/infra` are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.1] - 2026-06-22
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`dashClient` typing** — The client plugin now declares an explicit `BetterAuthClientPlugin` uses the updated signature, so `createAuthClient` inference works without casts when composing with other plugins.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Package metadata** — Repository and issue tracker URLs now point to `better-auth/infrastructure`.
|
|
17
|
+
|
|
18
|
+
## [0.3.0] - 2026-06-19
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Better Auth 1.7.x support** — SCIM directory sync works without the removed `providerOwnership` option; SSO dashboard routes no longer require `@better-auth/sso` as a separate peer dependency.
|
|
23
|
+
- **Backwards compatibility for Better Auth 1.4–1.6** — Session deletion and session listing work across internal adapter API changes; secondary-storage sessions missing `id` are normalized for dashboard use.
|
|
24
|
+
- **Expanded invitation auth modes** — Invitation acceptance supports `credential_setup`, `create_with_session`, and `create_no_session` in addition to `auth` and `direct`, aligned with the platform dashboard.
|
|
25
|
+
- **`deleteAllSessions` on ban endpoints** — `banUser` and `banManyUsers` accept an optional `deleteAllSessions` flag (defaults to `true`); `banManyUsers` now revokes sessions for banned users.
|
|
26
|
+
- **Strict user schemas** — Dashboard user create and update routes validate against schemas derived from the auth user table.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Better Auth upgrade** — Compatibility target raised to Better Auth 1.7.x.
|
|
31
|
+
- **Dash config redaction** — Plugin options and sensitive auth configuration use shared redaction utilities for more consistent masking.
|
|
32
|
+
- **Client IP resolution** — Platform proxy headers are trusted by default; custom `ipAddressHeaders` must be configured explicitly.
|
|
33
|
+
- **Sentinel security evaluation** — Security check events fall back to an untrusted visitor ID when trusted device identification is unavailable.
|
|
34
|
+
- **Free-trial abuse prevention** — Uses atomic signup reservation and confirmation instead of a pre-create check.
|
|
35
|
+
- **SMS client IP forwarding** — Outbound SMS requests can include the end-user IP via `x-better-auth-client-ip` for server-side abuse limits.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Generic OAuth callback events** — Failed sign-in events are tracked for generic OAuth2 callbacks (`/oauth2/callback/:providerId`).
|
|
40
|
+
- **Event property typo** — Tracked failed sign-in events now use `userName` instead of `nameName`.
|
|
41
|
+
- **Credential stuffing on reset-password** — Sentinel breached-password checks now run on the reset-password route.
|
|
42
|
+
- **Sentinel identification** — Identify requests retry with exponential backoff, stale identification sessions expire and regenerate, and auth routes retry identification when the page-load attempt failed.
|
|
43
|
+
- **Free-trial security checks** — Fixed race conditions on signup reservation and confirmation.
|
|
44
|
+
- **Invitation scoping** — Cancel and resend invitation operations validate the invitation ID and scope queries to the authenticated organization.
|
|
45
|
+
|
|
46
|
+
### Security
|
|
47
|
+
|
|
48
|
+
- **Session tokens in dash responses** — Bearer tokens are stripped from dashboard user session listings.
|
|
49
|
+
- **Credential stuffing fingerprints** — Failed-login password fingerprints use per-tenant HMAC-SHA256 instead of unsalted SHA-256.
|
|
50
|
+
- **Identification for free-trial abuse** — Sign-up is blocked when free-trial abuse prevention is enabled but device identification is missing.
|
|
51
|
+
- **Security check binding** — Sentinel security API calls include `requestId` for tighter operation binding.
|
|
52
|
+
- **Redirect URL validation** — Dashboard and invitation routes validate callback and redirect URLs as safe `http(s)` URLs without embedded credentials.
|
|
53
|
+
- **Organization slug validation** — Organization routes enforce comprehensive slug validation.
|
|
54
|
+
- **User schema enforcement** — Dashboard user mutations are restricted to writable fields from the auth schema.
|
|
55
|
+
|
|
8
56
|
## [0.2.14] - 2026-06-09
|
|
9
57
|
|
|
10
58
|
### Added
|
|
@@ -37,5 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
37
85
|
|
|
38
86
|
- Fixed impossible travel security challenges.
|
|
39
87
|
|
|
88
|
+
[0.3.1]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.0...infra@v0.3.1
|
|
89
|
+
[0.3.0]: https://github.com/better-auth/infrastructure/compare/infra@v0.2.14...infra@v0.3.0
|
|
40
90
|
[0.2.14]: https://github.com/better-auth/better-auth-infra/compare/@better-auth/infra@0.2.13...@better-auth/infra@0.2.14
|
|
41
91
|
[0.2.13]: https://github.com/better-auth/better-auth-infra/compare/@better-auth/infra@0.2.12...@better-auth/infra@0.2.13
|
package/dist/client.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as DashGetAuditLogsInput, i as DashGetAllAuditLogsInput, n as DashAuditLogsResponse, o as dashClient, r as DashClientOptions, t as DashAuditLog } from "./dash-client-
|
|
1
|
+
import { a as DashGetAuditLogsInput, i as DashGetAllAuditLogsInput, n as DashAuditLogsResponse, o as dashClient, r as DashClientOptions, t as DashAuditLog } from "./dash-client-CImS7uA_.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/sentinel/client.d.ts
|
|
4
4
|
interface SentinelClientOptions {
|
package/dist/client.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import { n as
|
|
3
|
-
import { a as resolveSentinelClientIdentifyUrl, i as solvePoWChallenge, l as hash, n as decodePoWChallenge, o as generateRequestId, r as encodePoWSolution, s as identify, t as createPowRetryTimeout, u as dashClient } from "./pow-retry-D2RRftJr.mjs";
|
|
1
|
+
import { n as hash, o as createKV } from "./crypto-DuXBYsfj.mjs";
|
|
2
|
+
import { a as resolveSentinelClientIdentifyUrl, c as dashClient, i as solvePoWChallenge, n as decodePoWChallenge, o as generateRequestId, r as encodePoWSolution, s as identify, t as createPowRetryTimeout } from "./pow-retry-DyWb0par.mjs";
|
|
4
3
|
import { env } from "@better-auth/core/env";
|
|
5
4
|
//#region src/sentinel/fingerprint.ts
|
|
6
5
|
function murmurhash3(str, seed = 0) {
|
|
@@ -358,13 +357,23 @@ function calculateConfidence(components) {
|
|
|
358
357
|
}
|
|
359
358
|
let cachedFingerprint = null;
|
|
360
359
|
let fingerprintPromise = null;
|
|
361
|
-
let
|
|
362
|
-
let
|
|
363
|
-
let
|
|
360
|
+
let identifySucceeded = false;
|
|
361
|
+
let identifyExpiresAt = null;
|
|
362
|
+
let identifyInFlight = null;
|
|
363
|
+
function expireIdentifySessionIfStale() {
|
|
364
|
+
if (!identifySucceeded || identifyExpiresAt === null) return;
|
|
365
|
+
if (Date.now() < identifyExpiresAt) return;
|
|
366
|
+
identifySucceeded = false;
|
|
367
|
+
identifyExpiresAt = null;
|
|
368
|
+
if (cachedFingerprint) cachedFingerprint = {
|
|
369
|
+
...cachedFingerprint,
|
|
370
|
+
requestId: generateRequestId()
|
|
371
|
+
};
|
|
372
|
+
}
|
|
364
373
|
async function getFingerprint() {
|
|
365
374
|
if (typeof window === "undefined") return null;
|
|
366
|
-
if (
|
|
367
|
-
if (
|
|
375
|
+
if (cachedFingerprint) return cachedFingerprint;
|
|
376
|
+
if (fingerprintPromise !== null) return fingerprintPromise;
|
|
368
377
|
fingerprintPromise = (async () => {
|
|
369
378
|
const components = await collectFingerprint();
|
|
370
379
|
const visitorId = await generateVisitorId(components);
|
|
@@ -386,39 +395,39 @@ async function getFingerprint() {
|
|
|
386
395
|
}
|
|
387
396
|
}
|
|
388
397
|
async function sendIdentify($kv) {
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
398
|
+
if (typeof window === "undefined") return identifySucceeded;
|
|
399
|
+
expireIdentifySessionIfStale();
|
|
400
|
+
if (identifySucceeded) return true;
|
|
401
|
+
if (identifyInFlight !== null) return identifyInFlight;
|
|
402
|
+
identifyInFlight = (async () => {
|
|
403
|
+
const fingerprint = await getFingerprint();
|
|
404
|
+
if (!fingerprint) return false;
|
|
405
|
+
const payload = {
|
|
406
|
+
visitorId: fingerprint.visitorId,
|
|
407
|
+
requestId: fingerprint.requestId,
|
|
408
|
+
confidence: fingerprint.confidence,
|
|
409
|
+
components: fingerprint.components,
|
|
410
|
+
url: window.location.href,
|
|
411
|
+
incognito: detectIncognito()
|
|
412
|
+
};
|
|
413
|
+
try {
|
|
414
|
+
const result = await identify($kv, payload);
|
|
415
|
+
identifySucceeded = true;
|
|
416
|
+
identifyExpiresAt = result.expiresAt ?? null;
|
|
417
|
+
return true;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.warn("[Dash] Identify request failed:", error);
|
|
420
|
+
return false;
|
|
421
|
+
} finally {
|
|
422
|
+
identifyInFlight = null;
|
|
423
|
+
}
|
|
424
|
+
})();
|
|
425
|
+
return identifyInFlight;
|
|
411
426
|
}
|
|
412
|
-
/**
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
* When `timeoutMs` is omitted or nullish, {@link KV_TIMEOUT_MS} is used.
|
|
417
|
-
*/
|
|
418
|
-
async function waitForIdentify(timeoutMs) {
|
|
419
|
-
if (!identifyCompletePromise) return;
|
|
420
|
-
const ms = timeoutMs ?? 1e3;
|
|
421
|
-
await Promise.race([identifyCompletePromise, new Promise((resolve) => setTimeout(resolve, ms))]);
|
|
427
|
+
/** Append visitor id always; request id only after a successful identify POST. */
|
|
428
|
+
function appendIdentification(headers, fingerprint) {
|
|
429
|
+
headers.set("X-Visitor-Id", fingerprint.visitorId);
|
|
430
|
+
if (identifySucceeded) headers.set("X-Request-Id", fingerprint.requestId);
|
|
422
431
|
}
|
|
423
432
|
//#endregion
|
|
424
433
|
//#region src/sentinel/client.ts
|
|
@@ -432,6 +441,19 @@ const sentinelClient = (options) => {
|
|
|
432
441
|
envKvUrl: env.BETTER_AUTH_KV_URL
|
|
433
442
|
}),
|
|
434
443
|
kvTimeout: options?.kvTimeout
|
|
444
|
+
}, {
|
|
445
|
+
throw: true,
|
|
446
|
+
retry: {
|
|
447
|
+
type: "exponential",
|
|
448
|
+
attempts: 2,
|
|
449
|
+
baseDelay: 400,
|
|
450
|
+
maxDelay: 600,
|
|
451
|
+
shouldRetry(response) {
|
|
452
|
+
if (response === null) return true;
|
|
453
|
+
if (response.status === 429) return true;
|
|
454
|
+
return response.status >= 500;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
435
457
|
});
|
|
436
458
|
if (typeof window !== "undefined") {
|
|
437
459
|
const scheduleIdentify = () => {
|
|
@@ -452,14 +474,11 @@ const sentinelClient = (options) => {
|
|
|
452
474
|
name: "sentinel-fingerprint",
|
|
453
475
|
hooks: { async onRequest(context) {
|
|
454
476
|
if (typeof window === "undefined") return context;
|
|
455
|
-
await
|
|
477
|
+
await sendIdentify($kv);
|
|
456
478
|
const fingerprint = await getFingerprint();
|
|
457
479
|
if (!fingerprint) return context;
|
|
458
480
|
const headers = context.headers || new Headers();
|
|
459
|
-
if (headers instanceof Headers)
|
|
460
|
-
headers.set("X-Visitor-Id", fingerprint.visitorId);
|
|
461
|
-
headers.set("X-Request-Id", fingerprint.requestId);
|
|
462
|
-
}
|
|
481
|
+
if (headers instanceof Headers) appendIdentification(headers, fingerprint);
|
|
463
482
|
return {
|
|
464
483
|
...context,
|
|
465
484
|
headers
|
|
@@ -492,10 +511,7 @@ const sentinelClient = (options) => {
|
|
|
492
511
|
const fingerprint = await getFingerprint();
|
|
493
512
|
const retryHeaders = new Headers();
|
|
494
513
|
retryHeaders.set("X-PoW-Solution", encodePoWSolution(solution));
|
|
495
|
-
if (fingerprint)
|
|
496
|
-
retryHeaders.set("X-Visitor-Id", fingerprint.visitorId);
|
|
497
|
-
retryHeaders.set("X-Request-Id", fingerprint.requestId);
|
|
498
|
-
}
|
|
514
|
+
if (fingerprint) appendIdentification(retryHeaders, fingerprint);
|
|
499
515
|
retryHeaders.set("Content-Type", "application/json");
|
|
500
516
|
const powRetry = createPowRetryTimeout(req.timeout);
|
|
501
517
|
try {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { i as INFRA_USER_AGENT } from "./constants-Cjw_ECs7.mjs";
|
|
2
|
+
import { createFetch } from "@better-fetch/fetch";
|
|
3
|
+
//#region src/fetch.ts
|
|
4
|
+
function createAPI(options, fetchOptions) {
|
|
5
|
+
return createFetch({
|
|
6
|
+
baseURL: options.apiUrl,
|
|
7
|
+
headers: {
|
|
8
|
+
"user-agent": INFRA_USER_AGENT,
|
|
9
|
+
"x-api-key": options.apiKey
|
|
10
|
+
},
|
|
11
|
+
timeout: options.apiTimeout,
|
|
12
|
+
...fetchOptions
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function createKV(options, fetchOptions) {
|
|
16
|
+
const headers = { "user-agent": INFRA_USER_AGENT };
|
|
17
|
+
if (options.apiKey) headers["x-api-key"] = options.apiKey;
|
|
18
|
+
return createFetch({
|
|
19
|
+
baseURL: options.kvUrl,
|
|
20
|
+
headers,
|
|
21
|
+
timeout: options.kvTimeout ?? 1e3,
|
|
22
|
+
...fetchOptions
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/crypto.ts
|
|
27
|
+
function randomBytes(length) {
|
|
28
|
+
const bytes = new Uint8Array(length);
|
|
29
|
+
crypto.getRandomValues(bytes);
|
|
30
|
+
return bytes;
|
|
31
|
+
}
|
|
32
|
+
function bytesToHex(bytes) {
|
|
33
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
34
|
+
}
|
|
35
|
+
async function hash(message) {
|
|
36
|
+
const msgBuffer = new TextEncoder().encode(message);
|
|
37
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
|
38
|
+
return bytesToHex(new Uint8Array(hashBuffer));
|
|
39
|
+
}
|
|
40
|
+
/** Keyed HMAC-SHA256 for blinded fingerprints (e.g. failed-login passwords). */
|
|
41
|
+
async function hmacSha256Hex(key, message) {
|
|
42
|
+
const encoder = new TextEncoder();
|
|
43
|
+
const cryptoKey = await crypto.subtle.importKey("raw", encoder.encode(key), {
|
|
44
|
+
name: "HMAC",
|
|
45
|
+
hash: "SHA-256"
|
|
46
|
+
}, false, ["sign"]);
|
|
47
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(message));
|
|
48
|
+
return bytesToHex(new Uint8Array(signature));
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { createAPI as a, randomBytes as i, hash as n, createKV as o, hmacSha256Hex as r, bytesToHex as t };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BetterAuthClientPlugin } from "better-auth";
|
|
2
|
+
|
|
1
3
|
//#region src/dash-client.d.ts
|
|
2
4
|
interface DashAuditLog {
|
|
3
5
|
eventType: string;
|
|
@@ -54,38 +56,6 @@ interface DashClientOptions {
|
|
|
54
56
|
session?: SessionLike | null;
|
|
55
57
|
}) => string | undefined;
|
|
56
58
|
}
|
|
57
|
-
declare const dashClient: (options?: DashClientOptions) =>
|
|
58
|
-
id: "dash";
|
|
59
|
-
getActions: ($fetch: import("@better-fetch/fetch").BetterFetch) => {
|
|
60
|
-
dash: {
|
|
61
|
-
getAuditLogs: (input?: DashGetAuditLogsInput) => Promise<{
|
|
62
|
-
data: null;
|
|
63
|
-
error: {
|
|
64
|
-
message?: string | undefined;
|
|
65
|
-
status: number;
|
|
66
|
-
statusText: string;
|
|
67
|
-
};
|
|
68
|
-
} | {
|
|
69
|
-
data: DashAuditLogsResponse;
|
|
70
|
-
error: null;
|
|
71
|
-
}>;
|
|
72
|
-
getAllAuditLogs: (input?: DashGetAllAuditLogsInput) => Promise<{
|
|
73
|
-
data: null;
|
|
74
|
-
error: {
|
|
75
|
-
message?: string | undefined;
|
|
76
|
-
status: number;
|
|
77
|
-
statusText: string;
|
|
78
|
-
};
|
|
79
|
-
} | {
|
|
80
|
-
data: DashAuditLogsResponse;
|
|
81
|
-
error: null;
|
|
82
|
-
}>;
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
pathMethods: {
|
|
86
|
-
"/events/audit-logs": "GET";
|
|
87
|
-
"/events/all-audit-logs": "GET";
|
|
88
|
-
};
|
|
89
|
-
};
|
|
59
|
+
declare const dashClient: (options?: DashClientOptions) => BetterAuthClientPlugin;
|
|
90
60
|
//#endregion
|
|
91
61
|
export { DashGetAuditLogsInput as a, DashGetAllAuditLogsInput as i, DashAuditLogsResponse as n, dashClient as o, DashClientOptions as r, DashAuditLog as t };
|
package/dist/email.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as INFRA_USER_AGENT, n as INFRA_API_URL } from "./constants-
|
|
1
|
+
import { i as INFRA_USER_AGENT, n as INFRA_API_URL } from "./constants-Cjw_ECs7.mjs";
|
|
2
2
|
import { logger } from "better-auth";
|
|
3
3
|
import { env } from "@better-auth/core/env";
|
|
4
4
|
import { createFetch } from "@better-fetch/fetch";
|