@better-auth/sso 1.5.5 → 1.5.7-beta.1

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/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as DataEncryptionAlgorithm, C as TimestampValidationOptions, D as SSOOptions, E as SAMLConfig, M as DigestAlgorithm, N as KeyEncryptionAlgorithm, O as SSOProvider, P as SignatureAlgorithm, S as SAMLConditions, T as OIDCConfig, _ as REQUIRED_DISCOVERY_FIELDS, a as fetchDiscoveryDocument, b as DEFAULT_MAX_SAML_METADATA_SIZE, c as normalizeUrl, d as validateDiscoveryUrl, f as DiscoverOIDCConfigParams, g as OIDCDiscoveryDocument, h as HydratedOIDCConfig, i as discoverOIDCConfig, j as DeprecatedAlgorithmBehavior, k as AlgorithmValidationOptions, l as selectTokenEndpointAuthMethod, m as DiscoveryErrorCode, n as sso, o as needsRuntimeDiscovery, p as DiscoveryError, r as computeDiscoveryUrl, s as normalizeDiscoveryUrls, t as SSOPlugin, u as validateDiscoveryDocument, v as RequiredDiscoveryField, w as validateSAMLTimestamp, x as DEFAULT_MAX_SAML_RESPONSE_SIZE, y as DEFAULT_CLOCK_SKEW_MS } from "./index-DHITQH_m.mjs";
1
+ import { A as DataEncryptionAlgorithm, C as TimestampValidationOptions, D as SSOOptions, E as SAMLConfig, M as DigestAlgorithm, N as KeyEncryptionAlgorithm, O as SSOProvider, P as SignatureAlgorithm, S as SAMLConditions, T as OIDCConfig, _ as REQUIRED_DISCOVERY_FIELDS, a as fetchDiscoveryDocument, b as DEFAULT_MAX_SAML_METADATA_SIZE, c as normalizeUrl, d as validateDiscoveryUrl, f as DiscoverOIDCConfigParams, g as OIDCDiscoveryDocument, h as HydratedOIDCConfig, i as discoverOIDCConfig, j as DeprecatedAlgorithmBehavior, k as AlgorithmValidationOptions, l as selectTokenEndpointAuthMethod, m as DiscoveryErrorCode, n as sso, o as needsRuntimeDiscovery, p as DiscoveryError, r as computeDiscoveryUrl, s as normalizeDiscoveryUrls, t as SSOPlugin, u as validateDiscoveryDocument, v as RequiredDiscoveryField, w as validateSAMLTimestamp, x as DEFAULT_MAX_SAML_RESPONSE_SIZE, y as DEFAULT_CLOCK_SKEW_MS } from "./index-N-z2Csye.mjs";
2
2
  export { AlgorithmValidationOptions, DEFAULT_CLOCK_SKEW_MS, DEFAULT_MAX_SAML_METADATA_SIZE, DEFAULT_MAX_SAML_RESPONSE_SIZE, DataEncryptionAlgorithm, DeprecatedAlgorithmBehavior, DigestAlgorithm, DiscoverOIDCConfigParams, DiscoveryError, DiscoveryErrorCode, HydratedOIDCConfig, KeyEncryptionAlgorithm, OIDCConfig, OIDCDiscoveryDocument, REQUIRED_DISCOVERY_FIELDS, RequiredDiscoveryField, SAMLConditions, SAMLConfig, SSOOptions, SSOPlugin, SSOProvider, SignatureAlgorithm, TimestampValidationOptions, computeDiscoveryUrl, discoverOIDCConfig, fetchDiscoveryDocument, needsRuntimeDiscovery, normalizeDiscoveryUrls, normalizeUrl, selectTokenEndpointAuthMethod, sso, validateDiscoveryDocument, validateDiscoveryUrl, validateSAMLTimestamp };
package/dist/index.mjs CHANGED
@@ -1,11 +1,10 @@
1
1
  import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
2
2
  import { XMLParser, XMLValidator } from "fast-xml-parser";
3
- import saml from "samlify";
3
+ import * as saml from "samlify";
4
4
  import { X509Certificate } from "node:crypto";
5
5
  import { getHostname } from "tldts";
6
6
  import { generateRandomString } from "better-auth/crypto";
7
- import * as z$1 from "zod/v4";
8
- import z from "zod/v4";
7
+ import * as z from "zod";
9
8
  import { base64 } from "@better-auth/utils/base64";
10
9
  import { BetterFetchError, betterFetch } from "@better-fetch/fetch";
11
10
  import { HIDE_METADATA, createAuthorizationURL, generateGenericState, generateState, parseGenericState, parseState, validateAuthorizationCode, validateToken } from "better-auth";
@@ -13,7 +12,6 @@ import { deleteSessionCookie, setSessionCookie } from "better-auth/cookies";
13
12
  import { handleOAuthUserInfo } from "better-auth/oauth2";
14
13
  import { decodeJwt } from "jose";
15
14
  import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
16
-
17
15
  //#region src/constants.ts
18
16
  /**
19
17
  * SAML Constants
@@ -31,21 +29,11 @@ const SAML_SESSION_BY_ID_PREFIX = "saml-session-by-id:";
31
29
  /** Prefix for LogoutRequest IDs used in SP-initiated SLO validation */
32
30
  const LOGOUT_REQUEST_KEY_PREFIX = "saml-logout-request:";
33
31
  /**
34
- * Default TTL for AuthnRequest records (5 minutes).
35
- * This should be sufficient for most IdPs while protecting against stale requests.
36
- */
37
- const DEFAULT_AUTHN_REQUEST_TTL_MS = 300 * 1e3;
38
- /**
39
32
  * Default TTL for used assertion records (15 minutes).
40
33
  * This should match the maximum expected NotOnOrAfter window plus clock skew.
41
34
  */
42
35
  const DEFAULT_ASSERTION_TTL_MS = 900 * 1e3;
43
36
  /**
44
- * Default TTL for LogoutRequest records (5 minutes).
45
- * Should be sufficient for IdP to process and respond.
46
- */
47
- const DEFAULT_LOGOUT_REQUEST_TTL_MS = 300 * 1e3;
48
- /**
49
37
  * Default clock skew tolerance (5 minutes).
50
38
  * Allows for minor time differences between IdP and SP servers.
51
39
  *
@@ -66,7 +54,6 @@ const DEFAULT_MAX_SAML_RESPONSE_SIZE = 256 * 1024;
66
54
  */
67
55
  const DEFAULT_MAX_SAML_METADATA_SIZE = 100 * 1024;
68
56
  const SAML_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success";
69
-
70
57
  //#endregion
71
58
  //#region src/utils.ts
72
59
  /**
@@ -120,7 +107,6 @@ function maskClientId(clientId) {
120
107
  if (clientId.length <= 4) return "****";
121
108
  return `****${clientId.slice(-4)}`;
122
109
  }
123
-
124
110
  //#endregion
125
111
  //#region src/linking/org-assignment.ts
126
112
  /**
@@ -217,12 +203,11 @@ async function assignOrganizationByDomain(ctx, options) {
217
203
  }
218
204
  });
219
205
  }
220
-
221
206
  //#endregion
222
207
  //#region src/routes/domain-verification.ts
223
208
  const DNS_LABEL_MAX_LENGTH = 63;
224
209
  const DEFAULT_TOKEN_PREFIX = "better-auth-token";
225
- const domainVerificationBodySchema = z$1.object({ providerId: z$1.string() });
210
+ const domainVerificationBodySchema = z.object({ providerId: z.string() });
226
211
  function getVerificationIdentifier(options, providerId) {
227
212
  return `_${options.domainVerification?.tokenPrefix || DEFAULT_TOKEN_PREFIX}-${providerId}`;
228
213
  }
@@ -383,7 +368,6 @@ const verifyDomain = (options) => {
383
368
  ctx.setStatus(204);
384
369
  });
385
370
  };
386
-
387
371
  //#endregion
388
372
  //#region src/saml/parser.ts
389
373
  const xmlParser = new XMLParser({
@@ -418,7 +402,6 @@ function countAllNodes(obj, nodeName) {
418
402
  else if (typeof value === "object" && value !== null) count += countAllNodes(value, nodeName);
419
403
  return count;
420
404
  }
421
-
422
405
  //#endregion
423
406
  //#region src/saml/algorithms.ts
424
407
  const SignatureAlgorithm = {
@@ -599,7 +582,6 @@ function validateConfigAlgorithms(config, options = {}) {
599
582
  });
600
583
  }
601
584
  }
602
-
603
585
  //#endregion
604
586
  //#region src/saml/assertions.ts
605
587
  /** @lintignore used in tests */
@@ -642,7 +624,6 @@ function validateSingleAssertion(samlResponse) {
642
624
  code: "SAML_MULTIPLE_ASSERTIONS"
643
625
  });
644
626
  }
645
-
646
627
  //#endregion
647
628
  //#region src/routes/schemas.ts
648
629
  const oidcMappingSchema = z.object({
@@ -721,7 +702,6 @@ const updateSSOProviderBodySchema = z.object({
721
702
  oidcConfig: oidcConfigSchema.optional(),
722
703
  samlConfig: samlConfigSchema.optional()
723
704
  });
724
-
725
705
  //#endregion
726
706
  //#region src/routes/providers.ts
727
707
  const ADMIN_ROLES = ["owner", "admin"];
@@ -951,7 +931,7 @@ const updateSSOProvider = (options) => {
951
931
  }
952
932
  if (body.samlConfig) {
953
933
  if (body.samlConfig.idpMetadata?.metadata) {
954
- const maxMetadataSize = options?.saml?.maxMetadataSize ?? DEFAULT_MAX_SAML_METADATA_SIZE;
934
+ const maxMetadataSize = options?.saml?.maxMetadataSize ?? 102400;
955
935
  if (new TextEncoder().encode(body.samlConfig.idpMetadata.metadata).length > maxMetadataSize) throw new APIError("BAD_REQUEST", { message: `IdP metadata exceeds maximum allowed size (${maxMetadataSize} bytes)` });
956
936
  }
957
937
  if (body.samlConfig.signatureAlgorithm !== void 0 || body.samlConfig.digestAlgorithm !== void 0) validateConfigAlgorithms({
@@ -1014,7 +994,6 @@ const deleteSSOProvider = () => {
1014
994
  return ctx.json({ success: true });
1015
995
  });
1016
996
  };
1017
-
1018
997
  //#endregion
1019
998
  //#region src/oidc/types.ts
1020
999
  /**
@@ -1041,7 +1020,6 @@ const REQUIRED_DISCOVERY_FIELDS = [
1041
1020
  "token_endpoint",
1042
1021
  "jwks_uri"
1043
1022
  ];
1044
-
1045
1023
  //#endregion
1046
1024
  //#region src/oidc/discovery.ts
1047
1025
  /**
@@ -1308,7 +1286,6 @@ async function ensureRuntimeDiscovery(config, issuer, isTrustedOrigin) {
1308
1286
  jwksEndpoint: hydrated.jwksEndpoint
1309
1287
  };
1310
1288
  }
1311
-
1312
1289
  //#endregion
1313
1290
  //#region src/oidc/errors.ts
1314
1291
  /**
@@ -1379,7 +1356,6 @@ function mapDiscoveryErrorToAPIError(error) {
1379
1356
  });
1380
1357
  }
1381
1358
  }
1382
-
1383
1359
  //#endregion
1384
1360
  //#region src/saml/error-codes.ts
1385
1361
  const SAML_ERROR_CODES = defineErrorCodes({
@@ -1390,7 +1366,6 @@ const SAML_ERROR_CODES = defineErrorCodes({
1390
1366
  IDP_SLO_NOT_SUPPORTED: "IdP does not support Single Logout Service",
1391
1367
  SAML_PROVIDER_NOT_FOUND: "SAML provider not found"
1392
1368
  });
1393
-
1394
1369
  //#endregion
1395
1370
  //#region src/saml-state.ts
1396
1371
  async function generateRelayState(c, link, additionalData) {
@@ -1422,7 +1397,10 @@ async function parseRelayState(c) {
1422
1397
  const errorURL = c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;
1423
1398
  let parsedData;
1424
1399
  try {
1425
- parsedData = await parseGenericState(c, state, { cookieName: "relay_state" });
1400
+ parsedData = await parseGenericState(c, state, {
1401
+ cookieName: "relay_state",
1402
+ skipStateCookieCheck: true
1403
+ });
1426
1404
  } catch (error) {
1427
1405
  c.context.logger.error("Failed to parse relay state", error);
1428
1406
  throw new APIError("BAD_REQUEST", {
@@ -1433,7 +1411,6 @@ async function parseRelayState(c) {
1433
1411
  if (!parsedData.errorURL) parsedData.errorURL = errorURL;
1434
1412
  return parsedData;
1435
1413
  }
1436
-
1437
1414
  //#endregion
1438
1415
  //#region src/routes/helpers.ts
1439
1416
  async function findSAMLProvider(providerId, options, adapter) {
@@ -1513,7 +1490,6 @@ function createSAMLPostForm(action, samlParam, samlValue, relayState) {
1513
1490
  const html = `<!DOCTYPE html><html><body onload="document.forms[0].submit();"><form method="POST" action="${safeAction}"><input type="hidden" name="${safeSamlParam}" value="${safeSamlValue}" />${safeRelayState ? `<input type="hidden" name="RelayState" value="${safeRelayState}" />` : ""}<noscript><input type="submit" value="Continue" /></noscript></form></body></html>`;
1514
1491
  return new Response(html, { headers: { "Content-Type": "text/html" } });
1515
1492
  }
1516
-
1517
1493
  //#endregion
1518
1494
  //#region src/routes/sso.ts
1519
1495
  /**
@@ -1535,7 +1511,7 @@ function getOIDCRedirectURI(baseURL, providerId, options) {
1535
1511
  * @throws {APIError} If timestamps are invalid, expired, or not yet valid
1536
1512
  */
1537
1513
  function validateSAMLTimestamp(conditions, options = {}) {
1538
- const clockSkew = options.clockSkew ?? DEFAULT_CLOCK_SKEW_MS;
1514
+ const clockSkew = options.clockSkew ?? 3e5;
1539
1515
  if (!(conditions?.notBefore || conditions?.notOnOrAfter)) {
1540
1516
  if (options.requireTimestamps) throw new APIError("BAD_REQUEST", {
1541
1517
  message: "SAML assertion missing required timestamp conditions",
@@ -1897,7 +1873,7 @@ const registerSSOProvider = (options) => {
1897
1873
  const body = ctx.body;
1898
1874
  if (z.string().url().safeParse(body.issuer).error) throw new APIError("BAD_REQUEST", { message: "Invalid issuer. Must be a valid URL" });
1899
1875
  if (body.samlConfig?.idpMetadata?.metadata) {
1900
- const maxMetadataSize = options?.saml?.maxMetadataSize ?? DEFAULT_MAX_SAML_METADATA_SIZE;
1876
+ const maxMetadataSize = options?.saml?.maxMetadataSize ?? 102400;
1901
1877
  if (new TextEncoder().encode(body.samlConfig.idpMetadata.metadata).length > maxMetadataSize) throw new APIError("BAD_REQUEST", { message: `IdP metadata exceeds maximum allowed size (${maxMetadataSize} bytes)` });
1902
1878
  }
1903
1879
  if (ctx.body.organizationId) {
@@ -2249,7 +2225,7 @@ const signInSSO = (options) => {
2249
2225
  if (!loginRequest) throw new APIError("BAD_REQUEST", { message: "Invalid SAML request" });
2250
2226
  const { state: relayState } = await generateRelayState(ctx, void 0, false);
2251
2227
  if (loginRequest.id && options?.saml?.enableInResponseToValidation) {
2252
- const ttl = options?.saml?.requestTTL ?? DEFAULT_AUTHN_REQUEST_TTL_MS;
2228
+ const ttl = options?.saml?.requestTTL ?? 3e5;
2253
2229
  const record = {
2254
2230
  id: loginRequest.id,
2255
2231
  providerId: provider.providerId,
@@ -2347,6 +2323,7 @@ async function handleOIDCCallback(ctx, options, providerId, stateData) {
2347
2323
  tokenEndpoint: config.tokenEndpoint,
2348
2324
  authentication: config.tokenEndpointAuthentication === "client_secret_post" ? "post" : "basic"
2349
2325
  }).catch((e) => {
2326
+ ctx.context.logger.error("Error validating authorization code", e);
2350
2327
  if (e instanceof BetterFetchError) throw ctx.redirect(`${errorURL || callbackURL}?error=invalid_provider&error_description=${e.message}`);
2351
2328
  return null;
2352
2329
  });
@@ -2558,7 +2535,7 @@ const callbackSSOSAML = (options) => {
2558
2535
  }
2559
2536
  if (!ctx.body?.SAMLResponse) throw new APIError("BAD_REQUEST", { message: "SAMLResponse is required for POST requests" });
2560
2537
  const { SAMLResponse } = ctx.body;
2561
- const maxResponseSize = options?.saml?.maxResponseSize ?? DEFAULT_MAX_SAML_RESPONSE_SIZE;
2538
+ const maxResponseSize = options?.saml?.maxResponseSize ?? 262144;
2562
2539
  if (new TextEncoder().encode(SAMLResponse).length > maxResponseSize) throw new APIError("BAD_REQUEST", { message: `SAML response exceeds maximum allowed size (${maxResponseSize} bytes)` });
2563
2540
  let relayState = null;
2564
2541
  if (ctx.body.RelayState) try {
@@ -2698,7 +2675,7 @@ const callbackSSOSAML = (options) => {
2698
2675
  if (assertionId) {
2699
2676
  const issuer = idp.entityMeta.getEntityID();
2700
2677
  const conditions = extract.conditions;
2701
- const clockSkew = options?.saml?.clockSkew ?? DEFAULT_CLOCK_SKEW_MS;
2678
+ const clockSkew = options?.saml?.clockSkew ?? 3e5;
2702
2679
  const expiresAt = conditions?.notOnOrAfter ? new Date(conditions.notOnOrAfter).getTime() + clockSkew : Date.now() + DEFAULT_ASSERTION_TTL_MS;
2703
2680
  const existingAssertion = await ctx.context.internalAdapter.findVerificationValue(`${USED_ASSERTION_KEY_PREFIX}${assertionId}`);
2704
2681
  let isReplay = false;
@@ -2838,7 +2815,7 @@ const acsEndpoint = (options) => {
2838
2815
  const { providerId } = ctx.params;
2839
2816
  const currentCallbackPath = `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`;
2840
2817
  const appOrigin = new URL(ctx.context.baseURL).origin;
2841
- const maxResponseSize = options?.saml?.maxResponseSize ?? DEFAULT_MAX_SAML_RESPONSE_SIZE;
2818
+ const maxResponseSize = options?.saml?.maxResponseSize ?? 262144;
2842
2819
  if (new TextEncoder().encode(SAMLResponse).length > maxResponseSize) throw new APIError("BAD_REQUEST", { message: `SAML response exceeds maximum allowed size (${maxResponseSize} bytes)` });
2843
2820
  let relayState = null;
2844
2821
  if (ctx.body.RelayState) try {
@@ -2969,7 +2946,7 @@ const acsEndpoint = (options) => {
2969
2946
  if (assertionIdAcs) {
2970
2947
  const issuer = idp.entityMeta.getEntityID();
2971
2948
  const conditions = extract.conditions;
2972
- const clockSkew = options?.saml?.clockSkew ?? DEFAULT_CLOCK_SKEW_MS;
2949
+ const clockSkew = options?.saml?.clockSkew ?? 3e5;
2973
2950
  const expiresAt = conditions?.notOnOrAfter ? new Date(conditions.notOnOrAfter).getTime() + clockSkew : Date.now() + DEFAULT_ASSERTION_TTL_MS;
2974
2951
  const existingAssertion = await ctx.context.internalAdapter.findVerificationValue(`${USED_ASSERTION_KEY_PREFIX}${assertionIdAcs}`);
2975
2952
  let isReplay = false;
@@ -3137,7 +3114,7 @@ async function handleLogoutResponse(ctx, sp, idp, relayState, providerId) {
3137
3114
  }
3138
3115
  const extract = parsed?.extract;
3139
3116
  const statusCode = extract?.statusCode || extract?.status || parsed?.samlContent?.status?.statusCode;
3140
- if (statusCode && statusCode !== SAML_STATUS_SUCCESS) {
3117
+ if (statusCode && statusCode !== "urn:oasis:names:tc:SAML:2.0:status:Success") {
3141
3118
  ctx.context.logger.warn("LogoutResponse indicates failure", { statusCode });
3142
3119
  throw APIError.from("BAD_REQUEST", SAML_ERROR_CODES.LOGOUT_FAILED_AT_IDP);
3143
3120
  }
@@ -3230,7 +3207,7 @@ const initiateSLO = (options) => {
3230
3207
  sessionIndex,
3231
3208
  relayState: callbackURL
3232
3209
  });
3233
- const ttl = options?.saml?.logoutRequestTTL ?? DEFAULT_LOGOUT_REQUEST_TTL_MS;
3210
+ const ttl = options?.saml?.logoutRequestTTL ?? 3e5;
3234
3211
  await ctx.context.internalAdapter.createVerificationValue({
3235
3212
  identifier: `${LOGOUT_REQUEST_KEY_PREFIX}${logoutRequest.id}`,
3236
3213
  value: providerId,
@@ -3243,7 +3220,6 @@ const initiateSLO = (options) => {
3243
3220
  throw ctx.redirect(logoutRequest.context);
3244
3221
  });
3245
3222
  };
3246
-
3247
3223
  //#endregion
3248
3224
  //#region src/index.ts
3249
3225
  saml.setSchemaValidator({ async validate(xml) {
@@ -3379,7 +3355,7 @@ function sso(options) {
3379
3355
  options
3380
3356
  };
3381
3357
  }
3382
-
3383
3358
  //#endregion
3384
3359
  export { DEFAULT_CLOCK_SKEW_MS, DEFAULT_MAX_SAML_METADATA_SIZE, DEFAULT_MAX_SAML_RESPONSE_SIZE, DataEncryptionAlgorithm, DigestAlgorithm, DiscoveryError, KeyEncryptionAlgorithm, REQUIRED_DISCOVERY_FIELDS, SignatureAlgorithm, computeDiscoveryUrl, discoverOIDCConfig, fetchDiscoveryDocument, needsRuntimeDiscovery, normalizeDiscoveryUrls, normalizeUrl, selectTokenEndpointAuthMethod, sso, validateDiscoveryDocument, validateDiscoveryUrl, validateSAMLTimestamp };
3360
+
3385
3361
  //# sourceMappingURL=index.mjs.map