@better-auth/infra 0.4.3 → 0.4.4
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 +10 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +2 -2
- package/dist/{constants-CA5EAv5K.mjs → constants-CARN9f1y.mjs} +1 -1
- package/dist/{crypto-SmxL66Tk.mjs → crypto-DZClUivd.mjs} +1 -1
- package/dist/email.mjs +1 -1
- package/dist/{identify-client-options-CgijjwVT.d.mts → identify-client-options-Cim5Fztg.d.mts} +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +333 -199
- package/dist/native.d.mts +1 -1
- package/dist/native.mjs +2 -2
- package/dist/{pow-retry-BWDJUT8X.mjs → pow-retry-BvjK8YBw.mjs} +1 -1
- package/dist/{types-Ddk4r5x9.d.mts → types-sP9ucYj1.d.mts} +20 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.4] - 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`GET /dash/config` adapter insight** — Config insights report database adapter identity and capability flags.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Sentinel PoW vs rate limits** — Proof-of-work challenges no longer consume Better Auth rate-limit budget.
|
|
17
|
+
|
|
8
18
|
## [0.4.3] - 2026-08-25
|
|
9
19
|
|
|
10
20
|
### Added
|
package/dist/client.d.mts
CHANGED
|
@@ -1,4 +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-
|
|
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-Cim5Fztg.mjs";
|
|
2
2
|
//#region src/sentinel/client.d.ts
|
|
3
3
|
interface SentinelClientOptions {
|
|
4
4
|
/**
|
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 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-
|
|
1
|
+
import { n as hash, o as createKV } from "./crypto-DZClUivd.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-BvjK8YBw.mjs";
|
|
3
3
|
import { env } from "@better-auth/core/env";
|
|
4
4
|
//#region src/sentinel/fingerprint.ts
|
|
5
5
|
function murmurhash3(str, seed = 0) {
|
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-CARN9f1y.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";
|
package/dist/{identify-client-options-CgijjwVT.d.mts → identify-client-options-Cim5Fztg.d.mts}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as KvRetryOptions } from "./types-
|
|
1
|
+
import { m as KvRetryOptions } from "./types-sP9ucYj1.mjs";
|
|
2
2
|
import { BetterAuthClientOptions, ClientStore } from "better-auth";
|
|
3
3
|
import { BetterFetch } from "@better-fetch/fetch";
|
|
4
4
|
//#region src/dash-client.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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-sP9ucYj1.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
4
|
import z from "zod";
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as INFRA_USER_AGENT, n as INFRA_API_URL, o as PLUGIN_VERSION, r as INFRA_KV_URL } from "./constants-
|
|
2
|
-
import { a as createAPI, n as hash$1, o as createKV, r as hmacSha256Hex } from "./crypto-
|
|
1
|
+
import { i as INFRA_USER_AGENT, n as INFRA_API_URL, o as PLUGIN_VERSION, r as INFRA_KV_URL } from "./constants-CARN9f1y.mjs";
|
|
2
|
+
import { a as createAPI, n as hash$1, o as createKV, r as hmacSha256Hex } from "./crypto-DZClUivd.mjs";
|
|
3
3
|
import { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
|
|
4
4
|
import { getCurrentAdapter, getCurrentAuthContext, getCurrentDBAdapterAsyncLocalStorage, runWithTransaction } from "@better-auth/core/context";
|
|
5
5
|
import { APIError, generateId, getAuthTables, logger } from "better-auth";
|
|
@@ -56,6 +56,27 @@ function resolveSentinelOptions(options) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
//#endregion
|
|
59
|
+
//#region ../utils/dist/ip.mjs
|
|
60
|
+
/** Platform-set client IP headers (not accepted from clients on the open internet). */
|
|
61
|
+
const DEFAULT_TRUSTED_IP_HEADERS = [
|
|
62
|
+
"cf-connecting-ip",
|
|
63
|
+
"true-client-ip",
|
|
64
|
+
"x-vercel-forwarded-for"
|
|
65
|
+
];
|
|
66
|
+
/**
|
|
67
|
+
* Resolves client IP from request headers.
|
|
68
|
+
* Defaults to platform-trusted headers only; set `ipAddressHeaders` for self-hosted proxies.
|
|
69
|
+
*/
|
|
70
|
+
function resolveClientIpFromHeaders(headers, ipAddressHeaders) {
|
|
71
|
+
const headerNames = ipAddressHeaders?.length ? ipAddressHeaders : [...DEFAULT_TRUSTED_IP_HEADERS];
|
|
72
|
+
for (const headerName of headerNames) {
|
|
73
|
+
const value = headers.get(headerName);
|
|
74
|
+
if (!value) continue;
|
|
75
|
+
const ip = value.split(",")[0]?.trim();
|
|
76
|
+
if (ip) return ip;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
59
80
|
//#region src/events/constants.ts
|
|
60
81
|
const EVENT_TYPES = {
|
|
61
82
|
USER_CREATED: "user_created",
|
|
@@ -1060,27 +1081,6 @@ const initTrackEvents = ($api) => {
|
|
|
1060
1081
|
return { tracker: { trackEvent } };
|
|
1061
1082
|
};
|
|
1062
1083
|
//#endregion
|
|
1063
|
-
//#region ../utils/dist/ip.mjs
|
|
1064
|
-
/** Platform-set client IP headers (not accepted from clients on the open internet). */
|
|
1065
|
-
const DEFAULT_TRUSTED_IP_HEADERS = [
|
|
1066
|
-
"cf-connecting-ip",
|
|
1067
|
-
"true-client-ip",
|
|
1068
|
-
"x-vercel-forwarded-for"
|
|
1069
|
-
];
|
|
1070
|
-
/**
|
|
1071
|
-
* Resolves client IP from request headers.
|
|
1072
|
-
* Defaults to platform-trusted headers only; set `ipAddressHeaders` for self-hosted proxies.
|
|
1073
|
-
*/
|
|
1074
|
-
function resolveClientIpFromHeaders(headers, ipAddressHeaders) {
|
|
1075
|
-
const headerNames = ipAddressHeaders?.length ? ipAddressHeaders : [...DEFAULT_TRUSTED_IP_HEADERS];
|
|
1076
|
-
for (const headerName of headerNames) {
|
|
1077
|
-
const value = headers.get(headerName);
|
|
1078
|
-
if (!value) continue;
|
|
1079
|
-
const ip = value.split(",")[0]?.trim();
|
|
1080
|
-
if (ip) return ip;
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
//#endregion
|
|
1084
1084
|
//#region src/identification.ts
|
|
1085
1085
|
/**
|
|
1086
1086
|
* Identification Service
|
|
@@ -2316,6 +2316,9 @@ const ERROR_MESSAGES = {
|
|
|
2316
2316
|
compromised_password: "This password has been found in data breaches. Please choose a different password.",
|
|
2317
2317
|
impossible_travel: "Login blocked due to suspicious location change."
|
|
2318
2318
|
};
|
|
2319
|
+
function securityBlockMessage(reason) {
|
|
2320
|
+
return ERROR_MESSAGES[reason] || "Access denied.";
|
|
2321
|
+
}
|
|
2319
2322
|
/**
|
|
2320
2323
|
* Throw a challenge error with appropriate headers
|
|
2321
2324
|
*/
|
|
@@ -2330,56 +2333,152 @@ function throwChallengeError(challenge, reason, message = "Please complete a sec
|
|
|
2330
2333
|
};
|
|
2331
2334
|
throw error;
|
|
2332
2335
|
}
|
|
2333
|
-
/**
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2336
|
+
/**
|
|
2337
|
+
* Cooldown → consolidated security check → challenge/block/allow.
|
|
2338
|
+
* Does not emit events or throw; callers apply the result.
|
|
2339
|
+
*/
|
|
2340
|
+
async function resolveAdmission(input, securityService) {
|
|
2341
|
+
const requestId = input.identification?.requestId ?? input.requestId ?? null;
|
|
2342
|
+
if (input.visitorId || input.ip) {
|
|
2343
|
+
if (await securityService.isBlocked(input.visitorId ?? "", input.ip, requestId)) return { type: "cooldown" };
|
|
2344
|
+
}
|
|
2345
|
+
const verdict = await securityService.checkSecurity({
|
|
2346
|
+
visitorId: input.visitorId,
|
|
2347
|
+
requestId,
|
|
2348
|
+
ip: input.ip,
|
|
2349
|
+
path: input.path,
|
|
2350
|
+
identifier: input.identifier,
|
|
2351
|
+
powSolution: input.powSolution ?? void 0
|
|
2352
|
+
});
|
|
2353
|
+
if (verdict.action === "allow") return {
|
|
2354
|
+
type: "allow",
|
|
2355
|
+
powVerified: verdict.powVerified
|
|
2356
|
+
};
|
|
2337
2357
|
const reason = verdict.reason || "unknown";
|
|
2338
|
-
if (verdict.action === "challenge"
|
|
2339
|
-
|
|
2358
|
+
if (verdict.action === "challenge") {
|
|
2359
|
+
if (!input.visitorId) return {
|
|
2360
|
+
type: "allow",
|
|
2361
|
+
powVerified: verdict.powVerified
|
|
2362
|
+
};
|
|
2363
|
+
const challenge = verdict.challenge || await securityService.generateChallenge(input.visitorId, requestId);
|
|
2340
2364
|
if (!challenge?.trim()) {
|
|
2341
2365
|
logger.warn("[Sentinel] Could not generate PoW challenge (service may be unavailable). Falling back to allow.");
|
|
2342
|
-
return
|
|
2366
|
+
return {
|
|
2367
|
+
type: "allow",
|
|
2368
|
+
powVerified: verdict.powVerified
|
|
2369
|
+
};
|
|
2343
2370
|
}
|
|
2344
|
-
|
|
2371
|
+
return {
|
|
2372
|
+
type: "challenge",
|
|
2373
|
+
challenge,
|
|
2345
2374
|
reason,
|
|
2346
|
-
|
|
2347
|
-
}
|
|
2348
|
-
emitEvaluation(hookCtx, trackEvent, {
|
|
2349
|
-
identifier: checkCtx.identifier,
|
|
2350
|
-
userAgent: checkCtx.userAgent
|
|
2351
|
-
});
|
|
2352
|
-
throwChallengeError(challenge, reason);
|
|
2375
|
+
details: verdict.details
|
|
2376
|
+
};
|
|
2353
2377
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2378
|
+
return {
|
|
2379
|
+
type: "block",
|
|
2380
|
+
reason,
|
|
2381
|
+
details: verdict.details
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Record evaluation side effects and throw on deny. Returns on allow.
|
|
2386
|
+
*/
|
|
2387
|
+
function applyAdmission(admission, hookCtx, meta, trackEvent) {
|
|
2388
|
+
if (hookCtx.context.visitorId || hookCtx.context.ip) recordCheck(hookCtx, "credential_stuffing");
|
|
2389
|
+
if (admission.type === "allow") {
|
|
2390
|
+
recordCheck(hookCtx, "server_security_check");
|
|
2391
|
+
if (admission.powVerified) recordCheck(hookCtx, "pow_challenge");
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
if (admission.type === "cooldown") {
|
|
2395
|
+
setOutcome(hookCtx, "blocked", "credential_stuffing", {
|
|
2396
|
+
phase: "cooldown_active",
|
|
2397
|
+
visitorId: hookCtx.context.untrustedVisitorId ?? void 0,
|
|
2398
|
+
confidence: 1
|
|
2399
|
+
});
|
|
2400
|
+
emitEvaluation(hookCtx, trackEvent, meta);
|
|
2401
|
+
throw new APIError(403, { message: "Too many failed attempts. Please try again later." });
|
|
2402
|
+
}
|
|
2403
|
+
if (admission.type === "challenge") {
|
|
2404
|
+
recordCheck(hookCtx, "server_security_check");
|
|
2405
|
+
setOutcome(hookCtx, "challenged", "server_security_check", {
|
|
2406
|
+
reason: admission.reason,
|
|
2407
|
+
...admission.details
|
|
2362
2408
|
});
|
|
2363
|
-
|
|
2409
|
+
emitEvaluation(hookCtx, trackEvent, meta);
|
|
2410
|
+
throwChallengeError(admission.challenge, admission.reason);
|
|
2364
2411
|
}
|
|
2412
|
+
recordCheck(hookCtx, "server_security_check");
|
|
2413
|
+
setOutcome(hookCtx, "blocked", "server_security_check", {
|
|
2414
|
+
reason: admission.reason,
|
|
2415
|
+
...admission.details
|
|
2416
|
+
});
|
|
2417
|
+
emitEvaluation(hookCtx, trackEvent, meta);
|
|
2418
|
+
throw new APIError("FORBIDDEN", { message: securityBlockMessage(admission.reason) });
|
|
2365
2419
|
}
|
|
2366
2420
|
/**
|
|
2367
|
-
*
|
|
2368
|
-
* one verdict — counts as one billable check.
|
|
2421
|
+
* Resolve + apply in one step (before-hook when rate-limit did not pre-decide).
|
|
2369
2422
|
*/
|
|
2370
2423
|
async function runSecurityChecks(hookCtx, checkCtx, securityService, trackEvent, powSolution) {
|
|
2371
|
-
|
|
2372
|
-
await handleSecurityVerdict(await securityService.checkSecurity({
|
|
2424
|
+
applyAdmission(await resolveAdmission({
|
|
2373
2425
|
visitorId: checkCtx.visitorId,
|
|
2374
|
-
requestId: checkCtx.
|
|
2426
|
+
requestId: checkCtx.requestId,
|
|
2427
|
+
identification: checkCtx.identification,
|
|
2375
2428
|
ip: checkCtx.ip,
|
|
2376
2429
|
path: checkCtx.path,
|
|
2377
2430
|
identifier: checkCtx.identifier,
|
|
2378
|
-
powSolution
|
|
2379
|
-
}), hookCtx,
|
|
2431
|
+
powSolution
|
|
2432
|
+
}, securityService), hookCtx, {
|
|
2433
|
+
identifier: checkCtx.identifier,
|
|
2434
|
+
userAgent: checkCtx.userAgent
|
|
2435
|
+
}, trackEvent);
|
|
2380
2436
|
}
|
|
2381
2437
|
//#endregion
|
|
2382
2438
|
//#region src/sentinel/sentinel.ts
|
|
2439
|
+
/**
|
|
2440
|
+
* Strip basePath from a request URL for path matching.
|
|
2441
|
+
* Local copy of BA's normalizePathname — `@better-auth/core/utils/url` is not
|
|
2442
|
+
* exported on 1.4; `@better-auth/core/utils` barrel is gone on 1.7.
|
|
2443
|
+
*/
|
|
2444
|
+
function normalizeAuthPath(requestUrl, basePath) {
|
|
2445
|
+
try {
|
|
2446
|
+
const pathname = new URL(requestUrl).pathname.replace(/\/+$/, "") || "/";
|
|
2447
|
+
const base = basePath.replace(/\/+$/, "") || "";
|
|
2448
|
+
if (!base || base === "/") return pathname;
|
|
2449
|
+
if (pathname === base) return "/";
|
|
2450
|
+
if (pathname.startsWith(`${base}/`)) return pathname.slice(base.length).replace(/\/+$/, "") || "/";
|
|
2451
|
+
return pathname;
|
|
2452
|
+
} catch {
|
|
2453
|
+
return "/";
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
const SECURITY_PROTECTED_ROUTES = [
|
|
2457
|
+
routes.SIGN_IN_EMAIL,
|
|
2458
|
+
routes.SIGN_IN_USERNAME,
|
|
2459
|
+
routes.SIGN_IN_EMAIL_OTP,
|
|
2460
|
+
routes.SIGN_IN_SOCIAL,
|
|
2461
|
+
routes.SIGN_IN_PASSKEY,
|
|
2462
|
+
routes.SIGN_IN_MAGIC_LINK,
|
|
2463
|
+
routes.SIGN_IN_SSO,
|
|
2464
|
+
routes.SIGN_IN_ANONYMOUS,
|
|
2465
|
+
routes.SIGN_UP_EMAIL,
|
|
2466
|
+
routes.FORGET_PASSWORD,
|
|
2467
|
+
routes.REQUEST_PASSWORD_RESET,
|
|
2468
|
+
routes.RESET_PASSWORD,
|
|
2469
|
+
routes.TWO_FACTOR_VERIFY_TOTP,
|
|
2470
|
+
routes.TWO_FACTOR_VERIFY_BACKUP,
|
|
2471
|
+
routes.TWO_FACTOR_VERIFY_OTP,
|
|
2472
|
+
routes.EMAIL_OTP_SEND,
|
|
2473
|
+
routes.PHONE_SEND_OTP,
|
|
2474
|
+
routes.MAGIC_LINK_VERIFY,
|
|
2475
|
+
routes.ORG_CREATE,
|
|
2476
|
+
routes.CHANGE_EMAIL,
|
|
2477
|
+
routes.CHANGE_PASSWORD,
|
|
2478
|
+
routes.SET_PASSWORD,
|
|
2479
|
+
routes.LINK_SOCIAL,
|
|
2480
|
+
routes.PASSKEY_ADD
|
|
2481
|
+
];
|
|
2383
2482
|
const sentinel = (options) => {
|
|
2384
2483
|
const opts = resolveSentinelOptions(options);
|
|
2385
2484
|
const $api = createAPI(opts);
|
|
@@ -2402,6 +2501,12 @@ const sentinel = (options) => {
|
|
|
2402
2501
|
return typeof err.body === "object" && err.body !== null && "code" in err.body && err.body.code === STALE_ACCOUNT_BLOCK_ERROR.code;
|
|
2403
2502
|
}
|
|
2404
2503
|
let activityTrackingEnabled = false;
|
|
2504
|
+
/**
|
|
2505
|
+
* Admission from rateLimit.customRules for this Request. Reused by the
|
|
2506
|
+
* before-hook and by BA 1.5's second customRules pass on onResponse.
|
|
2507
|
+
* Do not delete — response-phase must still hit. GC cleans up with Request.
|
|
2508
|
+
*/
|
|
2509
|
+
const rateLimitAdmission = /* @__PURE__ */ new WeakMap();
|
|
2405
2510
|
if (!opts.apiKey) logger.warn("[Sentinel] Missing BETTER_AUTH_API_KEY. Security checks may fall back to allow mode when the Infra API rejects requests.");
|
|
2406
2511
|
const securityService = createSecurityClient(opts, $apiThrowing, opts.security || {}, (event) => {
|
|
2407
2512
|
trackEvent({
|
|
@@ -2432,114 +2537,165 @@ const sentinel = (options) => {
|
|
|
2432
2537
|
id: "sentinel",
|
|
2433
2538
|
init(ctx) {
|
|
2434
2539
|
activityTrackingEnabled = (ctx.getPlugin("dash")?.options)?.activityTracking?.enabled === true;
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2540
|
+
const basePath = new URL(ctx.baseURL).pathname;
|
|
2541
|
+
const ipHeaders = ctx.options.advanced?.ipAddress?.ipAddressHeaders || null;
|
|
2542
|
+
const disableIpTracking = ctx.options.advanced?.ipAddress?.disableIpTracking === true;
|
|
2543
|
+
/**
|
|
2544
|
+
* After any user customRule resolves, run Sentinel admission so PoW
|
|
2545
|
+
* challenges skip BA consume (protocol round-trip) even when the app
|
|
2546
|
+
* owns path rules or `**`. Block/cooldown still consume.
|
|
2547
|
+
*/
|
|
2548
|
+
const applyCoordination = async (request, afterUser) => {
|
|
2549
|
+
const path = normalizeAuthPath(request.url, basePath);
|
|
2550
|
+
if (request.method === "GET" || isDashRoute(path) || !matchesAnyRoute(path, SECURITY_PROTECTED_ROUTES)) return afterUser;
|
|
2551
|
+
try {
|
|
2552
|
+
const cached = rateLimitAdmission.get(request);
|
|
2553
|
+
if (cached) return cached.type === "challenge" ? false : afterUser;
|
|
2554
|
+
const requestId = request.headers.get("X-Request-Id");
|
|
2555
|
+
const identification = requestId ? await getIdentification(requestId, $kv, opts.kvOptions.retry) : null;
|
|
2556
|
+
const ip = disableIpTracking ? identification?.ip ?? null : identification?.ip ?? resolveClientIpFromHeaders(request.headers, ipHeaders) ?? null;
|
|
2557
|
+
const admission = await resolveAdmission({
|
|
2558
|
+
visitorId: identification?.visitorId ?? null,
|
|
2559
|
+
requestId,
|
|
2560
|
+
identification,
|
|
2561
|
+
ip,
|
|
2562
|
+
path,
|
|
2563
|
+
powSolution: request.headers.get("X-PoW-Solution")
|
|
2564
|
+
}, securityService);
|
|
2565
|
+
rateLimitAdmission.set(request, admission);
|
|
2566
|
+
if (admission.type === "challenge") return false;
|
|
2567
|
+
return afterUser;
|
|
2568
|
+
} catch {
|
|
2569
|
+
return afterUser;
|
|
2570
|
+
}
|
|
2571
|
+
};
|
|
2572
|
+
const wrapCustomRule = (userRule) => {
|
|
2573
|
+
return async (request, current) => {
|
|
2574
|
+
const userResult = typeof userRule === "function" ? await userRule(request, current) : userRule;
|
|
2575
|
+
return applyCoordination(request, userResult === false ? false : userResult || current);
|
|
2576
|
+
};
|
|
2577
|
+
};
|
|
2578
|
+
const customRules = {};
|
|
2579
|
+
for (const [key, rule] of Object.entries(ctx.rateLimit?.customRules ?? {})) {
|
|
2580
|
+
if (rule === void 0) continue;
|
|
2581
|
+
customRules[key] = wrapCustomRule(rule);
|
|
2582
|
+
}
|
|
2583
|
+
if (!Object.hasOwn(customRules, "**")) customRules["**"] = async (request, current) => applyCoordination(request, current);
|
|
2584
|
+
return {
|
|
2585
|
+
context: { rateLimit: {
|
|
2586
|
+
...ctx.rateLimit,
|
|
2587
|
+
customRules
|
|
2588
|
+
} },
|
|
2589
|
+
options: {
|
|
2590
|
+
emailValidation: opts.security?.emailValidation,
|
|
2591
|
+
emailNormalization: opts.security?.emailNormalization,
|
|
2592
|
+
rateLimit: { customRules },
|
|
2593
|
+
databaseHooks: {
|
|
2594
|
+
user: {
|
|
2595
|
+
create: {
|
|
2596
|
+
async before(user, ctx) {
|
|
2597
|
+
if (!ctx) return;
|
|
2598
|
+
if (isDashRoute(ctx.path)) {
|
|
2599
|
+
if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
|
|
2600
|
+
...user,
|
|
2601
|
+
email: normalizeEmail(user.email, ctx.context)
|
|
2602
|
+
} };
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const visitorId = ctx.context.visitorId;
|
|
2606
|
+
if (opts.security?.freeTrialAbuse?.enabled) {
|
|
2607
|
+
if (!visitorId) {
|
|
2608
|
+
recordCheck(ctx, "free_trial_abuse");
|
|
2609
|
+
setOutcome(ctx, "blocked", "free_trial_abuse", { reason: "identification_required" });
|
|
2610
|
+
emitEvaluation(ctx, trackEvent);
|
|
2611
|
+
throw new APIError("FORBIDDEN", { message: "Account creation is not allowed without device identification." });
|
|
2612
|
+
}
|
|
2613
|
+
recordCheck(ctx, "free_trial_abuse");
|
|
2614
|
+
const reservationId = crypto.randomUUID();
|
|
2615
|
+
ctx.context.freeTrialReservationId = reservationId;
|
|
2616
|
+
const abuseCheck = await securityService.reserveFreeTrialSignup(visitorId, reservationId, ctx.context.requestId ?? null);
|
|
2617
|
+
if (abuseCheck.isAbuse && abuseCheck.action === "block") {
|
|
2618
|
+
setOutcome(ctx, "blocked", "free_trial_abuse", {
|
|
2619
|
+
accountCount: abuseCheck.accountCount,
|
|
2620
|
+
maxAccounts: abuseCheck.maxAccounts
|
|
2621
|
+
});
|
|
2622
|
+
emitEvaluation(ctx, trackEvent);
|
|
2623
|
+
throw new APIError("FORBIDDEN", { message: "Account creation is not allowed from this device." });
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2444
2626
|
if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
|
|
2445
2627
|
...user,
|
|
2446
2628
|
email: normalizeEmail(user.email, ctx.context)
|
|
2447
2629
|
} };
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
setOutcome(ctx, "blocked", "free_trial_abuse", { reason: "identification_required" });
|
|
2455
|
-
emitEvaluation(ctx, trackEvent);
|
|
2456
|
-
throw new APIError("FORBIDDEN", { message: "Account creation is not allowed without device identification." });
|
|
2457
|
-
}
|
|
2458
|
-
recordCheck(ctx, "free_trial_abuse");
|
|
2459
|
-
const reservationId = crypto.randomUUID();
|
|
2460
|
-
ctx.context.freeTrialReservationId = reservationId;
|
|
2461
|
-
const abuseCheck = await securityService.reserveFreeTrialSignup(visitorId, reservationId, ctx.context.requestId ?? null);
|
|
2462
|
-
if (abuseCheck.isAbuse && abuseCheck.action === "block") {
|
|
2463
|
-
setOutcome(ctx, "blocked", "free_trial_abuse", {
|
|
2464
|
-
accountCount: abuseCheck.accountCount,
|
|
2465
|
-
maxAccounts: abuseCheck.maxAccounts
|
|
2466
|
-
});
|
|
2467
|
-
emitEvaluation(ctx, trackEvent);
|
|
2468
|
-
throw new APIError("FORBIDDEN", { message: "Account creation is not allowed from this device." });
|
|
2469
|
-
}
|
|
2630
|
+
},
|
|
2631
|
+
async after(user, ctx) {
|
|
2632
|
+
if (!ctx || isDashRoute(ctx.path)) return;
|
|
2633
|
+
const visitorId = ctx.context.visitorId;
|
|
2634
|
+
const reservationId = ctx.context.freeTrialReservationId;
|
|
2635
|
+
if (visitorId && reservationId && opts.security?.freeTrialAbuse?.enabled) await ctx.context.runInBackgroundOrAwait(securityService.confirmFreeTrialSignup(visitorId, reservationId, user.id, ctx.context.requestId ?? null));
|
|
2470
2636
|
}
|
|
2637
|
+
},
|
|
2638
|
+
update: { async before(user, ctx) {
|
|
2639
|
+
if (!ctx) return;
|
|
2471
2640
|
if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
|
|
2472
2641
|
...user,
|
|
2473
2642
|
email: normalizeEmail(user.email, ctx.context)
|
|
2474
2643
|
} };
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2644
|
+
} }
|
|
2645
|
+
},
|
|
2646
|
+
session: { create: {
|
|
2647
|
+
async before(session, ctx) {
|
|
2477
2648
|
if (!ctx || isDashRoute(ctx.path)) return;
|
|
2478
2649
|
const visitorId = ctx.context.visitorId;
|
|
2479
|
-
const
|
|
2480
|
-
if (
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
distance: travelCheck.distance,
|
|
2507
|
-
speedRequired: travelCheck.speedRequired
|
|
2508
|
-
});
|
|
2509
|
-
emitEvaluation(ctx, trackEvent);
|
|
2510
|
-
throw new APIError("FORBIDDEN", { message: "Login blocked due to suspicious location change." });
|
|
2511
|
-
}
|
|
2512
|
-
if (travelCheck.action === "challenge" && travelCheck.challenge) {
|
|
2513
|
-
setOutcome(ctx, "challenged", "impossible_travel", {
|
|
2514
|
-
from: travelCheck.from,
|
|
2515
|
-
to: travelCheck.to,
|
|
2516
|
-
distance: travelCheck.distance,
|
|
2517
|
-
speedRequired: travelCheck.speedRequired
|
|
2518
|
-
});
|
|
2519
|
-
emitEvaluation(ctx, trackEvent);
|
|
2520
|
-
throwChallengeError(travelCheck.challenge, "impossible_travel", "Unusual login location detected. Please complete a security check.");
|
|
2650
|
+
const identification = ctx.context.identification;
|
|
2651
|
+
if (session.userId && identification?.location && visitorId) {
|
|
2652
|
+
recordCheck(ctx, "impossible_travel");
|
|
2653
|
+
const powSolution = ctx.headers?.get?.("X-PoW-Solution") ?? null;
|
|
2654
|
+
const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId, identification.ip, powSolution, identification.requestId);
|
|
2655
|
+
if (travelCheck?.powVerified) recordCheck(ctx, "pow_challenge");
|
|
2656
|
+
if (travelCheck?.isImpossible) {
|
|
2657
|
+
if (travelCheck.action === "block") {
|
|
2658
|
+
setOutcome(ctx, "blocked", "impossible_travel", {
|
|
2659
|
+
from: travelCheck.from,
|
|
2660
|
+
to: travelCheck.to,
|
|
2661
|
+
distance: travelCheck.distance,
|
|
2662
|
+
speedRequired: travelCheck.speedRequired
|
|
2663
|
+
});
|
|
2664
|
+
emitEvaluation(ctx, trackEvent);
|
|
2665
|
+
throw new APIError("FORBIDDEN", { message: "Login blocked due to suspicious location change." });
|
|
2666
|
+
}
|
|
2667
|
+
if (travelCheck.action === "challenge" && travelCheck.challenge) {
|
|
2668
|
+
setOutcome(ctx, "challenged", "impossible_travel", {
|
|
2669
|
+
from: travelCheck.from,
|
|
2670
|
+
to: travelCheck.to,
|
|
2671
|
+
distance: travelCheck.distance,
|
|
2672
|
+
speedRequired: travelCheck.speedRequired
|
|
2673
|
+
});
|
|
2674
|
+
emitEvaluation(ctx, trackEvent);
|
|
2675
|
+
throwChallengeError(travelCheck.challenge, "impossible_travel", "Unusual login location detected. Please complete a security check.");
|
|
2676
|
+
}
|
|
2521
2677
|
}
|
|
2522
2678
|
}
|
|
2679
|
+
},
|
|
2680
|
+
async after(session, ctx) {
|
|
2681
|
+
if (!ctx || !session.userId || isDashRoute(ctx.path)) return;
|
|
2682
|
+
const visitorId = ctx.context.visitorId;
|
|
2683
|
+
const identification = ctx.context.identification;
|
|
2684
|
+
let user = null;
|
|
2685
|
+
try {
|
|
2686
|
+
user = await getUserById(session.userId, ctx, { includeLastActiveAt: activityTrackingEnabled });
|
|
2687
|
+
} catch (error) {
|
|
2688
|
+
logger.warn("[Sentinel] Failed to fetch user for security checks:", error);
|
|
2689
|
+
}
|
|
2690
|
+
if (visitorId) {
|
|
2691
|
+
if (await securityService.checkUnknownDevice(session.userId, visitorId) && user?.email) await ctx.context.runInBackgroundOrAwait(securityService.notifyUnknownDevice(session.userId, user.email, identification));
|
|
2692
|
+
}
|
|
2693
|
+
if (opts.security?.impossibleTravel?.enabled && identification?.location) await ctx.context.runInBackgroundOrAwait(securityService.storeLastLocation(session.userId, identification.location, identification.ip));
|
|
2523
2694
|
}
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
if (!ctx || !session.userId || isDashRoute(ctx.path)) return;
|
|
2527
|
-
const visitorId = ctx.context.visitorId;
|
|
2528
|
-
const identification = ctx.context.identification;
|
|
2529
|
-
let user = null;
|
|
2530
|
-
try {
|
|
2531
|
-
user = await getUserById(session.userId, ctx, { includeLastActiveAt: activityTrackingEnabled });
|
|
2532
|
-
} catch (error) {
|
|
2533
|
-
logger.warn("[Sentinel] Failed to fetch user for security checks:", error);
|
|
2534
|
-
}
|
|
2535
|
-
if (visitorId) {
|
|
2536
|
-
if (await securityService.checkUnknownDevice(session.userId, visitorId) && user?.email) await ctx.context.runInBackgroundOrAwait(securityService.notifyUnknownDevice(session.userId, user.email, identification));
|
|
2537
|
-
}
|
|
2538
|
-
if (opts.security?.impossibleTravel?.enabled && identification?.location) await ctx.context.runInBackgroundOrAwait(securityService.storeLastLocation(session.userId, identification.location, identification.ip));
|
|
2539
|
-
}
|
|
2540
|
-
} }
|
|
2695
|
+
} }
|
|
2696
|
+
}
|
|
2541
2697
|
}
|
|
2542
|
-
}
|
|
2698
|
+
};
|
|
2543
2699
|
},
|
|
2544
2700
|
hooks: {
|
|
2545
2701
|
before: [
|
|
@@ -2558,64 +2714,25 @@ const sentinel = (options) => {
|
|
|
2558
2714
|
const visitorId = ctx.context.visitorId;
|
|
2559
2715
|
const identification = ctx.context.identification;
|
|
2560
2716
|
const ip = ctx.context.ip;
|
|
2561
|
-
|
|
2562
|
-
routes.SIGN_IN_EMAIL,
|
|
2563
|
-
routes.SIGN_IN_USERNAME,
|
|
2564
|
-
routes.SIGN_IN_EMAIL_OTP,
|
|
2565
|
-
routes.SIGN_IN_SOCIAL,
|
|
2566
|
-
routes.SIGN_IN_PASSKEY,
|
|
2567
|
-
routes.SIGN_IN_MAGIC_LINK,
|
|
2568
|
-
routes.SIGN_IN_SSO,
|
|
2569
|
-
routes.SIGN_IN_ANONYMOUS
|
|
2570
|
-
]);
|
|
2571
|
-
const isSignUp = matchesAnyRoute(ctx.path, [routes.SIGN_UP_EMAIL]);
|
|
2572
|
-
const isPasswordReset = matchesAnyRoute(ctx.path, [
|
|
2573
|
-
routes.FORGET_PASSWORD,
|
|
2574
|
-
routes.REQUEST_PASSWORD_RESET,
|
|
2575
|
-
routes.RESET_PASSWORD
|
|
2576
|
-
]);
|
|
2577
|
-
const isTwoFactor = matchesAnyRoute(ctx.path, [
|
|
2578
|
-
routes.TWO_FACTOR_VERIFY_TOTP,
|
|
2579
|
-
routes.TWO_FACTOR_VERIFY_BACKUP,
|
|
2580
|
-
routes.TWO_FACTOR_VERIFY_OTP
|
|
2581
|
-
]);
|
|
2582
|
-
const isOtpSend = matchesAnyRoute(ctx.path, [routes.EMAIL_OTP_SEND, routes.PHONE_SEND_OTP]);
|
|
2583
|
-
const isMagicLinkVerify = matchesAnyRoute(ctx.path, [routes.MAGIC_LINK_VERIFY]);
|
|
2584
|
-
const isOrgCreate = matchesAnyRoute(ctx.path, [routes.ORG_CREATE]);
|
|
2585
|
-
const isSensitiveAction = matchesAnyRoute(ctx.path, [
|
|
2586
|
-
routes.CHANGE_EMAIL,
|
|
2587
|
-
routes.CHANGE_PASSWORD,
|
|
2588
|
-
routes.SET_PASSWORD,
|
|
2589
|
-
routes.LINK_SOCIAL,
|
|
2590
|
-
routes.PASSKEY_ADD
|
|
2591
|
-
]);
|
|
2592
|
-
if (!(isSignIn || isSignUp || isPasswordReset || isTwoFactor || isOtpSend || isMagicLinkVerify || isOrgCreate || isSensitiveAction)) return;
|
|
2717
|
+
if (!matchesAnyRoute(ctx.path, SECURITY_PROTECTED_ROUTES)) return;
|
|
2593
2718
|
const requestBody = ctx.body;
|
|
2594
2719
|
const identifier = requestBody?.email || requestBody?.phone || requestBody?.username || void 0;
|
|
2595
2720
|
const powSolution = ctx.headers?.get?.("X-PoW-Solution");
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
identifier,
|
|
2606
|
-
userAgent: ctx.headers?.get?.("user-agent") || ""
|
|
2607
|
-
});
|
|
2608
|
-
throw new APIError(403, { message: "Too many failed attempts. Please try again later." });
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
await runSecurityChecks(ctx, {
|
|
2612
|
-
path: ctx.path,
|
|
2721
|
+
const userAgent = ctx.headers?.get?.("user-agent") || "";
|
|
2722
|
+
const meta = {
|
|
2723
|
+
identifier,
|
|
2724
|
+
userAgent
|
|
2725
|
+
};
|
|
2726
|
+
const cached = ctx.request ? rateLimitAdmission.get(ctx.request) : void 0;
|
|
2727
|
+
if (cached) applyAdmission(cached, ctx, meta, trackEvent);
|
|
2728
|
+
else await runSecurityChecks(ctx, {
|
|
2729
|
+
path: ctx.path ?? "",
|
|
2613
2730
|
identifier,
|
|
2614
2731
|
visitorId,
|
|
2615
2732
|
requestId: ctx.context.requestId ?? null,
|
|
2616
2733
|
identification,
|
|
2617
|
-
ip
|
|
2618
|
-
userAgent
|
|
2734
|
+
ip,
|
|
2735
|
+
userAgent
|
|
2619
2736
|
}, securityService, trackEvent, powSolution);
|
|
2620
2737
|
if (matchesAnyRoute(ctx.path, [
|
|
2621
2738
|
routes.SIGN_UP_EMAIL,
|
|
@@ -2633,7 +2750,7 @@ const sentinel = (options) => {
|
|
|
2633
2750
|
setOutcome(ctx, "blocked", "compromised_password", { breachCount: compromisedResult.breachCount });
|
|
2634
2751
|
emitEvaluation(ctx, trackEvent, {
|
|
2635
2752
|
identifier,
|
|
2636
|
-
userAgent
|
|
2753
|
+
userAgent
|
|
2637
2754
|
});
|
|
2638
2755
|
throw new APIError("BAD_REQUEST", {
|
|
2639
2756
|
message: "This password has been found in data breaches. Please choose a different password.",
|
|
@@ -4280,6 +4397,23 @@ const getConfig = (options) => {
|
|
|
4280
4397
|
emailVerification: { sendVerificationEmailEnabled: !!ctx.context.options.emailVerification?.sendVerificationEmail },
|
|
4281
4398
|
insights: {
|
|
4282
4399
|
hasDatabase: !!ctx.context.options.database,
|
|
4400
|
+
adapter: (() => {
|
|
4401
|
+
const adapter = ctx.context.adapter;
|
|
4402
|
+
if (!adapter) return null;
|
|
4403
|
+
const adapterConfig = adapter.options?.adapterConfig;
|
|
4404
|
+
return {
|
|
4405
|
+
id: adapter.id ?? adapterConfig?.adapterId ?? null,
|
|
4406
|
+
name: adapterConfig?.adapterName ?? adapter.id ?? adapterConfig?.adapterId ?? null,
|
|
4407
|
+
usePlural: adapterConfig?.usePlural ?? false,
|
|
4408
|
+
supportsTransactions: typeof adapterConfig?.transaction === "function",
|
|
4409
|
+
supportsJSON: typeof adapterConfig?.supportsJSON === "boolean" ? adapterConfig.supportsJSON : null,
|
|
4410
|
+
supportsDates: typeof adapterConfig?.supportsDates === "boolean" ? adapterConfig.supportsDates : null,
|
|
4411
|
+
supportsBooleans: typeof adapterConfig?.supportsBooleans === "boolean" ? adapterConfig.supportsBooleans : null,
|
|
4412
|
+
supportsArrays: typeof adapterConfig?.supportsArrays === "boolean" ? adapterConfig.supportsArrays : null,
|
|
4413
|
+
supportsNumericIds: typeof adapterConfig?.supportsNumericIds === "boolean" ? adapterConfig.supportsNumericIds : null,
|
|
4414
|
+
supportsUUIDs: typeof adapterConfig?.supportsUUIDs === "boolean" ? adapterConfig.supportsUUIDs : null
|
|
4415
|
+
};
|
|
4416
|
+
})(),
|
|
4283
4417
|
cookies: advancedOptions?.cookies ? Object.entries(advancedOptions.cookies).map(([key, value]) => ({
|
|
4284
4418
|
key,
|
|
4285
4419
|
name: value?.name,
|
package/dist/native.d.mts
CHANGED
|
@@ -1,4 +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-
|
|
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-Cim5Fztg.mjs";
|
|
2
2
|
import { BetterAuthClientPlugin } from "better-auth";
|
|
3
3
|
//#region src/sentinel/native/client.d.ts
|
|
4
4
|
interface SentinelNativeClientOptions {
|
package/dist/native.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as createKV, t as bytesToHex } from "./crypto-
|
|
2
|
-
import { a as resolveSentinelClientIdentifyUrl, c as identify, i as solvePoWChallenge, l as dashClient, n as decodePoWChallenge, o as resolveIdentifyClientRetry, r as encodePoWSolution, t as createPowRetryTimeout } from "./pow-retry-
|
|
1
|
+
import { o as createKV, t as bytesToHex } from "./crypto-DZClUivd.mjs";
|
|
2
|
+
import { a as resolveSentinelClientIdentifyUrl, c as identify, i as solvePoWChallenge, l as dashClient, n as decodePoWChallenge, o as resolveIdentifyClientRetry, r as encodePoWSolution, t as createPowRetryTimeout } from "./pow-retry-BvjK8YBw.mjs";
|
|
3
3
|
import { env } from "@better-auth/core/env";
|
|
4
4
|
import { Dimensions, InteractionManager, PixelRatio, Platform } from "react-native";
|
|
5
5
|
//#region src/sentinel/native/components.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as randomBytes, n as hash, t as bytesToHex } from "./crypto-
|
|
1
|
+
import { i as randomBytes, n as hash, t as bytesToHex } from "./crypto-DZClUivd.mjs";
|
|
2
2
|
//#region src/dash-client.ts
|
|
3
3
|
function resolveDashUserId(input, options) {
|
|
4
4
|
return input.userId || options?.resolveUserId?.({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BetterAuthPlugin, GenericEndpointContext } from "better-auth";
|
|
2
2
|
import { createFetch } from "@better-fetch/fetch";
|
|
3
|
+
import { BetterAuthRateLimitRule } from "@better-auth/core";
|
|
3
4
|
import { APIError as APIError$1, Endpoint, EndpointOptions } from "better-call";
|
|
4
5
|
//#region src/identification.d.ts
|
|
5
6
|
interface IPLocation {
|
|
@@ -167,6 +168,20 @@ type SecurityEventType = "unknown_device" | "credential_stuffing" | "impossible_
|
|
|
167
168
|
declare const sentinel: (options?: SentinelOptions) => {
|
|
168
169
|
id: "sentinel";
|
|
169
170
|
init(ctx: import("better-auth").AuthContext): {
|
|
171
|
+
context: {
|
|
172
|
+
rateLimit: {
|
|
173
|
+
customRules: {
|
|
174
|
+
[key: string]: false | BetterAuthRateLimitRule | ((request: Request, currentRule: BetterAuthRateLimitRule) => import("@better-auth/core").Awaitable<false | BetterAuthRateLimitRule>);
|
|
175
|
+
};
|
|
176
|
+
enabled: boolean;
|
|
177
|
+
window: number;
|
|
178
|
+
max: number;
|
|
179
|
+
storage: "memory" | "database" | "secondary-storage";
|
|
180
|
+
modelName?: import("better-auth").LiteralString | "rateLimit" | undefined;
|
|
181
|
+
fields?: Partial<Record<"key" | "count" | "lastRequest", string>> | undefined;
|
|
182
|
+
customStorage?: import("better-auth").BetterAuthRateLimitStorage | undefined;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
170
185
|
options: {
|
|
171
186
|
emailValidation: {
|
|
172
187
|
enabled?: boolean;
|
|
@@ -177,6 +192,11 @@ declare const sentinel: (options?: SentinelOptions) => {
|
|
|
177
192
|
emailNormalization: {
|
|
178
193
|
enabled?: boolean;
|
|
179
194
|
} | undefined;
|
|
195
|
+
rateLimit: {
|
|
196
|
+
customRules: {
|
|
197
|
+
[key: string]: false | BetterAuthRateLimitRule | ((request: Request, currentRule: BetterAuthRateLimitRule) => import("@better-auth/core").Awaitable<false | BetterAuthRateLimitRule>);
|
|
198
|
+
};
|
|
199
|
+
};
|
|
180
200
|
databaseHooks: {
|
|
181
201
|
user: {
|
|
182
202
|
create: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/infra",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Dashboard and analytics plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"happy-dom": "^20.9.0",
|
|
75
75
|
"msw": "^2.14.6",
|
|
76
76
|
"tsdown": "^0.22.14",
|
|
77
|
-
"typescript": "^
|
|
77
|
+
"typescript": "^6.0.3",
|
|
78
78
|
"zod": "4.3.6"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|