@better-auth/infra 0.4.7 → 0.4.9
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 +12 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +2 -2
- package/dist/{constants-BypXAAvQ.mjs → constants-CdZodAXp.mjs} +1 -1
- package/dist/{crypto-C9Iqqv8l.mjs → crypto-DDadesq-.mjs} +11 -8
- package/dist/email.d.mts +5 -0
- package/dist/email.mjs +4 -3
- package/dist/{identify-client-options-Be9mAljS.d.mts → identify-client-options-DgKA4Eq8.d.mts} +1 -1
- package/dist/index.d.mts +173 -166
- package/dist/index.mjs +214 -140
- package/dist/native.d.mts +1 -1
- package/dist/native.mjs +2 -2
- package/dist/{pow-retry-Bk5otjQT.mjs → pow-retry-DRUkPcBn.mjs} +1 -1
- package/dist/{types-Ba7NK8JV.d.mts → types-BkUWsMpe.d.mts} +67 -196
- package/package.json +18 -10
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-CdZodAXp.mjs";
|
|
2
|
+
import { a as createAPI, n as hash$1, o as createKV, r as hmacSha256Hex } from "./crypto-DDadesq-.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";
|
|
@@ -21,14 +21,18 @@ function resolveConnectionOptions(options) {
|
|
|
21
21
|
apiUrl: options?.apiUrl || INFRA_API_URL,
|
|
22
22
|
kvUrl: options?.kvUrl || INFRA_KV_URL,
|
|
23
23
|
apiKey: options?.apiKey || env.BETTER_AUTH_API_KEY || "",
|
|
24
|
-
apiOptions: {
|
|
24
|
+
apiOptions: {
|
|
25
|
+
timeout: options?.apiOptions?.timeout ?? options?.apiTimeout ?? 3e3,
|
|
26
|
+
headers: options?.apiOptions?.headers
|
|
27
|
+
},
|
|
25
28
|
kvOptions: {
|
|
26
29
|
timeout: options?.kvOptions?.timeout ?? options?.kvTimeout ?? 1e3,
|
|
27
30
|
retry: {
|
|
28
31
|
attempts: options?.kvOptions?.retry?.attempts ?? 2,
|
|
29
32
|
baseDelay: options?.kvOptions?.retry?.baseDelay ?? 400,
|
|
30
33
|
maxDelay: options?.kvOptions?.retry?.maxDelay ?? 600
|
|
31
|
-
}
|
|
34
|
+
},
|
|
35
|
+
headers: options?.kvOptions?.headers
|
|
32
36
|
}
|
|
33
37
|
};
|
|
34
38
|
}
|
|
@@ -56,6 +60,21 @@ function resolveSentinelOptions(options) {
|
|
|
56
60
|
};
|
|
57
61
|
}
|
|
58
62
|
//#endregion
|
|
63
|
+
//#region src/types.ts
|
|
64
|
+
/** Type-only cast — does not wrap or mutate ctx. */
|
|
65
|
+
function asInfraContext(ctx) {
|
|
66
|
+
return ctx;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* better-auth createAuthMiddleware with InfraAuthContext on `ctx.context`.
|
|
70
|
+
* Pass-through at runtime — type-only (does not wrap the handler).
|
|
71
|
+
*
|
|
72
|
+
* Prefer this for dash/sentinel/identification hooks. Keep better-auth's
|
|
73
|
+
* createAuthMiddleware where middleware return values must extend AuthContext
|
|
74
|
+
* (e.g. JWT `payload` via `use: [...]`).
|
|
75
|
+
*/
|
|
76
|
+
const createAuthMiddleware$1 = createAuthMiddleware;
|
|
77
|
+
//#endregion
|
|
59
78
|
//#region ../utils/dist/ip.mjs
|
|
60
79
|
/** Platform-set client IP headers (not accepted from clients on the open internet). */
|
|
61
80
|
const DEFAULT_TRUSTED_IP_HEADERS = [
|
|
@@ -77,6 +96,91 @@ function resolveClientIpFromHeaders(headers, ipAddressHeaders) {
|
|
|
77
96
|
}
|
|
78
97
|
}
|
|
79
98
|
//#endregion
|
|
99
|
+
//#region ../utils/dist/url.mjs
|
|
100
|
+
/**
|
|
101
|
+
* URL helpers: normalization, SSRF-safe parsing/validation, and path/query segments.
|
|
102
|
+
*/
|
|
103
|
+
const HTTP_PROTOCOLS = ["http:", "https:"];
|
|
104
|
+
function isAllowedProtocol(protocol, allowedProtocols) {
|
|
105
|
+
return (allowedProtocols?.length ? allowedProtocols : HTTP_PROTOCOLS).includes(protocol);
|
|
106
|
+
}
|
|
107
|
+
/** Normalizes a base URL and path by trimming slashes and joining them. */
|
|
108
|
+
function normalizeUrl(base, path) {
|
|
109
|
+
return `${base.replace(/\/+$/, "")}${path ? `/${path.replace(/^\/+|\/+$/g, "")}` : ""}`;
|
|
110
|
+
}
|
|
111
|
+
function normalizePath(path) {
|
|
112
|
+
const trimmed = path.trim();
|
|
113
|
+
if (!trimmed) throw new Error("Path must be a non-empty string");
|
|
114
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, "")}`;
|
|
115
|
+
}
|
|
116
|
+
/** Normalizes a base URL and path into separate parts and a joined URL. */
|
|
117
|
+
function resolveBaseUrlPath(baseURL, basePath) {
|
|
118
|
+
const cleanBase = baseURL.trim().replace(/\/+$/, "");
|
|
119
|
+
const resolvedBasePath = normalizePath(basePath);
|
|
120
|
+
return {
|
|
121
|
+
baseURL: cleanBase,
|
|
122
|
+
basePath: resolvedBasePath,
|
|
123
|
+
url: normalizeUrl(cleanBase, resolvedBasePath)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Falls back when basePath is null, undefined, or blank/whitespace. */
|
|
127
|
+
function resolveProjectBasePath(basePath) {
|
|
128
|
+
return basePath?.trim() || "/api/auth";
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Normalized project auth base URL + path (blank basePath → default).
|
|
132
|
+
* If `baseURL` already has a non-root pathname, that path wins over `basePath`
|
|
133
|
+
* (Better Auth path-in-baseURL form).
|
|
134
|
+
*/
|
|
135
|
+
function resolveProjectAuthBase(baseURL, basePath) {
|
|
136
|
+
try {
|
|
137
|
+
const url = new URL(baseURL);
|
|
138
|
+
const pathname = url.pathname.replace(/\/+$/, "") || "";
|
|
139
|
+
if (pathname && pathname !== "/") return resolveBaseUrlPath(url.origin, pathname);
|
|
140
|
+
return resolveBaseUrlPath(url.origin, resolveProjectBasePath(basePath));
|
|
141
|
+
} catch {
|
|
142
|
+
return resolveBaseUrlPath(baseURL, resolveProjectBasePath(basePath));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function toAllowedOriginSet(allowedOrigins) {
|
|
146
|
+
const set = /* @__PURE__ */ new Set();
|
|
147
|
+
for (const raw of allowedOrigins) try {
|
|
148
|
+
const parsed = new URL(raw.trim());
|
|
149
|
+
if (isAllowedProtocol(parsed.protocol, HTTP_PROTOCOLS)) set.add(parsed.origin);
|
|
150
|
+
} catch {}
|
|
151
|
+
return set;
|
|
152
|
+
}
|
|
153
|
+
function isAllowedOrigin(origin, allowedOrigins) {
|
|
154
|
+
if (!allowedOrigins.length) return false;
|
|
155
|
+
return toAllowedOriginSet(allowedOrigins).has(origin);
|
|
156
|
+
}
|
|
157
|
+
function safeResolveUrl(raw, baseURL) {
|
|
158
|
+
const trim = raw.trim();
|
|
159
|
+
if (!trim) return null;
|
|
160
|
+
if (trim.startsWith("//") || trim.includes("\\")) return null;
|
|
161
|
+
if (/[\u0000-\u001F\u007F]/.test(trim)) return null;
|
|
162
|
+
try {
|
|
163
|
+
if (trim.startsWith("/")) return new URL(trim, baseURL);
|
|
164
|
+
return new URL(trim);
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function getSafeUniqueOrigins(urls) {
|
|
170
|
+
return [...toAllowedOriginSet(urls)];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns an absolute URL only if its protocol and origin are allowed (after
|
|
174
|
+
* resolving relative paths against `baseURL`). Otherwise returns `null`.
|
|
175
|
+
*/
|
|
176
|
+
function safeUrlParse(input) {
|
|
177
|
+
if (input.raw == null || input.raw.trim() === "") return null;
|
|
178
|
+
const target = safeResolveUrl(input.raw, input.baseURL);
|
|
179
|
+
if (!target || !isAllowedProtocol(target.protocol, input.allowedProtocols)) return null;
|
|
180
|
+
if (!isAllowedOrigin(target.origin, input.allowedOrigins ?? [])) return null;
|
|
181
|
+
return target.href;
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
80
184
|
//#region src/events/constants.ts
|
|
81
185
|
const EVENT_TYPES = {
|
|
82
186
|
USER_CREATED: "user_created",
|
|
@@ -1102,7 +1206,7 @@ function shouldRunIdentification(ctx, getRoutes = IDENTIFICATION_GET_ROUTES) {
|
|
|
1102
1206
|
* needs it; together they would append duplicate Set-Cookie headers).
|
|
1103
1207
|
*/
|
|
1104
1208
|
function createIdentificationCookieAfterMiddleware() {
|
|
1105
|
-
return createAuthMiddleware(async (ctx) => {
|
|
1209
|
+
return createAuthMiddleware$1(async (ctx) => {
|
|
1106
1210
|
if (ctx.context.infraRidCookieSet) return;
|
|
1107
1211
|
ctx.context.infraRidCookieSet = true;
|
|
1108
1212
|
const headerRequestId = ctx.request?.headers.get("X-Request-Id");
|
|
@@ -1230,7 +1334,7 @@ function resolveUntrustedVisitorId(visitorId, ip, headerVisitorId) {
|
|
|
1230
1334
|
*/
|
|
1231
1335
|
function createIdentificationMiddleware($kv, options) {
|
|
1232
1336
|
const retry = options?.retry ?? DEFAULT_KV_RETRY;
|
|
1233
|
-
return createAuthMiddleware(async (ctx) => {
|
|
1337
|
+
return createAuthMiddleware$1(async (ctx) => {
|
|
1234
1338
|
const skipIdentification = options?.skipIdentification?.(ctx) ?? false;
|
|
1235
1339
|
let headerVisitorId = null;
|
|
1236
1340
|
let requestId = null;
|
|
@@ -1368,7 +1472,8 @@ function createSecurityClient(conn, $api, options, onSecurityEvent) {
|
|
|
1368
1472
|
const resolvedApiUrl = conn.apiUrl || INFRA_API_URL;
|
|
1369
1473
|
const emailSender = createEmailSender({
|
|
1370
1474
|
apiUrl: resolvedApiUrl,
|
|
1371
|
-
apiKey: conn.apiKey
|
|
1475
|
+
apiKey: conn.apiKey,
|
|
1476
|
+
apiOptions: { headers: conn.apiOptions.headers }
|
|
1372
1477
|
});
|
|
1373
1478
|
function logEvent(event) {
|
|
1374
1479
|
const fullEvent = {
|
|
@@ -2475,7 +2580,8 @@ const sentinel = (options) => {
|
|
|
2475
2580
|
const $kv = createKV({
|
|
2476
2581
|
kvUrl: opts.kvUrl,
|
|
2477
2582
|
apiKey: opts.apiKey,
|
|
2478
|
-
timeout: opts.kvOptions.timeout
|
|
2583
|
+
timeout: opts.kvOptions.timeout,
|
|
2584
|
+
headers: opts.kvOptions.headers
|
|
2479
2585
|
});
|
|
2480
2586
|
const { tracker } = initTrackEvents($api);
|
|
2481
2587
|
const { trackEvent } = tracker;
|
|
@@ -2526,7 +2632,7 @@ const sentinel = (options) => {
|
|
|
2526
2632
|
id: "sentinel",
|
|
2527
2633
|
init(ctx) {
|
|
2528
2634
|
activityTrackingEnabled = (ctx.getPlugin("dash")?.options)?.activityTracking?.enabled === true;
|
|
2529
|
-
const basePath =
|
|
2635
|
+
const basePath = ctx.baseURL ? resolveProjectAuthBase(ctx.baseURL, ctx.options.basePath).basePath : resolveProjectBasePath(ctx.options.basePath);
|
|
2530
2636
|
const ipHeaders = ctx.options.advanced?.ipAddress?.ipAddressHeaders || null;
|
|
2531
2637
|
const disableIpTracking = ctx.options.advanced?.ipAddress?.disableIpTracking === true;
|
|
2532
2638
|
/**
|
|
@@ -2582,8 +2688,9 @@ const sentinel = (options) => {
|
|
|
2582
2688
|
databaseHooks: {
|
|
2583
2689
|
user: {
|
|
2584
2690
|
create: {
|
|
2585
|
-
async before(user,
|
|
2586
|
-
if (!
|
|
2691
|
+
async before(user, _ctx) {
|
|
2692
|
+
if (!_ctx) return;
|
|
2693
|
+
const ctx = asInfraContext(_ctx);
|
|
2587
2694
|
if (isDashRoute(ctx.path)) {
|
|
2588
2695
|
if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
|
|
2589
2696
|
...user,
|
|
@@ -2617,8 +2724,9 @@ const sentinel = (options) => {
|
|
|
2617
2724
|
email: normalizeEmail(user.email, ctx.context)
|
|
2618
2725
|
} };
|
|
2619
2726
|
},
|
|
2620
|
-
async after(user,
|
|
2621
|
-
if (!
|
|
2727
|
+
async after(user, _ctx) {
|
|
2728
|
+
if (!_ctx || isDashRoute(_ctx.path)) return;
|
|
2729
|
+
const ctx = asInfraContext(_ctx);
|
|
2622
2730
|
const visitorId = ctx.context.visitorId;
|
|
2623
2731
|
const reservationId = ctx.context.freeTrialReservationId;
|
|
2624
2732
|
if (visitorId && reservationId && opts.security?.freeTrialAbuse?.enabled) await ctx.context.runInBackgroundOrAwait(securityService.confirmFreeTrialSignup(visitorId, reservationId, user.id, ctx.context.requestId ?? null));
|
|
@@ -2633,8 +2741,9 @@ const sentinel = (options) => {
|
|
|
2633
2741
|
} }
|
|
2634
2742
|
},
|
|
2635
2743
|
session: { create: {
|
|
2636
|
-
async before(session,
|
|
2637
|
-
if (!
|
|
2744
|
+
async before(session, _ctx) {
|
|
2745
|
+
if (!_ctx || isDashRoute(_ctx.path)) return;
|
|
2746
|
+
const ctx = asInfraContext(_ctx);
|
|
2638
2747
|
const visitorId = ctx.context.visitorId;
|
|
2639
2748
|
const identification = ctx.context.identification;
|
|
2640
2749
|
if (session.userId && identification?.location && visitorId) {
|
|
@@ -2666,8 +2775,9 @@ const sentinel = (options) => {
|
|
|
2666
2775
|
}
|
|
2667
2776
|
}
|
|
2668
2777
|
},
|
|
2669
|
-
async after(session,
|
|
2670
|
-
if (!
|
|
2778
|
+
async after(session, _ctx) {
|
|
2779
|
+
if (!_ctx || !session.userId || isDashRoute(_ctx.path)) return;
|
|
2780
|
+
const ctx = asInfraContext(_ctx);
|
|
2671
2781
|
const visitorId = ctx.context.visitorId;
|
|
2672
2782
|
const identification = ctx.context.identification;
|
|
2673
2783
|
let user = null;
|
|
@@ -2699,7 +2809,7 @@ const sentinel = (options) => {
|
|
|
2699
2809
|
...phoneValidationHooks.before,
|
|
2700
2810
|
{
|
|
2701
2811
|
matcher: (ctx) => ctx.request?.method !== "GET" && !isDashRoute(ctx.path),
|
|
2702
|
-
handler: createAuthMiddleware(async (ctx) => {
|
|
2812
|
+
handler: createAuthMiddleware$1(async (ctx) => {
|
|
2703
2813
|
const visitorId = ctx.context.visitorId;
|
|
2704
2814
|
const identification = ctx.context.identification;
|
|
2705
2815
|
const ip = ctx.context.ip;
|
|
@@ -2759,7 +2869,7 @@ const sentinel = (options) => {
|
|
|
2759
2869
|
},
|
|
2760
2870
|
{
|
|
2761
2871
|
matcher: (ctx) => !!opts.security?.staleUsers?.enabled && !isDashRoute(ctx.path),
|
|
2762
|
-
handler: createAuthMiddleware(async (ctx) => {
|
|
2872
|
+
handler: createAuthMiddleware$1(async (ctx) => {
|
|
2763
2873
|
if (ctx.context.returned instanceof Error) return;
|
|
2764
2874
|
const created = ctx.context.newSession;
|
|
2765
2875
|
const userId = created?.user?.id ?? created?.session?.userId;
|
|
@@ -2806,7 +2916,7 @@ const sentinel = (options) => {
|
|
|
2806
2916
|
},
|
|
2807
2917
|
{
|
|
2808
2918
|
matcher: (ctx) => ctx.request?.method !== "GET" && !isDashRoute(ctx.path),
|
|
2809
|
-
handler: createAuthMiddleware(async (ctx) => {
|
|
2919
|
+
handler: createAuthMiddleware$1(async (ctx) => {
|
|
2810
2920
|
const untrustedVisitorId = ctx.context.untrustedVisitorId;
|
|
2811
2921
|
const ip = ctx.context.ip;
|
|
2812
2922
|
const body = ctx.body;
|
|
@@ -4242,6 +4352,66 @@ const jwtValidateMiddleware = (options) => {
|
|
|
4242
4352
|
});
|
|
4243
4353
|
};
|
|
4244
4354
|
//#endregion
|
|
4355
|
+
//#region src/lib/safe-url.ts
|
|
4356
|
+
function isSafeHttpUrl(value) {
|
|
4357
|
+
try {
|
|
4358
|
+
const url = new URL(value);
|
|
4359
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "";
|
|
4360
|
+
} catch {
|
|
4361
|
+
return false;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
/** http(s) absolute URL without embedded credentials. */
|
|
4365
|
+
const safeUrlSchema = z.string().trim().min(1).pipe(z.url().refine(isSafeHttpUrl, { message: "URL must be a valid http(s) URL without credentials" })).transform((value) => new URL(value).href);
|
|
4366
|
+
const optionalSafeUrlSchema = z.union([safeUrlSchema, z.literal("")]).optional();
|
|
4367
|
+
/**
|
|
4368
|
+
* Request-scoped full auth URL (origin + mount). Prefer over `options.baseURL`,
|
|
4369
|
+
* which is origin-only or a dynamic config object.
|
|
4370
|
+
* BA resolves this before endpoint handlers; missing value is a programming error.
|
|
4371
|
+
*/
|
|
4372
|
+
function getAuthBaseUrl(ctx) {
|
|
4373
|
+
const resolved = ctx.context.baseURL?.trim();
|
|
4374
|
+
if (!resolved) throw new APIError$1("INTERNAL_SERVER_ERROR", { message: "Auth base URL is not resolved for this request." });
|
|
4375
|
+
return resolved;
|
|
4376
|
+
}
|
|
4377
|
+
/** App origin for post-auth redirects (not the auth mount path). */
|
|
4378
|
+
function getAuthOrigin(ctx) {
|
|
4379
|
+
return new URL(getAuthBaseUrl(ctx)).origin;
|
|
4380
|
+
}
|
|
4381
|
+
function getAllowedAuthOrigins(ctx, extraOrigins = []) {
|
|
4382
|
+
return getSafeUniqueOrigins([
|
|
4383
|
+
getAuthBaseUrl(ctx),
|
|
4384
|
+
...ctx.context.trustedOrigins ?? [],
|
|
4385
|
+
...extraOrigins
|
|
4386
|
+
]);
|
|
4387
|
+
}
|
|
4388
|
+
/**
|
|
4389
|
+
* Resolve a redirect against the auth server's base URL and trusted origins.
|
|
4390
|
+
* Falls back when the target origin is not allowed.
|
|
4391
|
+
*/
|
|
4392
|
+
function resolveTrustedAuthRedirectUrl(ctx, raw, fallback) {
|
|
4393
|
+
return safeUrlParse({
|
|
4394
|
+
raw,
|
|
4395
|
+
baseURL: getAuthBaseUrl(ctx),
|
|
4396
|
+
allowedOrigins: getAllowedAuthOrigins(ctx)
|
|
4397
|
+
}) ?? fallback;
|
|
4398
|
+
}
|
|
4399
|
+
/** Returns a trusted redirect URL or null when the origin is not allowed. */
|
|
4400
|
+
function parseTrustedAuthRedirectUrl(ctx, raw) {
|
|
4401
|
+
return safeUrlParse({
|
|
4402
|
+
raw,
|
|
4403
|
+
baseURL: getAuthBaseUrl(ctx),
|
|
4404
|
+
allowedOrigins: getAllowedAuthOrigins(ctx)
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
const TRUSTED_CALLBACK_URL_ERROR = "Callback URL must use http(s) and match the auth base URL or a trusted origin.";
|
|
4408
|
+
/** Rejects email/redirect callbacks whose origin is not on the auth allowlist. */
|
|
4409
|
+
function requireTrustedAuthCallbackUrl(ctx, raw) {
|
|
4410
|
+
const trusted = parseTrustedAuthRedirectUrl(ctx, raw);
|
|
4411
|
+
if (!trusted) throw new APIError$1("BAD_REQUEST", { message: TRUSTED_CALLBACK_URL_ERROR });
|
|
4412
|
+
return trusted;
|
|
4413
|
+
}
|
|
4414
|
+
//#endregion
|
|
4245
4415
|
//#region ../utils/dist/semver.mjs
|
|
4246
4416
|
/**
|
|
4247
4417
|
* Parses a semver string into components.
|
|
@@ -4312,6 +4482,7 @@ const getConfig = (options) => {
|
|
|
4312
4482
|
}, async (ctx) => {
|
|
4313
4483
|
const advancedOptions = ctx.context.options.advanced;
|
|
4314
4484
|
const organizationPlugin = ctx.context.getPlugin("organization");
|
|
4485
|
+
const authBase = resolveProjectAuthBase(getAuthBaseUrl(ctx), ctx.context.options.basePath);
|
|
4315
4486
|
return redactDashSettings({
|
|
4316
4487
|
version: ctx.context.version || null,
|
|
4317
4488
|
socialProviders: Object.keys(ctx.context.options.socialProviders || {}),
|
|
@@ -4381,8 +4552,8 @@ const getConfig = (options) => {
|
|
|
4381
4552
|
deleteUserEnabled: !!ctx.context.options.user?.deleteUser?.enabled,
|
|
4382
4553
|
modelName: ctx.context.options.user?.modelName
|
|
4383
4554
|
},
|
|
4384
|
-
baseURL:
|
|
4385
|
-
basePath:
|
|
4555
|
+
baseURL: authBase.baseURL,
|
|
4556
|
+
basePath: authBase.basePath,
|
|
4386
4557
|
emailVerification: { sendVerificationEmailEnabled: !!ctx.context.options.emailVerification?.sendVerificationEmail },
|
|
4387
4558
|
insights: {
|
|
4388
4559
|
hasDatabase: !!ctx.context.options.database,
|
|
@@ -4576,7 +4747,7 @@ const directorySyncClaimsSchema = z.object({
|
|
|
4576
4747
|
setupOperationId: z.string().trim().min(16).max(255).optional()
|
|
4577
4748
|
});
|
|
4578
4749
|
function getScimEndpoint$1(baseUrl) {
|
|
4579
|
-
return
|
|
4750
|
+
return normalizeUrl(baseUrl, "scim/v2");
|
|
4580
4751
|
}
|
|
4581
4752
|
function assertDirectorySyncClaims(ctx, organizationId) {
|
|
4582
4753
|
const claims = ctx.context.payload;
|
|
@@ -5478,7 +5649,7 @@ async function scimProviderExists(ctx, organizationId, providerId) {
|
|
|
5478
5649
|
}) != null;
|
|
5479
5650
|
}
|
|
5480
5651
|
function getScimEndpoint(baseUrl) {
|
|
5481
|
-
return
|
|
5652
|
+
return normalizeUrl(baseUrl, "scim/v2");
|
|
5482
5653
|
}
|
|
5483
5654
|
function resolveMode(ctx) {
|
|
5484
5655
|
return resolveDirectorySyncMode(getSCIMPlugin(ctx), isManagedDirectorySyncEnabled(ctx));
|
|
@@ -6037,105 +6208,6 @@ function allowsPasswordlessInviteComplete(authMode) {
|
|
|
6037
6208
|
return authMode === "create_with_session";
|
|
6038
6209
|
}
|
|
6039
6210
|
//#endregion
|
|
6040
|
-
//#region ../utils/dist/url.mjs
|
|
6041
|
-
/**
|
|
6042
|
-
* URL helpers: normalization, SSRF-safe parsing/validation, and path/query segments.
|
|
6043
|
-
*/
|
|
6044
|
-
const HTTP_PROTOCOLS = ["http:", "https:"];
|
|
6045
|
-
function isAllowedProtocol(protocol, allowedProtocols) {
|
|
6046
|
-
return (allowedProtocols?.length ? allowedProtocols : HTTP_PROTOCOLS).includes(protocol);
|
|
6047
|
-
}
|
|
6048
|
-
function toAllowedOriginSet(allowedOrigins) {
|
|
6049
|
-
const set = /* @__PURE__ */ new Set();
|
|
6050
|
-
for (const raw of allowedOrigins) try {
|
|
6051
|
-
const parsed = new URL(raw.trim());
|
|
6052
|
-
if (isAllowedProtocol(parsed.protocol, HTTP_PROTOCOLS)) set.add(parsed.origin);
|
|
6053
|
-
} catch {}
|
|
6054
|
-
return set;
|
|
6055
|
-
}
|
|
6056
|
-
function isAllowedOrigin(origin, allowedOrigins) {
|
|
6057
|
-
if (!allowedOrigins.length) return false;
|
|
6058
|
-
return toAllowedOriginSet(allowedOrigins).has(origin);
|
|
6059
|
-
}
|
|
6060
|
-
function safeResolveUrl(raw, baseURL) {
|
|
6061
|
-
const trim = raw.trim();
|
|
6062
|
-
if (!trim) return null;
|
|
6063
|
-
if (trim.startsWith("//") || trim.includes("\\")) return null;
|
|
6064
|
-
if (/[\u0000-\u001F\u007F]/.test(trim)) return null;
|
|
6065
|
-
try {
|
|
6066
|
-
if (trim.startsWith("/")) return new URL(trim, baseURL);
|
|
6067
|
-
return new URL(trim);
|
|
6068
|
-
} catch {
|
|
6069
|
-
return null;
|
|
6070
|
-
}
|
|
6071
|
-
}
|
|
6072
|
-
function getSafeUniqueOrigins(urls) {
|
|
6073
|
-
return [...toAllowedOriginSet(urls)];
|
|
6074
|
-
}
|
|
6075
|
-
/**
|
|
6076
|
-
* Returns an absolute URL only if its protocol and origin are allowed (after
|
|
6077
|
-
* resolving relative paths against `baseURL`). Otherwise returns `null`.
|
|
6078
|
-
*/
|
|
6079
|
-
function safeUrlParse(input) {
|
|
6080
|
-
if (input.raw == null || input.raw.trim() === "") return null;
|
|
6081
|
-
const target = safeResolveUrl(input.raw, input.baseURL);
|
|
6082
|
-
if (!target || !isAllowedProtocol(target.protocol, input.allowedProtocols)) return null;
|
|
6083
|
-
if (!isAllowedOrigin(target.origin, input.allowedOrigins ?? [])) return null;
|
|
6084
|
-
return target.href;
|
|
6085
|
-
}
|
|
6086
|
-
//#endregion
|
|
6087
|
-
//#region src/lib/safe-url.ts
|
|
6088
|
-
function isSafeHttpUrl(value) {
|
|
6089
|
-
try {
|
|
6090
|
-
const url = new URL(value);
|
|
6091
|
-
return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "";
|
|
6092
|
-
} catch {
|
|
6093
|
-
return false;
|
|
6094
|
-
}
|
|
6095
|
-
}
|
|
6096
|
-
/** http(s) absolute URL without embedded credentials. */
|
|
6097
|
-
const safeUrlSchema = z.string().trim().min(1).pipe(z.url().refine(isSafeHttpUrl, { message: "URL must be a valid http(s) URL without credentials" })).transform((value) => new URL(value).href);
|
|
6098
|
-
const optionalSafeUrlSchema = z.union([safeUrlSchema, z.literal("")]).optional();
|
|
6099
|
-
function getAuthBaseUrl(ctx) {
|
|
6100
|
-
const baseURL = ctx.context.options.baseURL;
|
|
6101
|
-
return typeof baseURL === "string" && baseURL.trim() ? baseURL : "/";
|
|
6102
|
-
}
|
|
6103
|
-
function getAllowedAuthOrigins(ctx, extraOrigins = []) {
|
|
6104
|
-
const baseURL = getAuthBaseUrl(ctx);
|
|
6105
|
-
const trusted = ctx.context.options.trustedOrigins;
|
|
6106
|
-
return getSafeUniqueOrigins([
|
|
6107
|
-
baseURL,
|
|
6108
|
-
...Array.isArray(trusted) ? trusted.filter((origin) => typeof origin === "string") : [],
|
|
6109
|
-
...extraOrigins
|
|
6110
|
-
]);
|
|
6111
|
-
}
|
|
6112
|
-
/**
|
|
6113
|
-
* Resolve a redirect against the auth server's base URL and trusted origins.
|
|
6114
|
-
* Falls back when the target origin is not allowed.
|
|
6115
|
-
*/
|
|
6116
|
-
function resolveTrustedAuthRedirectUrl(ctx, raw, fallback) {
|
|
6117
|
-
return safeUrlParse({
|
|
6118
|
-
raw,
|
|
6119
|
-
baseURL: getAuthBaseUrl(ctx),
|
|
6120
|
-
allowedOrigins: getAllowedAuthOrigins(ctx)
|
|
6121
|
-
}) ?? fallback;
|
|
6122
|
-
}
|
|
6123
|
-
/** Returns a trusted redirect URL or null when the origin is not allowed. */
|
|
6124
|
-
function parseTrustedAuthRedirectUrl(ctx, raw) {
|
|
6125
|
-
return safeUrlParse({
|
|
6126
|
-
raw,
|
|
6127
|
-
baseURL: getAuthBaseUrl(ctx),
|
|
6128
|
-
allowedOrigins: getAllowedAuthOrigins(ctx)
|
|
6129
|
-
});
|
|
6130
|
-
}
|
|
6131
|
-
const TRUSTED_CALLBACK_URL_ERROR = "Callback URL must use http(s) and match the auth base URL or a trusted origin.";
|
|
6132
|
-
/** Rejects email/redirect callbacks whose origin is not on the auth allowlist. */
|
|
6133
|
-
function requireTrustedAuthCallbackUrl(ctx, raw) {
|
|
6134
|
-
const trusted = parseTrustedAuthRedirectUrl(ctx, raw);
|
|
6135
|
-
if (!trusted) throw new APIError$1("BAD_REQUEST", { message: TRUSTED_CALLBACK_URL_ERROR });
|
|
6136
|
-
return trusted;
|
|
6137
|
-
}
|
|
6138
|
-
//#endregion
|
|
6139
6211
|
//#region src/routes/invitations/index.ts
|
|
6140
6212
|
async function verifyPendingInvitation(token, $api, ctx) {
|
|
6141
6213
|
const { data: invitation, error } = await $api("/api/internal/invitations/verify", {
|
|
@@ -6181,7 +6253,7 @@ async function executePlatformInvitationCompletion(ctx, $api, invitation, args)
|
|
|
6181
6253
|
user: existingUser
|
|
6182
6254
|
});
|
|
6183
6255
|
}
|
|
6184
|
-
return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl,
|
|
6256
|
+
return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx)) };
|
|
6185
6257
|
}
|
|
6186
6258
|
if (!password && !allowsPasswordlessInviteComplete(invitation.authMode)) throw new APIError("BAD_REQUEST", { message: "Password is required to complete this invitation." });
|
|
6187
6259
|
const userPayload = {
|
|
@@ -6212,7 +6284,7 @@ async function executePlatformInvitationCompletion(ctx, $api, invitation, args)
|
|
|
6212
6284
|
user
|
|
6213
6285
|
});
|
|
6214
6286
|
}
|
|
6215
|
-
return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl,
|
|
6287
|
+
return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx)) };
|
|
6216
6288
|
}
|
|
6217
6289
|
/**
|
|
6218
6290
|
* Accept invitation endpoint
|
|
@@ -6245,14 +6317,14 @@ const acceptInvitation = (options) => {
|
|
|
6245
6317
|
user: existingUser
|
|
6246
6318
|
});
|
|
6247
6319
|
}
|
|
6248
|
-
const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl,
|
|
6320
|
+
const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx));
|
|
6249
6321
|
return ctx.redirect(redirectUrl);
|
|
6250
6322
|
}
|
|
6251
6323
|
if (usesPlatformInviteAcceptFlow(invitation.authMode)) {
|
|
6252
6324
|
const platformUrl = options.apiUrl || INFRA_API_URL;
|
|
6253
6325
|
const acceptPageUrl = new URL("/invite/accept", platformUrl);
|
|
6254
6326
|
acceptPageUrl.searchParams.set("token", token);
|
|
6255
|
-
const callbackUrl =
|
|
6327
|
+
const callbackUrl = normalizeUrl(ctx.context.baseURL, "dash/complete-invitation");
|
|
6256
6328
|
acceptPageUrl.searchParams.set("callback", callbackUrl);
|
|
6257
6329
|
return ctx.redirect(acceptPageUrl.toString());
|
|
6258
6330
|
}
|
|
@@ -6280,7 +6352,7 @@ const acceptInvitation = (options) => {
|
|
|
6280
6352
|
user
|
|
6281
6353
|
});
|
|
6282
6354
|
}
|
|
6283
|
-
const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl,
|
|
6355
|
+
const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx));
|
|
6284
6356
|
return ctx.redirect(redirectUrl);
|
|
6285
6357
|
});
|
|
6286
6358
|
};
|
|
@@ -6360,7 +6432,7 @@ const completeInvitationSocial = (options) => {
|
|
|
6360
6432
|
if (sessionCookieToken) await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
|
|
6361
6433
|
deleteSessionCookie(ctx);
|
|
6362
6434
|
}
|
|
6363
|
-
return ctx.redirect(resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl,
|
|
6435
|
+
return ctx.redirect(resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx)));
|
|
6364
6436
|
});
|
|
6365
6437
|
};
|
|
6366
6438
|
/**
|
|
@@ -8414,7 +8486,7 @@ async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
|
|
|
8414
8486
|
const ba17OrLater = isBetterAuth17OrLater(ctx.context.version);
|
|
8415
8487
|
if (saml17OrNewer && !resolvedEntryPoint) throw ctx.error("BAD_REQUEST", { message: "SAML entry point URL is required; provide entryPoint or IdP metadata with SingleSignOnService Location" });
|
|
8416
8488
|
if (ba17OrLater && !idpMetadataXml && !samlConfig.entityId?.trim()) throw ctx.error("BAD_REQUEST", { message: "IdP entity ID is required when IdP metadata XML is not provided" });
|
|
8417
|
-
const spIssuer = `${baseURL
|
|
8489
|
+
const spIssuer = `${normalizeUrl(baseURL, "sso/saml2/sp/metadata")}?providerId=${providerId}`;
|
|
8418
8490
|
const idpMetadata = idpMetadataXml ? { metadata: idpMetadataXml } : {
|
|
8419
8491
|
...ba17OrLater ? { entityID: samlConfig.entityId } : {},
|
|
8420
8492
|
...samlConfig.entryPoint ? { singleSignOnService: [{
|
|
@@ -8432,7 +8504,7 @@ async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
|
|
|
8432
8504
|
entryPoint: resolvedEntryPoint,
|
|
8433
8505
|
...samlConfig.cert ? { cert: samlConfig.cert } : {}
|
|
8434
8506
|
} : {
|
|
8435
|
-
callbackUrl:
|
|
8507
|
+
callbackUrl: normalizeUrl(baseURL, `sso/saml2/sp/acs/${providerId}`),
|
|
8436
8508
|
spMetadata: {},
|
|
8437
8509
|
entryPoint: resolvedEntryPoint ?? "",
|
|
8438
8510
|
cert: samlConfig.cert ?? ""
|
|
@@ -10525,11 +10597,12 @@ function createSMSSender(config) {
|
|
|
10525
10597
|
if (!apiKey) logger.warn("[Dash] No API key provided for SMS sending. Set BETTER_AUTH_API_KEY environment variable or pass apiKey in config.");
|
|
10526
10598
|
const $api = createFetch({
|
|
10527
10599
|
baseURL: apiUrl,
|
|
10600
|
+
timeout: config?.apiOptions?.timeout ?? config?.apiTimeout ?? 3e3,
|
|
10528
10601
|
headers: {
|
|
10602
|
+
...config?.apiOptions?.headers,
|
|
10529
10603
|
"user-agent": INFRA_USER_AGENT,
|
|
10530
10604
|
Authorization: `Bearer ${apiKey}`
|
|
10531
|
-
}
|
|
10532
|
-
timeout: config?.apiOptions?.timeout ?? config?.apiTimeout ?? 3e3
|
|
10605
|
+
}
|
|
10533
10606
|
});
|
|
10534
10607
|
/**
|
|
10535
10608
|
* Send an SMS with OTP code
|
|
@@ -10622,7 +10695,7 @@ function setPendingSessionTracking(ctx, pending) {
|
|
|
10622
10695
|
}
|
|
10623
10696
|
async function getRequestLocation() {
|
|
10624
10697
|
try {
|
|
10625
|
-
return (await getCurrentAuthContext()).context.location;
|
|
10698
|
+
return asInfraContext(await getCurrentAuthContext()).context.location;
|
|
10626
10699
|
} catch {
|
|
10627
10700
|
return;
|
|
10628
10701
|
}
|
|
@@ -10638,7 +10711,8 @@ const dash = (options) => {
|
|
|
10638
10711
|
const $kv = createKV({
|
|
10639
10712
|
kvUrl: opts.kvUrl,
|
|
10640
10713
|
apiKey: opts.apiKey,
|
|
10641
|
-
timeout: opts.kvOptions.timeout
|
|
10714
|
+
timeout: opts.kvOptions.timeout,
|
|
10715
|
+
headers: opts.kvOptions.headers
|
|
10642
10716
|
});
|
|
10643
10717
|
const activityUpdateInterval = opts.activityTracking?.updateInterval ?? 3e5;
|
|
10644
10718
|
const scheduleLastActiveUpdate = async (ctx, userId) => {
|
|
@@ -10970,7 +11044,8 @@ const dash = (options) => {
|
|
|
10970
11044
|
const location = ctx.context.location;
|
|
10971
11045
|
if (matchesAnyRoute(path, [routes.REQUEST_PASSWORD_RESET])) trackPasswordResetRequest(verification, trigger, ctx, location);
|
|
10972
11046
|
} },
|
|
10973
|
-
delete: { async after(verification,
|
|
11047
|
+
delete: { async after(verification, _ctx) {
|
|
11048
|
+
const ctx = _ctx;
|
|
10974
11049
|
if (!ctx) return;
|
|
10975
11050
|
const path = ctx.path;
|
|
10976
11051
|
const maybeUserId = ctx.context.session?.user.id ?? "unknown";
|
|
@@ -10994,7 +11069,7 @@ const dash = (options) => {
|
|
|
10994
11069
|
after: [
|
|
10995
11070
|
{
|
|
10996
11071
|
matcher: (ctx) => !!getPendingSessionTracking(ctx),
|
|
10997
|
-
handler: createAuthMiddleware(async (ctx) => {
|
|
11072
|
+
handler: createAuthMiddleware$1(async (ctx) => {
|
|
10998
11073
|
const pending = getPendingSessionTracking(ctx);
|
|
10999
11074
|
if (!pending) return;
|
|
11000
11075
|
trackSessionLifecycle(pending.enrichedSession, pending.userId, ctx, pending.location, resolveUserFromContext(pending.userId, ctx), pending.trackSignIn, pending.impersonatedBy);
|
|
@@ -11009,8 +11084,7 @@ const dash = (options) => {
|
|
|
11009
11084
|
routes.DASH_IMPERSONATE_USER
|
|
11010
11085
|
]);
|
|
11011
11086
|
},
|
|
11012
|
-
handler: createAuthMiddleware(async (
|
|
11013
|
-
const ctx = _ctx;
|
|
11087
|
+
handler: createAuthMiddleware$1(async (ctx) => {
|
|
11014
11088
|
const maybeUserId = ctx.context.session?.user.id ?? "unknown";
|
|
11015
11089
|
const trigger = getTriggerInfo(ctx, maybeUserId);
|
|
11016
11090
|
if (matchesAnyRoute(ctx.path, [routes.SEND_VERIFICATION_EMAIL, routes.DASH_SEND_VERIFICATION_EMAIL]) && ctx.context.session && !(ctx.context.returned instanceof Error)) trackEmailVerificationSent(ctx.context.session.session, ctx.context.session.user, trigger, ctx.context.location);
|
|
@@ -11025,7 +11099,7 @@ const dash = (options) => {
|
|
|
11025
11099
|
handler: createIdentificationCookieAfterMiddleware()
|
|
11026
11100
|
},
|
|
11027
11101
|
{
|
|
11028
|
-
handler: createAuthMiddleware(async (ctx) => {
|
|
11102
|
+
handler: createAuthMiddleware$1(async (ctx) => {
|
|
11029
11103
|
if (!opts.activityTracking?.enabled) return;
|
|
11030
11104
|
if (activityUpdateInterval === 0) return;
|
|
11031
11105
|
const session = ctx.context.session || ctx.context.newSession;
|
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-DgKA4Eq8.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-DDadesq-.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-DRUkPcBn.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-DDadesq-.mjs";
|
|
2
2
|
//#region src/dash-client.ts
|
|
3
3
|
function resolveDashUserId(input, options) {
|
|
4
4
|
return input.userId || options?.resolveUserId?.({
|