@better-auth/infra 0.1.14 → 0.2.0
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/README.md +44 -0
- package/dist/client.d.mts +2 -70
- package/dist/client.mjs +1 -87
- package/dist/dash-client-hJHp7l_X.d.mts +72 -0
- package/dist/index.d.mts +285 -308
- package/dist/index.mjs +199 -16
- package/dist/native.d.mts +18 -0
- package/dist/native.mjs +292 -0
- package/dist/pow-BUuN_EKw.mjs +131 -0
- package/package.json +28 -3
- package/dist/identification-DF2nvmng.mjs +0 -178
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ Infra plugins for Better Auth:
|
|
|
6
6
|
- `dashClient()` for dashboard client actions (including audit log queries).
|
|
7
7
|
- `sentinel()` for security checks and abuse protection.
|
|
8
8
|
- `sentinelClient()` for browser fingerprint headers + optional PoW auto-solving.
|
|
9
|
+
- `sentinelNativeClient()` (from `@better-auth/infra/native`) for React Native / Expo.
|
|
9
10
|
|
|
10
11
|
## Installation
|
|
11
12
|
|
|
@@ -69,6 +70,45 @@ const auditLogs = await authClient.dash.getAuditLogs({
|
|
|
69
70
|
});
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
## Native client
|
|
74
|
+
|
|
75
|
+
Use this plugin for your React Native / Expo app. This plugin is designed to work along with the server side sentinel and dash plugins and will ensure your app is protected from abuse and bot attacks.
|
|
76
|
+
|
|
77
|
+
### Install peers
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pnpm add react-native @react-native-async-storage/async-storage
|
|
81
|
+
# Optional, for richer device metadata in the identify payload:
|
|
82
|
+
pnpm add expo-constants expo-device
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`@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).
|
|
86
|
+
|
|
87
|
+
### Example
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { createAuthClient } from "better-auth/client";
|
|
91
|
+
import { dashClient, sentinelNativeClient } from "@better-auth/infra/native";
|
|
92
|
+
|
|
93
|
+
export const authClient = createAuthClient({
|
|
94
|
+
baseURL: "https://your-api.example.com",
|
|
95
|
+
plugins: [
|
|
96
|
+
dashClient(),
|
|
97
|
+
sentinelNativeClient({
|
|
98
|
+
identifyUrl: process.env.EXPO_PUBLIC_BETTER_AUTH_KV_URL,
|
|
99
|
+
autoSolveChallenge: true,
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Options (`sentinelNativeClient`)
|
|
106
|
+
|
|
107
|
+
- `identifyUrl?: string` — KV identify endpoint base (defaults to `BETTER_AUTH_KV_URL` from env, then `https://kv.better-auth.com`).
|
|
108
|
+
- `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
|
+
- `onChallengeReceived?` / `onChallengeSolved?` / `onChallengeFailed?` — PoW lifecycle hooks (reason string, solve time in ms, or error).
|
|
110
|
+
- `storage?: { getItem, setItem }` — Async key/value storage for a stable per-install visitor id (recommended for production).
|
|
111
|
+
|
|
72
112
|
## Audit Log APIs
|
|
73
113
|
|
|
74
114
|
### `dashClient()` API
|
|
@@ -249,6 +289,10 @@ All security configuration now belongs in `sentinel()`.
|
|
|
249
289
|
|
|
250
290
|
See `Audit Log APIs` above for full method details.
|
|
251
291
|
|
|
292
|
+
### `SentinelNativeClientOptions`
|
|
293
|
+
|
|
294
|
+
Exported from `@better-auth/infra/native`. See [React Native client](#native-client) for the full option list and usage.
|
|
295
|
+
|
|
252
296
|
## Migration
|
|
253
297
|
|
|
254
298
|
If you previously passed security config to `dash()`, move it to `sentinel()`:
|
package/dist/client.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as dashClient, i as DashGetAuditLogsInput, n as DashAuditLogsResponse, r as DashClientOptions, t as DashAuditLog } from "./dash-client-hJHp7l_X.mjs";
|
|
1
2
|
import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
|
|
2
3
|
|
|
3
4
|
//#region src/sentinel/client.d.ts
|
|
@@ -43,73 +44,4 @@ declare const sentinelClient: (options?: SentinelClientOptions) => {
|
|
|
43
44
|
})[];
|
|
44
45
|
};
|
|
45
46
|
//#endregion
|
|
46
|
-
|
|
47
|
-
interface DashAuditLog {
|
|
48
|
-
eventType: string;
|
|
49
|
-
eventData: Record<string, unknown>;
|
|
50
|
-
eventKey: string;
|
|
51
|
-
projectId: string;
|
|
52
|
-
createdAt: string;
|
|
53
|
-
updatedAt: string;
|
|
54
|
-
ageInMinutes?: number;
|
|
55
|
-
location?: {
|
|
56
|
-
ipAddress?: string | null;
|
|
57
|
-
city?: string | null;
|
|
58
|
-
country?: string | null;
|
|
59
|
-
countryCode?: string | null;
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
interface DashAuditLogsResponse {
|
|
63
|
-
events: DashAuditLog[];
|
|
64
|
-
total: number;
|
|
65
|
-
limit: number;
|
|
66
|
-
offset: number;
|
|
67
|
-
}
|
|
68
|
-
type SessionLike = {
|
|
69
|
-
user?: {
|
|
70
|
-
id?: string | null;
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
type UserLike = {
|
|
74
|
-
id?: string | null;
|
|
75
|
-
};
|
|
76
|
-
interface DashGetAuditLogsInput {
|
|
77
|
-
limit?: number;
|
|
78
|
-
offset?: number;
|
|
79
|
-
organizationId?: string;
|
|
80
|
-
identifier?: string;
|
|
81
|
-
eventType?: string;
|
|
82
|
-
userId?: string;
|
|
83
|
-
user?: UserLike | null;
|
|
84
|
-
session?: SessionLike | null;
|
|
85
|
-
}
|
|
86
|
-
interface DashClientOptions {
|
|
87
|
-
resolveUserId?: (input: {
|
|
88
|
-
userId?: string;
|
|
89
|
-
user?: UserLike | null;
|
|
90
|
-
session?: SessionLike | null;
|
|
91
|
-
}) => string | undefined;
|
|
92
|
-
}
|
|
93
|
-
declare const dashClient: (options?: DashClientOptions) => {
|
|
94
|
-
id: "dash";
|
|
95
|
-
getActions: ($fetch: _$_better_fetch_fetch0.BetterFetch) => {
|
|
96
|
-
dash: {
|
|
97
|
-
getAuditLogs: (input?: DashGetAuditLogsInput) => Promise<{
|
|
98
|
-
data: DashAuditLogsResponse;
|
|
99
|
-
error: null;
|
|
100
|
-
} | {
|
|
101
|
-
data: null;
|
|
102
|
-
error: {
|
|
103
|
-
message?: string | undefined;
|
|
104
|
-
status: number;
|
|
105
|
-
statusText: string;
|
|
106
|
-
};
|
|
107
|
-
}>;
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
pathMethods: {
|
|
111
|
-
"/events/audit-logs": "GET";
|
|
112
|
-
};
|
|
113
|
-
};
|
|
114
|
-
//#endregion
|
|
115
|
-
export { DashAuditLog, DashAuditLogsResponse, DashClientOptions, DashGetAuditLogsInput, type SentinelClientOptions, dashClient, sentinelClient };
|
|
47
|
+
export { type DashAuditLog, type DashAuditLogsResponse, type DashClientOptions, type DashGetAuditLogsInput, type SentinelClientOptions, dashClient, sentinelClient };
|
package/dist/client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as KV_TIMEOUT_MS } from "./constants-DdWGfvz1.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as identify, c as dashClient, i as generateRequestId, n as encodePoWSolution, r as solvePoWChallenge, s as hash, t as decodePoWChallenge } from "./pow-BUuN_EKw.mjs";
|
|
3
3
|
import { env } from "@better-auth/core/env";
|
|
4
4
|
//#region src/sentinel/fingerprint.ts
|
|
5
5
|
function murmurhash3(str, seed = 0) {
|
|
@@ -417,63 +417,6 @@ async function waitForIdentify(timeoutMs = 500) {
|
|
|
417
417
|
await Promise.race([identifyCompletePromise, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
418
418
|
}
|
|
419
419
|
//#endregion
|
|
420
|
-
//#region src/sentinel/pow.ts
|
|
421
|
-
function hasLeadingZeroBits(hash, bits) {
|
|
422
|
-
const fullHexChars = Math.floor(bits / 4);
|
|
423
|
-
const remainingBits = bits % 4;
|
|
424
|
-
for (let i = 0; i < fullHexChars; i++) if (hash[i] !== "0") return false;
|
|
425
|
-
if (remainingBits > 0 && fullHexChars < hash.length) {
|
|
426
|
-
if (parseInt(hash[fullHexChars], 16) > (1 << 4 - remainingBits) - 1) return false;
|
|
427
|
-
}
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
async function solvePoWChallenge(challenge) {
|
|
431
|
-
const { nonce, difficulty } = challenge;
|
|
432
|
-
let counter = 0;
|
|
433
|
-
while (true) {
|
|
434
|
-
if (hasLeadingZeroBits(await hash(`${nonce}:${counter}`), difficulty)) return {
|
|
435
|
-
nonce,
|
|
436
|
-
counter
|
|
437
|
-
};
|
|
438
|
-
counter++;
|
|
439
|
-
if (counter % 1e3 === 0) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
440
|
-
if (counter > 1e8) throw new Error("PoW challenge took too long to solve");
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function decodeBase64ToUtf8(encoded) {
|
|
444
|
-
if (typeof globalThis.atob === "function") return globalThis.atob(encoded);
|
|
445
|
-
throw new Error("[Sentinel] Base64 decode requires atob (browser, Hermes, or Bun)");
|
|
446
|
-
}
|
|
447
|
-
function encodeUtf8ToBase64(str) {
|
|
448
|
-
if (typeof globalThis.btoa === "function") return globalThis.btoa(str);
|
|
449
|
-
const bytes = new TextEncoder().encode(str);
|
|
450
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
451
|
-
let out = "";
|
|
452
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
453
|
-
const b0 = bytes[i];
|
|
454
|
-
const b1 = bytes[i + 1] ?? 0;
|
|
455
|
-
const b2 = bytes[i + 2] ?? 0;
|
|
456
|
-
const triple = b0 << 16 | b1 << 8 | b2;
|
|
457
|
-
const pad = i + 2 >= bytes.length ? i + 1 >= bytes.length ? 2 : 1 : 0;
|
|
458
|
-
out += chars[triple >> 18 & 63];
|
|
459
|
-
out += chars[triple >> 12 & 63];
|
|
460
|
-
out += pad < 2 ? chars[triple >> 6 & 63] : "=";
|
|
461
|
-
out += pad < 1 ? chars[triple & 63] : "=";
|
|
462
|
-
}
|
|
463
|
-
return out;
|
|
464
|
-
}
|
|
465
|
-
function decodePoWChallenge(encoded) {
|
|
466
|
-
try {
|
|
467
|
-
const decoded = decodeBase64ToUtf8(encoded);
|
|
468
|
-
return JSON.parse(decoded);
|
|
469
|
-
} catch {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
function encodePoWSolution(solution) {
|
|
474
|
-
return encodeUtf8ToBase64(JSON.stringify(solution));
|
|
475
|
-
}
|
|
476
|
-
//#endregion
|
|
477
420
|
//#region src/sentinel/client.ts
|
|
478
421
|
const DEFAULT_IDENTIFY_URL = "https://kv.better-auth.com";
|
|
479
422
|
const sentinelClient = (options) => {
|
|
@@ -565,33 +508,4 @@ const sentinelClient = (options) => {
|
|
|
565
508
|
};
|
|
566
509
|
};
|
|
567
510
|
//#endregion
|
|
568
|
-
//#region src/client.ts
|
|
569
|
-
function resolveDashUserId(input, options) {
|
|
570
|
-
return input.userId || options?.resolveUserId?.({
|
|
571
|
-
userId: input.userId,
|
|
572
|
-
user: input.user,
|
|
573
|
-
session: input.session
|
|
574
|
-
}) || input.user?.id || input.session?.user?.id || void 0;
|
|
575
|
-
}
|
|
576
|
-
const dashClient = (options) => {
|
|
577
|
-
return {
|
|
578
|
-
id: "dash",
|
|
579
|
-
getActions: ($fetch) => ({ dash: { getAuditLogs: async (input = {}) => {
|
|
580
|
-
const userId = resolveDashUserId(input, options);
|
|
581
|
-
return $fetch("/events/audit-logs", {
|
|
582
|
-
method: "GET",
|
|
583
|
-
query: {
|
|
584
|
-
limit: input.limit,
|
|
585
|
-
offset: input.offset,
|
|
586
|
-
organizationId: input.organizationId,
|
|
587
|
-
identifier: input.identifier,
|
|
588
|
-
eventType: input.eventType,
|
|
589
|
-
userId
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
} } }),
|
|
593
|
-
pathMethods: { "/events/audit-logs": "GET" }
|
|
594
|
-
};
|
|
595
|
-
};
|
|
596
|
-
//#endregion
|
|
597
511
|
export { dashClient, sentinelClient };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
|
|
2
|
+
|
|
3
|
+
//#region src/dash-client.d.ts
|
|
4
|
+
interface DashAuditLog {
|
|
5
|
+
eventType: string;
|
|
6
|
+
eventData: Record<string, unknown>;
|
|
7
|
+
eventKey: string;
|
|
8
|
+
projectId: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
ageInMinutes?: number;
|
|
12
|
+
location?: {
|
|
13
|
+
ipAddress?: string | null;
|
|
14
|
+
city?: string | null;
|
|
15
|
+
country?: string | null;
|
|
16
|
+
countryCode?: string | null;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
interface DashAuditLogsResponse {
|
|
20
|
+
events: DashAuditLog[];
|
|
21
|
+
total: number;
|
|
22
|
+
limit: number;
|
|
23
|
+
offset: number;
|
|
24
|
+
}
|
|
25
|
+
type SessionLike = {
|
|
26
|
+
user?: {
|
|
27
|
+
id?: string | null;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
type UserLike = {
|
|
31
|
+
id?: string | null;
|
|
32
|
+
};
|
|
33
|
+
interface DashGetAuditLogsInput {
|
|
34
|
+
limit?: number;
|
|
35
|
+
offset?: number;
|
|
36
|
+
organizationId?: string;
|
|
37
|
+
identifier?: string;
|
|
38
|
+
eventType?: string;
|
|
39
|
+
userId?: string;
|
|
40
|
+
user?: UserLike | null;
|
|
41
|
+
session?: SessionLike | null;
|
|
42
|
+
}
|
|
43
|
+
interface DashClientOptions {
|
|
44
|
+
resolveUserId?: (input: {
|
|
45
|
+
userId?: string;
|
|
46
|
+
user?: UserLike | null;
|
|
47
|
+
session?: SessionLike | null;
|
|
48
|
+
}) => string | undefined;
|
|
49
|
+
}
|
|
50
|
+
declare const dashClient: (options?: DashClientOptions) => {
|
|
51
|
+
id: "dash";
|
|
52
|
+
getActions: ($fetch: _$_better_fetch_fetch0.BetterFetch) => {
|
|
53
|
+
dash: {
|
|
54
|
+
getAuditLogs: (input?: DashGetAuditLogsInput) => Promise<{
|
|
55
|
+
data: null;
|
|
56
|
+
error: {
|
|
57
|
+
message?: string | undefined;
|
|
58
|
+
status: number;
|
|
59
|
+
statusText: string;
|
|
60
|
+
};
|
|
61
|
+
} | {
|
|
62
|
+
data: DashAuditLogsResponse;
|
|
63
|
+
error: null;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
pathMethods: {
|
|
68
|
+
"/events/audit-logs": "GET";
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { dashClient as a, DashGetAuditLogsInput as i, DashAuditLogsResponse as n, DashClientOptions as r, DashAuditLog as t };
|