@better-auth/infra 0.4.8 → 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 CHANGED
@@ -5,6 +5,12 @@ 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/).
7
7
 
8
+ ## [0.4.9] - 2026-09-09
9
+
10
+ ### Fixed
11
+
12
+ - **Auth base URL resolution** — Correctly handle Better Auth `baseURL` with an embedded path and dynamic `allowedHosts` (1.5+): dash config, invitation callbacks, SSO/SCIM endpoints, and Sentinel no longer use the wrong mount path or break on object `baseURL`.
13
+
8
14
  ## [0.4.8] - 2026-09-09
9
15
 
10
16
  ### Added
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { n as hash, o as createKV } from "./crypto-DRS7yhmR.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-Di0eUKbA.mjs";
1
+ import { n as hash, o as createKV } 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, s as generateRequestId, t as createPowRetryTimeout } from "./pow-retry-DRUkPcBn.mjs";
3
3
  import { env } from "@better-auth/core/env";
4
4
  //#region src/sentinel/fingerprint.ts
5
5
  function murmurhash3(str, seed = 0) {
@@ -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.8";
4
+ const PLUGIN_VERSION = "0.4.9";
5
5
  //#endregion
6
6
  //#region src/constants.ts
7
7
  /**
@@ -1,4 +1,4 @@
1
- import { i as INFRA_USER_AGENT } from "./constants-C8_UJzzu.mjs";
1
+ import { i as INFRA_USER_AGENT } from "./constants-CdZodAXp.mjs";
2
2
  import { createFetch } from "@better-fetch/fetch";
3
3
  import { sha256 } from "@noble/hashes/sha2.js";
4
4
  //#region src/fetch.ts
package/dist/email.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as INFRA_USER_AGENT, n as INFRA_API_URL } from "./constants-C8_UJzzu.mjs";
1
+ import { i as INFRA_USER_AGENT, n as INFRA_API_URL } from "./constants-CdZodAXp.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/index.d.mts CHANGED
@@ -205,7 +205,9 @@ interface DashConfigResponse {
205
205
  deleteUserEnabled: boolean;
206
206
  modelName?: string;
207
207
  };
208
- baseURL: unknown;
208
+ /** Origin only (never the dynamic allowedHosts object). */
209
+ baseURL: string;
210
+ /** Mount path; reflects path embedded in baseURL when present. */
209
211
  basePath: string;
210
212
  emailVerification: {
211
213
  sendVerificationEmailEnabled: boolean;
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-C8_UJzzu.mjs";
2
- import { a as createAPI, n as hash$1, o as createKV, r as hmacSha256Hex } from "./crypto-DRS7yhmR.mjs";
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";
@@ -96,6 +96,91 @@ function resolveClientIpFromHeaders(headers, ipAddressHeaders) {
96
96
  }
97
97
  }
98
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
99
184
  //#region src/events/constants.ts
100
185
  const EVENT_TYPES = {
101
186
  USER_CREATED: "user_created",
@@ -2547,7 +2632,7 @@ const sentinel = (options) => {
2547
2632
  id: "sentinel",
2548
2633
  init(ctx) {
2549
2634
  activityTrackingEnabled = (ctx.getPlugin("dash")?.options)?.activityTracking?.enabled === true;
2550
- const basePath = new URL(ctx.baseURL).pathname;
2635
+ const basePath = ctx.baseURL ? resolveProjectAuthBase(ctx.baseURL, ctx.options.basePath).basePath : resolveProjectBasePath(ctx.options.basePath);
2551
2636
  const ipHeaders = ctx.options.advanced?.ipAddress?.ipAddressHeaders || null;
2552
2637
  const disableIpTracking = ctx.options.advanced?.ipAddress?.disableIpTracking === true;
2553
2638
  /**
@@ -4267,6 +4352,66 @@ const jwtValidateMiddleware = (options) => {
4267
4352
  });
4268
4353
  };
4269
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
4270
4415
  //#region ../utils/dist/semver.mjs
4271
4416
  /**
4272
4417
  * Parses a semver string into components.
@@ -4337,6 +4482,7 @@ const getConfig = (options) => {
4337
4482
  }, async (ctx) => {
4338
4483
  const advancedOptions = ctx.context.options.advanced;
4339
4484
  const organizationPlugin = ctx.context.getPlugin("organization");
4485
+ const authBase = resolveProjectAuthBase(getAuthBaseUrl(ctx), ctx.context.options.basePath);
4340
4486
  return redactDashSettings({
4341
4487
  version: ctx.context.version || null,
4342
4488
  socialProviders: Object.keys(ctx.context.options.socialProviders || {}),
@@ -4406,8 +4552,8 @@ const getConfig = (options) => {
4406
4552
  deleteUserEnabled: !!ctx.context.options.user?.deleteUser?.enabled,
4407
4553
  modelName: ctx.context.options.user?.modelName
4408
4554
  },
4409
- baseURL: ctx.context.options.baseURL,
4410
- basePath: ctx.context.options.basePath || "/api/auth",
4555
+ baseURL: authBase.baseURL,
4556
+ basePath: authBase.basePath,
4411
4557
  emailVerification: { sendVerificationEmailEnabled: !!ctx.context.options.emailVerification?.sendVerificationEmail },
4412
4558
  insights: {
4413
4559
  hasDatabase: !!ctx.context.options.database,
@@ -4601,7 +4747,7 @@ const directorySyncClaimsSchema = z.object({
4601
4747
  setupOperationId: z.string().trim().min(16).max(255).optional()
4602
4748
  });
4603
4749
  function getScimEndpoint$1(baseUrl) {
4604
- return `${baseUrl}/scim/v2`;
4750
+ return normalizeUrl(baseUrl, "scim/v2");
4605
4751
  }
4606
4752
  function assertDirectorySyncClaims(ctx, organizationId) {
4607
4753
  const claims = ctx.context.payload;
@@ -5503,7 +5649,7 @@ async function scimProviderExists(ctx, organizationId, providerId) {
5503
5649
  }) != null;
5504
5650
  }
5505
5651
  function getScimEndpoint(baseUrl) {
5506
- return `${baseUrl}/scim/v2`;
5652
+ return normalizeUrl(baseUrl, "scim/v2");
5507
5653
  }
5508
5654
  function resolveMode(ctx) {
5509
5655
  return resolveDirectorySyncMode(getSCIMPlugin(ctx), isManagedDirectorySyncEnabled(ctx));
@@ -6062,105 +6208,6 @@ function allowsPasswordlessInviteComplete(authMode) {
6062
6208
  return authMode === "create_with_session";
6063
6209
  }
6064
6210
  //#endregion
6065
- //#region ../utils/dist/url.mjs
6066
- /**
6067
- * URL helpers: normalization, SSRF-safe parsing/validation, and path/query segments.
6068
- */
6069
- const HTTP_PROTOCOLS = ["http:", "https:"];
6070
- function isAllowedProtocol(protocol, allowedProtocols) {
6071
- return (allowedProtocols?.length ? allowedProtocols : HTTP_PROTOCOLS).includes(protocol);
6072
- }
6073
- function toAllowedOriginSet(allowedOrigins) {
6074
- const set = /* @__PURE__ */ new Set();
6075
- for (const raw of allowedOrigins) try {
6076
- const parsed = new URL(raw.trim());
6077
- if (isAllowedProtocol(parsed.protocol, HTTP_PROTOCOLS)) set.add(parsed.origin);
6078
- } catch {}
6079
- return set;
6080
- }
6081
- function isAllowedOrigin(origin, allowedOrigins) {
6082
- if (!allowedOrigins.length) return false;
6083
- return toAllowedOriginSet(allowedOrigins).has(origin);
6084
- }
6085
- function safeResolveUrl(raw, baseURL) {
6086
- const trim = raw.trim();
6087
- if (!trim) return null;
6088
- if (trim.startsWith("//") || trim.includes("\\")) return null;
6089
- if (/[\u0000-\u001F\u007F]/.test(trim)) return null;
6090
- try {
6091
- if (trim.startsWith("/")) return new URL(trim, baseURL);
6092
- return new URL(trim);
6093
- } catch {
6094
- return null;
6095
- }
6096
- }
6097
- function getSafeUniqueOrigins(urls) {
6098
- return [...toAllowedOriginSet(urls)];
6099
- }
6100
- /**
6101
- * Returns an absolute URL only if its protocol and origin are allowed (after
6102
- * resolving relative paths against `baseURL`). Otherwise returns `null`.
6103
- */
6104
- function safeUrlParse(input) {
6105
- if (input.raw == null || input.raw.trim() === "") return null;
6106
- const target = safeResolveUrl(input.raw, input.baseURL);
6107
- if (!target || !isAllowedProtocol(target.protocol, input.allowedProtocols)) return null;
6108
- if (!isAllowedOrigin(target.origin, input.allowedOrigins ?? [])) return null;
6109
- return target.href;
6110
- }
6111
- //#endregion
6112
- //#region src/lib/safe-url.ts
6113
- function isSafeHttpUrl(value) {
6114
- try {
6115
- const url = new URL(value);
6116
- return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "";
6117
- } catch {
6118
- return false;
6119
- }
6120
- }
6121
- /** http(s) absolute URL without embedded credentials. */
6122
- 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);
6123
- const optionalSafeUrlSchema = z.union([safeUrlSchema, z.literal("")]).optional();
6124
- function getAuthBaseUrl(ctx) {
6125
- const baseURL = ctx.context.options.baseURL;
6126
- return typeof baseURL === "string" && baseURL.trim() ? baseURL : "/";
6127
- }
6128
- function getAllowedAuthOrigins(ctx, extraOrigins = []) {
6129
- const baseURL = getAuthBaseUrl(ctx);
6130
- const trusted = ctx.context.options.trustedOrigins;
6131
- return getSafeUniqueOrigins([
6132
- baseURL,
6133
- ...Array.isArray(trusted) ? trusted.filter((origin) => typeof origin === "string") : [],
6134
- ...extraOrigins
6135
- ]);
6136
- }
6137
- /**
6138
- * Resolve a redirect against the auth server's base URL and trusted origins.
6139
- * Falls back when the target origin is not allowed.
6140
- */
6141
- function resolveTrustedAuthRedirectUrl(ctx, raw, fallback) {
6142
- return safeUrlParse({
6143
- raw,
6144
- baseURL: getAuthBaseUrl(ctx),
6145
- allowedOrigins: getAllowedAuthOrigins(ctx)
6146
- }) ?? fallback;
6147
- }
6148
- /** Returns a trusted redirect URL or null when the origin is not allowed. */
6149
- function parseTrustedAuthRedirectUrl(ctx, raw) {
6150
- return safeUrlParse({
6151
- raw,
6152
- baseURL: getAuthBaseUrl(ctx),
6153
- allowedOrigins: getAllowedAuthOrigins(ctx)
6154
- });
6155
- }
6156
- const TRUSTED_CALLBACK_URL_ERROR = "Callback URL must use http(s) and match the auth base URL or a trusted origin.";
6157
- /** Rejects email/redirect callbacks whose origin is not on the auth allowlist. */
6158
- function requireTrustedAuthCallbackUrl(ctx, raw) {
6159
- const trusted = parseTrustedAuthRedirectUrl(ctx, raw);
6160
- if (!trusted) throw new APIError$1("BAD_REQUEST", { message: TRUSTED_CALLBACK_URL_ERROR });
6161
- return trusted;
6162
- }
6163
- //#endregion
6164
6211
  //#region src/routes/invitations/index.ts
6165
6212
  async function verifyPendingInvitation(token, $api, ctx) {
6166
6213
  const { data: invitation, error } = await $api("/api/internal/invitations/verify", {
@@ -6206,7 +6253,7 @@ async function executePlatformInvitationCompletion(ctx, $api, invitation, args)
6206
6253
  user: existingUser
6207
6254
  });
6208
6255
  }
6209
- return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx)) };
6256
+ return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx)) };
6210
6257
  }
6211
6258
  if (!password && !allowsPasswordlessInviteComplete(invitation.authMode)) throw new APIError("BAD_REQUEST", { message: "Password is required to complete this invitation." });
6212
6259
  const userPayload = {
@@ -6237,7 +6284,7 @@ async function executePlatformInvitationCompletion(ctx, $api, invitation, args)
6237
6284
  user
6238
6285
  });
6239
6286
  }
6240
- return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx)) };
6287
+ return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx)) };
6241
6288
  }
6242
6289
  /**
6243
6290
  * Accept invitation endpoint
@@ -6270,14 +6317,14 @@ const acceptInvitation = (options) => {
6270
6317
  user: existingUser
6271
6318
  });
6272
6319
  }
6273
- const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx));
6320
+ const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx));
6274
6321
  return ctx.redirect(redirectUrl);
6275
6322
  }
6276
6323
  if (usesPlatformInviteAcceptFlow(invitation.authMode)) {
6277
6324
  const platformUrl = options.apiUrl || INFRA_API_URL;
6278
6325
  const acceptPageUrl = new URL("/invite/accept", platformUrl);
6279
6326
  acceptPageUrl.searchParams.set("token", token);
6280
- const callbackUrl = `${ctx.context.options.baseURL}${ctx.context.options.basePath || "/api/auth"}/dash/complete-invitation`;
6327
+ const callbackUrl = normalizeUrl(ctx.context.baseURL, "dash/complete-invitation");
6281
6328
  acceptPageUrl.searchParams.set("callback", callbackUrl);
6282
6329
  return ctx.redirect(acceptPageUrl.toString());
6283
6330
  }
@@ -6305,7 +6352,7 @@ const acceptInvitation = (options) => {
6305
6352
  user
6306
6353
  });
6307
6354
  }
6308
- const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx));
6355
+ const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx));
6309
6356
  return ctx.redirect(redirectUrl);
6310
6357
  });
6311
6358
  };
@@ -6385,7 +6432,7 @@ const completeInvitationSocial = (options) => {
6385
6432
  if (sessionCookieToken) await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
6386
6433
  deleteSessionCookie(ctx);
6387
6434
  }
6388
- return ctx.redirect(resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx)));
6435
+ return ctx.redirect(resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthOrigin(ctx)));
6389
6436
  });
6390
6437
  };
6391
6438
  /**
@@ -8439,7 +8486,7 @@ async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
8439
8486
  const ba17OrLater = isBetterAuth17OrLater(ctx.context.version);
8440
8487
  if (saml17OrNewer && !resolvedEntryPoint) throw ctx.error("BAD_REQUEST", { message: "SAML entry point URL is required; provide entryPoint or IdP metadata with SingleSignOnService Location" });
8441
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" });
8442
- const spIssuer = `${baseURL}/sso/saml2/sp/metadata?providerId=${providerId}`;
8489
+ const spIssuer = `${normalizeUrl(baseURL, "sso/saml2/sp/metadata")}?providerId=${providerId}`;
8443
8490
  const idpMetadata = idpMetadataXml ? { metadata: idpMetadataXml } : {
8444
8491
  ...ba17OrLater ? { entityID: samlConfig.entityId } : {},
8445
8492
  ...samlConfig.entryPoint ? { singleSignOnService: [{
@@ -8457,7 +8504,7 @@ async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
8457
8504
  entryPoint: resolvedEntryPoint,
8458
8505
  ...samlConfig.cert ? { cert: samlConfig.cert } : {}
8459
8506
  } : {
8460
- callbackUrl: `${baseURL}/sso/saml2/sp/acs/${providerId}`,
8507
+ callbackUrl: normalizeUrl(baseURL, `sso/saml2/sp/acs/${providerId}`),
8461
8508
  spMetadata: {},
8462
8509
  entryPoint: resolvedEntryPoint ?? "",
8463
8510
  cert: samlConfig.cert ?? ""
package/dist/native.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { o as createKV, t as bytesToHex } from "./crypto-DRS7yhmR.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-Di0eUKbA.mjs";
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-DRS7yhmR.mjs";
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?.({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/infra",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Dashboard and analytics plugin for Better Auth",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",