@better-auth/sso 1.5.6 → 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-DoxMd-mL.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
@@ -12,7 +12,6 @@ import { deleteSessionCookie, setSessionCookie } from "better-auth/cookies";
12
12
  import { handleOAuthUserInfo } from "better-auth/oauth2";
13
13
  import { decodeJwt } from "jose";
14
14
  import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
15
-
16
15
  //#region src/constants.ts
17
16
  /**
18
17
  * SAML Constants
@@ -30,21 +29,11 @@ const SAML_SESSION_BY_ID_PREFIX = "saml-session-by-id:";
30
29
  /** Prefix for LogoutRequest IDs used in SP-initiated SLO validation */
31
30
  const LOGOUT_REQUEST_KEY_PREFIX = "saml-logout-request:";
32
31
  /**
33
- * Default TTL for AuthnRequest records (5 minutes).
34
- * This should be sufficient for most IdPs while protecting against stale requests.
35
- */
36
- const DEFAULT_AUTHN_REQUEST_TTL_MS = 300 * 1e3;
37
- /**
38
32
  * Default TTL for used assertion records (15 minutes).
39
33
  * This should match the maximum expected NotOnOrAfter window plus clock skew.
40
34
  */
41
35
  const DEFAULT_ASSERTION_TTL_MS = 900 * 1e3;
42
36
  /**
43
- * Default TTL for LogoutRequest records (5 minutes).
44
- * Should be sufficient for IdP to process and respond.
45
- */
46
- const DEFAULT_LOGOUT_REQUEST_TTL_MS = 300 * 1e3;
47
- /**
48
37
  * Default clock skew tolerance (5 minutes).
49
38
  * Allows for minor time differences between IdP and SP servers.
50
39
  *
@@ -65,7 +54,6 @@ const DEFAULT_MAX_SAML_RESPONSE_SIZE = 256 * 1024;
65
54
  */
66
55
  const DEFAULT_MAX_SAML_METADATA_SIZE = 100 * 1024;
67
56
  const SAML_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success";
68
-
69
57
  //#endregion
70
58
  //#region src/utils.ts
71
59
  /**
@@ -119,7 +107,6 @@ function maskClientId(clientId) {
119
107
  if (clientId.length <= 4) return "****";
120
108
  return `****${clientId.slice(-4)}`;
121
109
  }
122
-
123
110
  //#endregion
124
111
  //#region src/linking/org-assignment.ts
125
112
  /**
@@ -216,7 +203,6 @@ async function assignOrganizationByDomain(ctx, options) {
216
203
  }
217
204
  });
218
205
  }
219
-
220
206
  //#endregion
221
207
  //#region src/routes/domain-verification.ts
222
208
  const DNS_LABEL_MAX_LENGTH = 63;
@@ -382,7 +368,6 @@ const verifyDomain = (options) => {
382
368
  ctx.setStatus(204);
383
369
  });
384
370
  };
385
-
386
371
  //#endregion
387
372
  //#region src/saml/parser.ts
388
373
  const xmlParser = new XMLParser({
@@ -417,7 +402,6 @@ function countAllNodes(obj, nodeName) {
417
402
  else if (typeof value === "object" && value !== null) count += countAllNodes(value, nodeName);
418
403
  return count;
419
404
  }
420
-
421
405
  //#endregion
422
406
  //#region src/saml/algorithms.ts
423
407
  const SignatureAlgorithm = {
@@ -598,7 +582,6 @@ function validateConfigAlgorithms(config, options = {}) {
598
582
  });
599
583
  }
600
584
  }
601
-
602
585
  //#endregion
603
586
  //#region src/saml/assertions.ts
604
587
  /** @lintignore used in tests */
@@ -641,7 +624,6 @@ function validateSingleAssertion(samlResponse) {
641
624
  code: "SAML_MULTIPLE_ASSERTIONS"
642
625
  });
643
626
  }
644
-
645
627
  //#endregion
646
628
  //#region src/routes/schemas.ts
647
629
  const oidcMappingSchema = z.object({
@@ -720,7 +702,6 @@ const updateSSOProviderBodySchema = z.object({
720
702
  oidcConfig: oidcConfigSchema.optional(),
721
703
  samlConfig: samlConfigSchema.optional()
722
704
  });
723
-
724
705
  //#endregion
725
706
  //#region src/routes/providers.ts
726
707
  const ADMIN_ROLES = ["owner", "admin"];
@@ -950,7 +931,7 @@ const updateSSOProvider = (options) => {
950
931
  }
951
932
  if (body.samlConfig) {
952
933
  if (body.samlConfig.idpMetadata?.metadata) {
953
- const maxMetadataSize = options?.saml?.maxMetadataSize ?? DEFAULT_MAX_SAML_METADATA_SIZE;
934
+ const maxMetadataSize = options?.saml?.maxMetadataSize ?? 102400;
954
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)` });
955
936
  }
956
937
  if (body.samlConfig.signatureAlgorithm !== void 0 || body.samlConfig.digestAlgorithm !== void 0) validateConfigAlgorithms({
@@ -1013,7 +994,6 @@ const deleteSSOProvider = () => {
1013
994
  return ctx.json({ success: true });
1014
995
  });
1015
996
  };
1016
-
1017
997
  //#endregion
1018
998
  //#region src/oidc/types.ts
1019
999
  /**
@@ -1040,7 +1020,6 @@ const REQUIRED_DISCOVERY_FIELDS = [
1040
1020
  "token_endpoint",
1041
1021
  "jwks_uri"
1042
1022
  ];
1043
-
1044
1023
  //#endregion
1045
1024
  //#region src/oidc/discovery.ts
1046
1025
  /**
@@ -1307,7 +1286,6 @@ async function ensureRuntimeDiscovery(config, issuer, isTrustedOrigin) {
1307
1286
  jwksEndpoint: hydrated.jwksEndpoint
1308
1287
  };
1309
1288
  }
1310
-
1311
1289
  //#endregion
1312
1290
  //#region src/oidc/errors.ts
1313
1291
  /**
@@ -1378,7 +1356,6 @@ function mapDiscoveryErrorToAPIError(error) {
1378
1356
  });
1379
1357
  }
1380
1358
  }
1381
-
1382
1359
  //#endregion
1383
1360
  //#region src/saml/error-codes.ts
1384
1361
  const SAML_ERROR_CODES = defineErrorCodes({
@@ -1389,7 +1366,6 @@ const SAML_ERROR_CODES = defineErrorCodes({
1389
1366
  IDP_SLO_NOT_SUPPORTED: "IdP does not support Single Logout Service",
1390
1367
  SAML_PROVIDER_NOT_FOUND: "SAML provider not found"
1391
1368
  });
1392
-
1393
1369
  //#endregion
1394
1370
  //#region src/saml-state.ts
1395
1371
  async function generateRelayState(c, link, additionalData) {
@@ -1421,7 +1397,10 @@ async function parseRelayState(c) {
1421
1397
  const errorURL = c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;
1422
1398
  let parsedData;
1423
1399
  try {
1424
- parsedData = await parseGenericState(c, state, { cookieName: "relay_state" });
1400
+ parsedData = await parseGenericState(c, state, {
1401
+ cookieName: "relay_state",
1402
+ skipStateCookieCheck: true
1403
+ });
1425
1404
  } catch (error) {
1426
1405
  c.context.logger.error("Failed to parse relay state", error);
1427
1406
  throw new APIError("BAD_REQUEST", {
@@ -1432,7 +1411,6 @@ async function parseRelayState(c) {
1432
1411
  if (!parsedData.errorURL) parsedData.errorURL = errorURL;
1433
1412
  return parsedData;
1434
1413
  }
1435
-
1436
1414
  //#endregion
1437
1415
  //#region src/routes/helpers.ts
1438
1416
  async function findSAMLProvider(providerId, options, adapter) {
@@ -1512,7 +1490,6 @@ function createSAMLPostForm(action, samlParam, samlValue, relayState) {
1512
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>`;
1513
1491
  return new Response(html, { headers: { "Content-Type": "text/html" } });
1514
1492
  }
1515
-
1516
1493
  //#endregion
1517
1494
  //#region src/routes/sso.ts
1518
1495
  /**
@@ -1534,7 +1511,7 @@ function getOIDCRedirectURI(baseURL, providerId, options) {
1534
1511
  * @throws {APIError} If timestamps are invalid, expired, or not yet valid
1535
1512
  */
1536
1513
  function validateSAMLTimestamp(conditions, options = {}) {
1537
- const clockSkew = options.clockSkew ?? DEFAULT_CLOCK_SKEW_MS;
1514
+ const clockSkew = options.clockSkew ?? 3e5;
1538
1515
  if (!(conditions?.notBefore || conditions?.notOnOrAfter)) {
1539
1516
  if (options.requireTimestamps) throw new APIError("BAD_REQUEST", {
1540
1517
  message: "SAML assertion missing required timestamp conditions",
@@ -1896,7 +1873,7 @@ const registerSSOProvider = (options) => {
1896
1873
  const body = ctx.body;
1897
1874
  if (z.string().url().safeParse(body.issuer).error) throw new APIError("BAD_REQUEST", { message: "Invalid issuer. Must be a valid URL" });
1898
1875
  if (body.samlConfig?.idpMetadata?.metadata) {
1899
- const maxMetadataSize = options?.saml?.maxMetadataSize ?? DEFAULT_MAX_SAML_METADATA_SIZE;
1876
+ const maxMetadataSize = options?.saml?.maxMetadataSize ?? 102400;
1900
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)` });
1901
1878
  }
1902
1879
  if (ctx.body.organizationId) {
@@ -2248,7 +2225,7 @@ const signInSSO = (options) => {
2248
2225
  if (!loginRequest) throw new APIError("BAD_REQUEST", { message: "Invalid SAML request" });
2249
2226
  const { state: relayState } = await generateRelayState(ctx, void 0, false);
2250
2227
  if (loginRequest.id && options?.saml?.enableInResponseToValidation) {
2251
- const ttl = options?.saml?.requestTTL ?? DEFAULT_AUTHN_REQUEST_TTL_MS;
2228
+ const ttl = options?.saml?.requestTTL ?? 3e5;
2252
2229
  const record = {
2253
2230
  id: loginRequest.id,
2254
2231
  providerId: provider.providerId,
@@ -2346,6 +2323,7 @@ async function handleOIDCCallback(ctx, options, providerId, stateData) {
2346
2323
  tokenEndpoint: config.tokenEndpoint,
2347
2324
  authentication: config.tokenEndpointAuthentication === "client_secret_post" ? "post" : "basic"
2348
2325
  }).catch((e) => {
2326
+ ctx.context.logger.error("Error validating authorization code", e);
2349
2327
  if (e instanceof BetterFetchError) throw ctx.redirect(`${errorURL || callbackURL}?error=invalid_provider&error_description=${e.message}`);
2350
2328
  return null;
2351
2329
  });
@@ -2557,7 +2535,7 @@ const callbackSSOSAML = (options) => {
2557
2535
  }
2558
2536
  if (!ctx.body?.SAMLResponse) throw new APIError("BAD_REQUEST", { message: "SAMLResponse is required for POST requests" });
2559
2537
  const { SAMLResponse } = ctx.body;
2560
- const maxResponseSize = options?.saml?.maxResponseSize ?? DEFAULT_MAX_SAML_RESPONSE_SIZE;
2538
+ const maxResponseSize = options?.saml?.maxResponseSize ?? 262144;
2561
2539
  if (new TextEncoder().encode(SAMLResponse).length > maxResponseSize) throw new APIError("BAD_REQUEST", { message: `SAML response exceeds maximum allowed size (${maxResponseSize} bytes)` });
2562
2540
  let relayState = null;
2563
2541
  if (ctx.body.RelayState) try {
@@ -2697,7 +2675,7 @@ const callbackSSOSAML = (options) => {
2697
2675
  if (assertionId) {
2698
2676
  const issuer = idp.entityMeta.getEntityID();
2699
2677
  const conditions = extract.conditions;
2700
- const clockSkew = options?.saml?.clockSkew ?? DEFAULT_CLOCK_SKEW_MS;
2678
+ const clockSkew = options?.saml?.clockSkew ?? 3e5;
2701
2679
  const expiresAt = conditions?.notOnOrAfter ? new Date(conditions.notOnOrAfter).getTime() + clockSkew : Date.now() + DEFAULT_ASSERTION_TTL_MS;
2702
2680
  const existingAssertion = await ctx.context.internalAdapter.findVerificationValue(`${USED_ASSERTION_KEY_PREFIX}${assertionId}`);
2703
2681
  let isReplay = false;
@@ -2837,7 +2815,7 @@ const acsEndpoint = (options) => {
2837
2815
  const { providerId } = ctx.params;
2838
2816
  const currentCallbackPath = `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`;
2839
2817
  const appOrigin = new URL(ctx.context.baseURL).origin;
2840
- const maxResponseSize = options?.saml?.maxResponseSize ?? DEFAULT_MAX_SAML_RESPONSE_SIZE;
2818
+ const maxResponseSize = options?.saml?.maxResponseSize ?? 262144;
2841
2819
  if (new TextEncoder().encode(SAMLResponse).length > maxResponseSize) throw new APIError("BAD_REQUEST", { message: `SAML response exceeds maximum allowed size (${maxResponseSize} bytes)` });
2842
2820
  let relayState = null;
2843
2821
  if (ctx.body.RelayState) try {
@@ -2968,7 +2946,7 @@ const acsEndpoint = (options) => {
2968
2946
  if (assertionIdAcs) {
2969
2947
  const issuer = idp.entityMeta.getEntityID();
2970
2948
  const conditions = extract.conditions;
2971
- const clockSkew = options?.saml?.clockSkew ?? DEFAULT_CLOCK_SKEW_MS;
2949
+ const clockSkew = options?.saml?.clockSkew ?? 3e5;
2972
2950
  const expiresAt = conditions?.notOnOrAfter ? new Date(conditions.notOnOrAfter).getTime() + clockSkew : Date.now() + DEFAULT_ASSERTION_TTL_MS;
2973
2951
  const existingAssertion = await ctx.context.internalAdapter.findVerificationValue(`${USED_ASSERTION_KEY_PREFIX}${assertionIdAcs}`);
2974
2952
  let isReplay = false;
@@ -3136,7 +3114,7 @@ async function handleLogoutResponse(ctx, sp, idp, relayState, providerId) {
3136
3114
  }
3137
3115
  const extract = parsed?.extract;
3138
3116
  const statusCode = extract?.statusCode || extract?.status || parsed?.samlContent?.status?.statusCode;
3139
- if (statusCode && statusCode !== SAML_STATUS_SUCCESS) {
3117
+ if (statusCode && statusCode !== "urn:oasis:names:tc:SAML:2.0:status:Success") {
3140
3118
  ctx.context.logger.warn("LogoutResponse indicates failure", { statusCode });
3141
3119
  throw APIError.from("BAD_REQUEST", SAML_ERROR_CODES.LOGOUT_FAILED_AT_IDP);
3142
3120
  }
@@ -3229,7 +3207,7 @@ const initiateSLO = (options) => {
3229
3207
  sessionIndex,
3230
3208
  relayState: callbackURL
3231
3209
  });
3232
- const ttl = options?.saml?.logoutRequestTTL ?? DEFAULT_LOGOUT_REQUEST_TTL_MS;
3210
+ const ttl = options?.saml?.logoutRequestTTL ?? 3e5;
3233
3211
  await ctx.context.internalAdapter.createVerificationValue({
3234
3212
  identifier: `${LOGOUT_REQUEST_KEY_PREFIX}${logoutRequest.id}`,
3235
3213
  value: providerId,
@@ -3242,7 +3220,6 @@ const initiateSLO = (options) => {
3242
3220
  throw ctx.redirect(logoutRequest.context);
3243
3221
  });
3244
3222
  };
3245
-
3246
3223
  //#endregion
3247
3224
  //#region src/index.ts
3248
3225
  saml.setSchemaValidator({ async validate(xml) {
@@ -3378,7 +3355,7 @@ function sso(options) {
3378
3355
  options
3379
3356
  };
3380
3357
  }
3381
-
3382
3358
  //#endregion
3383
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
+
3384
3361
  //# sourceMappingURL=index.mjs.map