@better-auth/infra 0.4.0 → 0.4.2

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 CHANGED
@@ -5,8 +5,26 @@ 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.2] - 2026-08-21
9
+
10
+ ### Fixed
11
+
12
+ - **Identification on OAuth callbacks** — Sentinel and dash run identification on GET OAuth callback routes and persist `X-Request-Id` as the `__infra-rid` cookie in an after hook, so visitor identity resolves from KV across redirect-based social/OAuth sign-in (where identify headers are absent).
13
+ - **Issuer-scoped account lookups** — OAuth callback user resolution honors `account.identityStrategy` (`issuer` vs `provider-id`) when deriving account keys, matching Better Auth 1.7 issuer-scoped identity and the provider-scoped compatibility mode.
14
+
15
+ ### Changed
16
+
17
+ - **No runtime dependency on SCIM / SSO packages** — Directory-sync SCIM user-link acquisition and SAML SP policy checks are implemented in-package (aligned with `@better-auth/scim` / `@better-auth/sso`). Those packages remain type-only deps and are excluded from the published bundle.
18
+
19
+ ## [0.4.1] - 2026-08-19
20
+
21
+ ### Fixed
22
+
23
+ - **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`.
24
+
8
25
  ## [0.4.0] - 2026-08-14
9
26
 
27
+
10
28
  ### Added
11
29
 
12
30
  - **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.
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).
package/dist/client.d.mts CHANGED
@@ -1,5 +1,4 @@
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-RI-JiXQx.mjs";
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
  /**
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { n as hash, o as createKV } from "./crypto-CAdWFWEz.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-DZhZiYd_.mjs";
1
+ import { n as hash, o as createKV } from "./crypto-D3yZH0Cy.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-jWGI__oh.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
- return (await hash(JSON.stringify(stableData))).slice(0, 20);
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,11 +436,12 @@ 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: resolveSentinelClientIdentifyUrl({
440
- identifyUrl: options?.identifyUrl,
441
- envKvUrl: env.BETTER_AUTH_KV_URL
442
- }),
444
+ kvUrl: identifyUrl,
443
445
  timeout: options?.identifyOptions?.timeout ?? options?.kvTimeout
444
446
  }, {
445
447
  throw: true,
@@ -1,7 +1,7 @@
1
1
  import { env } from "@better-auth/core/env";
2
2
  //#endregion
3
3
  //#region src/version.ts
4
- const PLUGIN_VERSION = "0.4.0";
4
+ const PLUGIN_VERSION = "0.4.2";
5
5
  //#endregion
6
6
  //#region src/constants.ts
7
7
  /**
@@ -1,5 +1,6 @@
1
- import { i as INFRA_USER_AGENT } from "./constants-CcJHk5SE.mjs";
1
+ import { i as INFRA_USER_AGENT } from "./constants-CDhjfr8S.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({
@@ -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 msgBuffer = new TextEncoder().encode(message);
37
- const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
38
- return bytesToHex(new Uint8Array(hashBuffer));
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 cryptoKey = await crypto.subtle.importKey("raw", encoder.encode(key), {
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 crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(message));
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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as INFRA_USER_AGENT, n as INFRA_API_URL } from "./constants-CcJHk5SE.mjs";
1
+ import { i as INFRA_USER_AGENT, n as INFRA_API_URL } from "./constants-CDhjfr8S.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";
@@ -1,7 +1,6 @@
1
- import { m as KvRetryOptions } from "./types-B673lCib.mjs";
1
+ import { m as KvRetryOptions } from "./types-Ddk4r5x9.mjs";
2
2
  import { BetterAuthClientOptions, ClientStore } from "better-auth";
3
3
  import { BetterFetch } from "@better-fetch/fetch";
4
-
5
4
  //#region src/dash-client.d.ts
6
5
  interface DashAuditLog {
7
6
  eventType: string;
package/dist/index.d.mts CHANGED
@@ -1,11 +1,11 @@
1
- import { C as ImpossibleTravelResult, D as SecurityVerdict, E as SecurityOptions, O as StaleUserResult, S as CredentialStuffingResult, T as SecurityEventType, _ as LocationDataContext, a as DashOptionsInternal, b as sentinel, c as EndpointOptions, d as InfraPluginConnectionOptionsInternal, f as KvOptions, g as LocationData, h as KvRetryOptionsResolved, i as DashOptions, k as ThresholdConfig, l as InfraEndpointContext, m as KvRetryOptions, n as ApiOptions, o as DashOptionsResolved, p as KvOptionsResolved, r as ApiOptionsResolved, s as Endpoint, t as APIError, u as InfraPluginConnectionOptions, v as SentinelOptions, w as SecurityEvent, x as CompromisedPasswordResult, y as SentinelOptionsInternal } from "./types-B673lCib.mjs";
1
+ import { C as ImpossibleTravelResult, D as SecurityVerdict, E as SecurityOptions, O as StaleUserResult, S as CredentialStuffingResult, T as SecurityEventType, _ as LocationDataContext, a as DashOptionsInternal, b as sentinel, c as EndpointOptions, d as InfraPluginConnectionOptionsInternal, f as KvOptions, g as LocationData, h as KvRetryOptionsResolved, i as DashOptions, k as ThresholdConfig, l as InfraEndpointContext, m as KvRetryOptions, n as ApiOptions, o as DashOptionsResolved, p as KvOptionsResolved, r as ApiOptionsResolved, s as Endpoint, t as APIError, u as InfraPluginConnectionOptions, v as SentinelOptions, w as SecurityEvent, x as CompromisedPasswordResult, y as SentinelOptionsInternal } from "./types-Ddk4r5x9.mjs";
2
2
  import { EMAIL_TEMPLATES, EmailConfig, EmailTemplateId, EmailTemplateVariables, SendBulkEmailsOptions, SendBulkEmailsResult, SendEmailOptions, SendEmailResult, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
3
3
  import { Account, AuthContext, GenericEndpointContext, Session, User } from "better-auth";
4
- import z$1 from "zod";
4
+ import z from "zod";
5
5
  import { DBFieldAttribute } from "better-auth/db";
6
6
  import { Invitation, Member, Organization, Team, TeamMember } from "better-auth/plugins";
7
+ import "better-auth/types";
7
8
  export type * from "better-call";
8
-
9
9
  //#region src/pow.d.ts
10
10
  /**
11
11
  * Proof of Work Challenge System - Client Side
@@ -249,7 +249,22 @@ type DashSCIMManagedConnectionEventType = "connection.created" | "credential.iss
249
249
  /** Lifecycle state for one Infrastructure-owned directory-sync alias. */
250
250
  type DirectorySyncConnectionStatus = "active" | "decommissioning" | "decommissioned";
251
251
  type DirectorySyncMode = "legacy" | "managed" | "unavailable";
252
- type SCIMPlugin = ReturnType<typeof import("@better-auth/scim").scim>;
252
+ /**
253
+ * Structural stand-in for the SCIM plugin instance.
254
+ * Avoids `import("@better-auth/scim")` on the public type surface so consumers
255
+ * without that package can still typecheck the root entry.
256
+ */
257
+ type SCIMPlugin = {
258
+ id?: string;
259
+ options?: {
260
+ providerOwnership?: {
261
+ enabled?: boolean;
262
+ };
263
+ managedConnections?: unknown;
264
+ [key: string]: unknown;
265
+ };
266
+ endpoints: Record<string, unknown>;
267
+ };
253
268
  /** Public credential metadata. Raw bearer tokens are never included here. */
254
269
  interface DashDirectoryCredential {
255
270
  credentialId: string;