@better-auth/infra 0.3.7 → 0.4.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 +31 -0
- package/README.md +16 -1
- package/dist/client.d.mts +7 -3
- package/dist/client.mjs +11 -19
- package/dist/{constants-AfApXLhx.mjs → constants-CtvGRrlI.mjs} +1 -1
- package/dist/{crypto--3ycICW4.mjs → crypto-CV91nSbp.mjs} +23 -8
- package/dist/email.d.mts +11 -0
- package/dist/email.mjs +2 -2
- package/dist/{dash-client-DXuu2ctl.d.mts → identify-client-options-CgijjwVT.d.mts} +16 -2
- package/dist/index.d.mts +258 -571
- package/dist/index.mjs +2583 -709
- package/dist/native.d.mts +7 -3
- package/dist/native.mjs +10 -19
- package/dist/{pow-retry-Dy1D-TKx.mjs → pow-retry-7sPi32Ow.mjs} +19 -3
- package/dist/saml-policy-DQR0MBLk.mjs +12 -0
- package/dist/types-Ddk4r5x9.d.mts +672 -0
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.4.1] - 2026-08-19
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **React Native / Hermes crypto** — SHA-256 (including Sentinel PoW challenge solve/verify) no longer requires `crypto.subtle`. Prefers Web Crypto when present; otherwise falls back to `@noble/hashes`.
|
|
13
|
+
|
|
14
|
+
## [0.4.0] - 2026-08-14
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Managed SCIM directory sync** — Opt-in `dash({ managedDirectorySync: { enabled: true } })` control plane for Better Auth 1.7+ managed connections. Adds dashboard lifecycle endpoints to create, list, get, rotate credentials, revoke credentials, list events, unpair, and decommission organization directories, plus schema for `directorySyncConnection` / membership provenance, optional SSO pairing hooks, and SCIM → organization membership projection.
|
|
20
|
+
- **Issuer-scoped accounts** — Dashboard user create, set-password, and account unlink paths work with Better Auth 1.7 issuer-scoped account identity (`issuer` + subject) while remaining compatible with pre-issuer schemas.
|
|
21
|
+
- **Configurable API / KV client options** — Shared `apiOptions` / `kvOptions` (timeout and KV retry/backoff) on `dash` and `sentinel`. Legacy `apiTimeout` / `kvTimeout` remain supported as deprecated aliases.
|
|
22
|
+
- **`GET /dash/config` joins insight** — Config insights report `hasJoinsEnabled` from `advanced.database.joins` (and pre-1.7 `experimental.joins` when applicable).
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Better Auth 1.7 RC compatibility** — Support matrix and runtime paths updated through 1.7 rc.4 (issuer-scoped accounts, session lifecycle hooks, SSO domain verification contracts).
|
|
27
|
+
- **SSO domain verification TXT records** — Dashboard domain verification uses the SSO plugin `tokenPrefix` and the `_` + prefix + provider id TXT name shape expected by Better Auth domain verification.
|
|
28
|
+
- **Session lifecycle event tracking** — Sign-in / session-created / impersonation tracking deferred correctly when session creation runs before the user is available on the request context (Better Auth 1.7 rc.2+).
|
|
29
|
+
- **Directory list mode split** — Legacy pre-1.7 SCIM provider listing stays on the legacy surface; managed connection APIs only run when managed directory sync is enabled.
|
|
30
|
+
- **Account unlink body** — `unlinkAccount` requires `accountId` (local account row id) together with `providerId`.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **SSO domain verify errors** — `verify-domain` maps provider-changed, already-verified, and missing-DNS cases to stable dashboard responses instead of leaking raw API errors.
|
|
35
|
+
- **Sentinel stale-account handling** — Stale-user blocks clear the session cookie and surface a consistent `STALE_ACCOUNT` error after the endpoint runs, not only inside DB hooks.
|
|
36
|
+
- **SCIM token redaction** — Sensitive SCIM credential fields stay redacted in dash config / plugin option dumps.
|
|
37
|
+
|
|
8
38
|
## [0.3.7] - 2026-07-20
|
|
9
39
|
|
|
10
40
|
### Added
|
|
@@ -144,6 +174,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
144
174
|
|
|
145
175
|
[0.3.7]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.6...infra@v0.3.7
|
|
146
176
|
[0.3.6]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.5...infra@v0.3.6
|
|
177
|
+
[0.4.0]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.7...infra@v0.4.0
|
|
147
178
|
[0.3.5]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.4...infra@v0.3.5
|
|
148
179
|
[0.3.4]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.3...infra@v0.3.4
|
|
149
180
|
[0.3.3]: https://github.com/better-auth/infrastructure/compare/infra@v0.3.2...infra@v0.3.3
|
package/README.md
CHANGED
|
@@ -80,6 +80,10 @@ Use this plugin for your React Native / Expo app. This plugin is designed to wor
|
|
|
80
80
|
pnpm add react-native @react-native-async-storage/async-storage
|
|
81
81
|
# Optional, for richer device metadata in the identify payload:
|
|
82
82
|
pnpm add expo-constants expo-device
|
|
83
|
+
# Optional, secure visitor-id RNG (Expo):
|
|
84
|
+
pnpm add expo-crypto
|
|
85
|
+
# Or plain React Native — first import in the app entry:
|
|
86
|
+
# import "react-native-get-random-values";
|
|
83
87
|
```
|
|
84
88
|
|
|
85
89
|
`@react-native-async-storage/async-storage` is optional; if it is not installed, a session-only in-memory visitor id is used. For production, install it or pass `storage` (for example secure storage).
|
|
@@ -104,7 +108,9 @@ export const authClient = createAuthClient({
|
|
|
104
108
|
|
|
105
109
|
### Options (`sentinelNativeClient`)
|
|
106
110
|
|
|
107
|
-
- `identifyUrl?: string` —
|
|
111
|
+
- `identifyUrl?: string` — identify endpoint base (defaults to `BETTER_AUTH_KV_URL` from env, then `https://kv.better-auth.com`).
|
|
112
|
+
- `identifyOptions?: { timeout?: number; retry?: { attempts?: number; baseDelay?: number; maxDelay?: number } }` — identify HTTP timeout (ms, default `1000`) and POST retry (defaults `2` / `400` / `600`).
|
|
113
|
+
- `kvTimeout?: number` — **deprecated**, use `identifyOptions.timeout`.
|
|
108
114
|
- `autoSolveChallenge?: boolean` — When `true` (default), `423` responses that include `X-PoW-Challenge` are solved and the request is retried once with `X-PoW-Solution`.
|
|
109
115
|
- `onChallengeReceived?` / `onChallengeSolved?` / `onChallengeFailed?` — PoW lifecycle hooks (reason string, solve time in ms, or error).
|
|
110
116
|
- `storage?: { getItem, setItem }` — Async key/value storage for a stable per-install visitor id (recommended for production).
|
|
@@ -270,7 +276,12 @@ The `dash()` plugin registers these event endpoints:
|
|
|
270
276
|
- `apiUrl?: string`
|
|
271
277
|
- `kvUrl?: string`
|
|
272
278
|
- `apiKey?: string`
|
|
279
|
+
- `apiOptions?: { timeout?: number }` (ms, default `3000`)
|
|
280
|
+
- `kvOptions?: { timeout?: number; retry?: { attempts?: number; baseDelay?: number; maxDelay?: number } }` — KV timeout default `1000`; retry defaults `2` / `400` / `600`
|
|
281
|
+
- `apiTimeout?: number` — **deprecated**, use `apiOptions.timeout`
|
|
282
|
+
- `kvTimeout?: number` — **deprecated**, use `kvOptions.timeout`
|
|
273
283
|
- `activityTracking?: { enabled?: boolean; updateInterval?: number }`
|
|
284
|
+
- `managedDirectorySync?: { enabled?: boolean; ssoPairing?: boolean; membershipProjection?: { enabled?: boolean; role?: string } }` — opt into the 1.7+ managed directory-sync control plane (schema + reservation APIs). `enabled` alone turns on reservation tables/APIs. When enabled, dash `init` can also wire sibling-plugin callbacks: `ssoPairing` installs SSO `resolveUser` / `guardProviderMutation` (needed only for paired directories; requires `sso({})` or richer options — bare `sso()` leaves `options` undefined), and `membershipProjection` installs SCIM `projection.reconcileUser` for org membership sync. Defaults: `enabled: false`, `ssoPairing: true`, `membershipProjection.enabled: true`, `membershipProjection.role: "member"`. Required with `scim({ managedConnections })` for managed mode; does not affect or replace legacy pre-1.7 SCIM directory sync.
|
|
274
285
|
|
|
275
286
|
`dash()` no longer accepts sentinel security config.
|
|
276
287
|
|
|
@@ -279,6 +290,10 @@ The `dash()` plugin registers these event endpoints:
|
|
|
279
290
|
- `apiUrl?: string`
|
|
280
291
|
- `kvUrl?: string`
|
|
281
292
|
- `apiKey?: string`
|
|
293
|
+
- `apiOptions?: { timeout?: number }` — same as `DashOptions`
|
|
294
|
+
- `kvOptions?: { timeout?: number; retry?: { attempts?: number; baseDelay?: number; maxDelay?: number } }` — same as `DashOptions`
|
|
295
|
+
- `apiTimeout?: number` — **deprecated**, use `apiOptions.timeout`
|
|
296
|
+
- `kvTimeout?: number` — **deprecated**, use `kvOptions.timeout`
|
|
282
297
|
- `security?: SecurityOptions`
|
|
283
298
|
|
|
284
299
|
All security configuration now belongs in `sentinel()`.
|
package/dist/client.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
1
|
+
import { a as DashGetAllAuditLogsInput, i as DashClientOptions, n as DashAuditLog, o as DashGetAuditLogsInput, r as DashAuditLogsResponse, s as dashClient, t as IdentifyClientOptions } from "./identify-client-options-CgijjwVT.mjs";
|
|
3
2
|
//#region src/sentinel/client.d.ts
|
|
4
3
|
interface SentinelClientOptions {
|
|
5
4
|
/**
|
|
@@ -8,8 +7,13 @@ interface SentinelClientOptions {
|
|
|
8
7
|
*/
|
|
9
8
|
identifyUrl?: string;
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
10
|
+
* Identify HTTP client options.
|
|
11
|
+
*/
|
|
12
|
+
identifyOptions?: IdentifyClientOptions;
|
|
13
|
+
/**
|
|
14
|
+
* Timeout for identify and related HTTP requests (milliseconds).
|
|
12
15
|
* @default 1000
|
|
16
|
+
* @deprecated Use `identifyOptions.timeout` instead.
|
|
13
17
|
*/
|
|
14
18
|
kvTimeout?: number;
|
|
15
19
|
/**
|
package/dist/client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as hash, o as createKV } from "./crypto
|
|
2
|
-
import { a as resolveSentinelClientIdentifyUrl, c as
|
|
1
|
+
import { n as hash, o as createKV } from "./crypto-CV91nSbp.mjs";
|
|
2
|
+
import { a as resolveSentinelClientIdentifyUrl, c as identify, i as solvePoWChallenge, l as dashClient, n as decodePoWChallenge, o as resolveIdentifyClientRetry, r as encodePoWSolution, s as generateRequestId, t as createPowRetryTimeout } from "./pow-retry-7sPi32Ow.mjs";
|
|
3
3
|
import { env } from "@better-auth/core/env";
|
|
4
4
|
//#region src/sentinel/fingerprint.ts
|
|
5
5
|
function murmurhash3(str, seed = 0) {
|
|
@@ -325,7 +325,8 @@ async function generateVisitorId(components) {
|
|
|
325
325
|
fonts: components.fonts,
|
|
326
326
|
maxTouchPoints: components.maxTouchPoints
|
|
327
327
|
};
|
|
328
|
-
|
|
328
|
+
const dataString = JSON.stringify(stableData);
|
|
329
|
+
return (await hash(dataString)).slice(0, 20);
|
|
329
330
|
}
|
|
330
331
|
function calculateConfidence(components) {
|
|
331
332
|
const weights = {
|
|
@@ -435,25 +436,16 @@ function appendIdentification(headers, fingerprint) {
|
|
|
435
436
|
const MAX_POW_CHALLENGE_ROUNDS = 2;
|
|
436
437
|
const sentinelClient = (options) => {
|
|
437
438
|
const autoSolve = options?.autoSolveChallenge !== false;
|
|
439
|
+
const identifyUrl = resolveSentinelClientIdentifyUrl({
|
|
440
|
+
identifyUrl: options?.identifyUrl,
|
|
441
|
+
envKvUrl: env.BETTER_AUTH_KV_URL
|
|
442
|
+
});
|
|
438
443
|
const $kv = createKV({
|
|
439
|
-
kvUrl:
|
|
440
|
-
|
|
441
|
-
envKvUrl: env.BETTER_AUTH_KV_URL
|
|
442
|
-
}),
|
|
443
|
-
kvTimeout: options?.kvTimeout
|
|
444
|
+
kvUrl: identifyUrl,
|
|
445
|
+
timeout: options?.identifyOptions?.timeout ?? options?.kvTimeout
|
|
444
446
|
}, {
|
|
445
447
|
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
|
-
}
|
|
448
|
+
retry: resolveIdentifyClientRetry(options?.identifyOptions?.retry)
|
|
457
449
|
});
|
|
458
450
|
if (typeof window !== "undefined") {
|
|
459
451
|
const scheduleIdentify = () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { i as INFRA_USER_AGENT } from "./constants-
|
|
1
|
+
import { i as INFRA_USER_AGENT } from "./constants-CtvGRrlI.mjs";
|
|
2
2
|
import { createFetch } from "@better-fetch/fetch";
|
|
3
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
3
4
|
//#region src/fetch.ts
|
|
4
5
|
function createAPI(options, fetchOptions) {
|
|
5
6
|
return createFetch({
|
|
@@ -8,7 +9,7 @@ function createAPI(options, fetchOptions) {
|
|
|
8
9
|
"user-agent": INFRA_USER_AGENT,
|
|
9
10
|
"x-api-key": options.apiKey
|
|
10
11
|
},
|
|
11
|
-
timeout: options.
|
|
12
|
+
timeout: options.apiOptions.timeout,
|
|
12
13
|
...fetchOptions
|
|
13
14
|
});
|
|
14
15
|
}
|
|
@@ -18,7 +19,7 @@ function createKV(options, fetchOptions) {
|
|
|
18
19
|
return createFetch({
|
|
19
20
|
baseURL: options.kvUrl,
|
|
20
21
|
headers,
|
|
21
|
-
timeout: options.
|
|
22
|
+
timeout: options.timeout ?? 1e3,
|
|
22
23
|
...fetchOptions
|
|
23
24
|
});
|
|
24
25
|
}
|
|
@@ -32,19 +33,33 @@ function randomBytes(length) {
|
|
|
32
33
|
function bytesToHex(bytes) {
|
|
33
34
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
34
35
|
}
|
|
36
|
+
function getSubtle() {
|
|
37
|
+
try {
|
|
38
|
+
const c = globalThis.crypto;
|
|
39
|
+
if (c && typeof c.subtle === "object" && c.subtle != null) return c.subtle;
|
|
40
|
+
} catch {}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/** SHA-256 hex digest. Prefers Web Crypto; falls back to `@noble/hashes`. */
|
|
35
44
|
async function hash(message) {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
45
|
+
const data = new TextEncoder().encode(message);
|
|
46
|
+
const subtle = getSubtle();
|
|
47
|
+
if (subtle) {
|
|
48
|
+
const hashBuffer = await subtle.digest("SHA-256", data);
|
|
49
|
+
return bytesToHex(new Uint8Array(hashBuffer));
|
|
50
|
+
}
|
|
51
|
+
return bytesToHex(sha256(data));
|
|
39
52
|
}
|
|
40
53
|
/** Keyed HMAC-SHA256 for blinded fingerprints (e.g. failed-login passwords). */
|
|
41
54
|
async function hmacSha256Hex(key, message) {
|
|
42
55
|
const encoder = new TextEncoder();
|
|
43
|
-
const
|
|
56
|
+
const subtle = getSubtle();
|
|
57
|
+
if (!subtle) throw new Error("[@better-auth/infra] crypto.subtle is required for HMAC-SHA256");
|
|
58
|
+
const cryptoKey = await subtle.importKey("raw", encoder.encode(key), {
|
|
44
59
|
name: "HMAC",
|
|
45
60
|
hash: "SHA-256"
|
|
46
61
|
}, false, ["sign"]);
|
|
47
|
-
const signature = await
|
|
62
|
+
const signature = await subtle.sign("HMAC", cryptoKey, encoder.encode(message));
|
|
48
63
|
return bytesToHex(new Uint8Array(signature));
|
|
49
64
|
}
|
|
50
65
|
//#endregion
|
package/dist/email.d.mts
CHANGED
|
@@ -146,9 +146,20 @@ interface SendEmailResult {
|
|
|
146
146
|
interface EmailConfig {
|
|
147
147
|
apiKey?: string;
|
|
148
148
|
apiUrl?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Dash API HTTP client options.
|
|
151
|
+
*/
|
|
152
|
+
apiOptions?: {
|
|
153
|
+
/**
|
|
154
|
+
* Timeout for Dash email API HTTP requests (milliseconds).
|
|
155
|
+
* @default 3000
|
|
156
|
+
*/
|
|
157
|
+
timeout?: number;
|
|
158
|
+
};
|
|
149
159
|
/**
|
|
150
160
|
* Timeout for Dash email API HTTP requests (milliseconds).
|
|
151
161
|
* @default 3000
|
|
162
|
+
* @deprecated Use `apiOptions.timeout` instead.
|
|
152
163
|
*/
|
|
153
164
|
apiTimeout?: number;
|
|
154
165
|
}
|
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-CtvGRrlI.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";
|
|
@@ -41,7 +41,7 @@ function createEmailSender(config) {
|
|
|
41
41
|
"user-agent": INFRA_USER_AGENT,
|
|
42
42
|
Authorization: `Bearer ${apiKey}`
|
|
43
43
|
},
|
|
44
|
-
timeout: config?.apiTimeout ?? 3e3
|
|
44
|
+
timeout: config?.apiOptions?.timeout ?? config?.apiTimeout ?? 3e3
|
|
45
45
|
});
|
|
46
46
|
/**
|
|
47
47
|
* Send an email using a template from Better Auth Infra
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { m as KvRetryOptions } from "./types-Ddk4r5x9.mjs";
|
|
1
2
|
import { BetterAuthClientOptions, ClientStore } from "better-auth";
|
|
2
3
|
import { BetterFetch } from "@better-fetch/fetch";
|
|
3
|
-
|
|
4
4
|
//#region src/dash-client.d.ts
|
|
5
5
|
interface DashAuditLog {
|
|
6
6
|
eventType: string;
|
|
@@ -91,4 +91,18 @@ declare const dashClient: (options?: DashClientOptions) => {
|
|
|
91
91
|
};
|
|
92
92
|
};
|
|
93
93
|
//#endregion
|
|
94
|
-
|
|
94
|
+
//#region src/sentinel/identify-client-options.d.ts
|
|
95
|
+
type IdentifyClientOptions = {
|
|
96
|
+
/**
|
|
97
|
+
* Timeout for identify and related HTTP requests (milliseconds).
|
|
98
|
+
* @default 1000
|
|
99
|
+
*/
|
|
100
|
+
timeout?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Retry/backoff for identify POST requests.
|
|
103
|
+
* @default { attempts: 2, baseDelay: 400, maxDelay: 600 }
|
|
104
|
+
*/
|
|
105
|
+
retry?: KvRetryOptions;
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
export { DashGetAllAuditLogsInput as a, DashClientOptions as i, DashAuditLog as n, DashGetAuditLogsInput as o, DashAuditLogsResponse as r, dashClient as s, IdentifyClientOptions as t };
|