@better-auth/sso 1.6.12 → 1.6.13

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/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-dnGn0OgM.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BcVD6vO4.mjs";
2
2
  //#region src/client.ts
3
3
  const ssoClient = (options) => {
4
4
  return {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-dnGn0OgM.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BcVD6vO4.mjs";
2
2
  import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
3
3
  import { XMLParser, XMLValidator } from "fast-xml-parser";
4
4
  import { X509Certificate } from "node:crypto";
@@ -57,7 +57,6 @@ const DEFAULT_MAX_SAML_RESPONSE_SIZE = 256 * 1024;
57
57
  * Protects against oversized metadata documents.
58
58
  */
59
59
  const DEFAULT_MAX_SAML_METADATA_SIZE = 100 * 1024;
60
- const SAML_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success";
61
60
  //#endregion
62
61
  //#region src/utils.ts
63
62
  /**
@@ -104,6 +103,10 @@ function parseCertificate(certPem) {
104
103
  publicKeyAlgorithm: cert.publicKey.asymmetricKeyType?.toUpperCase() || "UNKNOWN"
105
104
  };
106
105
  }
106
+ function normalizePem(key) {
107
+ if (!key) return key;
108
+ return `${key.split("\n").map((line) => line.trim()).join("\n").trim()}\n`;
109
+ }
107
110
  function getHostnameFromDomain(domain) {
108
111
  return getHostname(domain) || null;
109
112
  }
@@ -1529,10 +1532,10 @@ function createSP(config, baseURL, providerId, opts) {
1529
1532
  wantLogoutRequestSigned: opts?.sloOptions?.wantLogoutRequestSigned ?? false,
1530
1533
  wantLogoutResponseSigned: opts?.sloOptions?.wantLogoutResponseSigned ?? false,
1531
1534
  metadata: spData?.metadata,
1532
- privateKey: spData?.privateKey || config.privateKey,
1535
+ privateKey: normalizePem(spData?.privateKey || config.privateKey),
1533
1536
  privateKeyPass: spData?.privateKeyPass,
1534
1537
  isAssertionEncrypted: spData?.isAssertionEncrypted || false,
1535
- encPrivateKey: spData?.encPrivateKey,
1538
+ encPrivateKey: normalizePem(spData?.encPrivateKey),
1536
1539
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1537
1540
  nameIDFormat: config.identifierFormat ? [config.identifierFormat] : void 0,
1538
1541
  relayState: opts?.relayState
@@ -1542,10 +1545,10 @@ function createIdP(config) {
1542
1545
  const idpData = config.idpMetadata;
1543
1546
  if (idpData?.metadata) return saml.IdentityProvider({
1544
1547
  metadata: idpData.metadata,
1545
- privateKey: idpData.privateKey,
1548
+ privateKey: normalizePem(idpData.privateKey),
1546
1549
  privateKeyPass: idpData.privateKeyPass,
1547
1550
  isAssertionEncrypted: idpData.isAssertionEncrypted,
1548
- encPrivateKey: idpData.encPrivateKey,
1551
+ encPrivateKey: normalizePem(idpData.encPrivateKey),
1549
1552
  encPrivateKeyPass: idpData.encPrivateKeyPass
1550
1553
  });
1551
1554
  return saml.IdentityProvider({
@@ -1558,7 +1561,7 @@ function createIdP(config) {
1558
1561
  signingCert: idpData?.cert || config.cert,
1559
1562
  wantAuthnRequestsSigned: config.authnRequestsSigned || false,
1560
1563
  isAssertionEncrypted: idpData?.isAssertionEncrypted || false,
1561
- encPrivateKey: idpData?.encPrivateKey,
1564
+ encPrivateKey: normalizePem(idpData?.encPrivateKey),
1562
1565
  encPrivateKeyPass: idpData?.encPrivateKeyPass
1563
1566
  });
1564
1567
  }
@@ -1792,12 +1795,16 @@ async function processSAMLResponse(ctx, params, options) {
1792
1795
  } else ctx.context.logger.warn("Could not extract assertion ID for replay protection", { providerId });
1793
1796
  const attributes = extract.attributes || {};
1794
1797
  const mapping = parsedSamlConfig.mapping ?? {};
1798
+ const attr = (key) => {
1799
+ const value = attributes[key];
1800
+ return Array.isArray(value) ? value[0] : value;
1801
+ };
1795
1802
  const userInfo = {
1796
1803
  ...Object.fromEntries(Object.entries(mapping.extraFields || {}).map(([key, value]) => [key, attributes[value]])),
1797
- id: attributes[mapping.id || "nameID"] || extract.nameID,
1798
- email: (attributes[mapping.email || "email"] || extract.nameID).toLowerCase(),
1799
- name: [attributes[mapping.firstName || "givenName"], attributes[mapping.lastName || "surname"]].filter(Boolean).join(" ") || attributes[mapping.name || "displayName"] || extract.nameID,
1800
- emailVerified: options?.trustEmailVerified && mapping.emailVerified ? attributes[mapping.emailVerified] || false : false
1804
+ id: attr(mapping.id || "nameID") || extract.nameID,
1805
+ email: (attr(mapping.email || "email") || extract.nameID || "").toLowerCase(),
1806
+ name: [attr(mapping.firstName || "givenName"), attr(mapping.lastName || "surname")].filter(Boolean).join(" ") || attr(mapping.name || "displayName") || extract.nameID,
1807
+ emailVerified: options?.trustEmailVerified && mapping.emailVerified ? attr(mapping.emailVerified) || false : false
1801
1808
  };
1802
1809
  if (!userInfo.id || !userInfo.email) {
1803
1810
  ctx.context.logger.error("Missing essential user info from SAML response", {
@@ -1867,6 +1874,7 @@ async function processSAMLResponse(ctx, params, options) {
1867
1874
  const samlSessionKey = `${SAML_SESSION_KEY_PREFIX}${providerId}:${extract.nameID}`;
1868
1875
  const samlSessionData = {
1869
1876
  sessionId: session.id,
1877
+ sessionToken: session.token,
1870
1878
  providerId,
1871
1879
  nameID: extract.nameID,
1872
1880
  sessionIndex: extract.sessionIndex
@@ -2543,7 +2551,7 @@ const signInSSO = (options) => {
2543
2551
  const sp = saml.ServiceProvider({
2544
2552
  metadata,
2545
2553
  allowCreate: true,
2546
- privateKey: parsedSamlConfig.spMetadata?.privateKey || parsedSamlConfig.privateKey,
2554
+ privateKey: normalizePem(parsedSamlConfig.spMetadata?.privateKey || parsedSamlConfig.privateKey),
2547
2555
  privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
2548
2556
  relayState
2549
2557
  });
@@ -2558,15 +2566,15 @@ const signInSSO = (options) => {
2558
2566
  signingCert: idpData?.cert || parsedSamlConfig.cert,
2559
2567
  wantAuthnRequestsSigned: parsedSamlConfig.authnRequestsSigned || false,
2560
2568
  isAssertionEncrypted: idpData?.isAssertionEncrypted || false,
2561
- encPrivateKey: idpData?.encPrivateKey,
2569
+ encPrivateKey: normalizePem(idpData?.encPrivateKey),
2562
2570
  encPrivateKeyPass: idpData?.encPrivateKeyPass
2563
2571
  });
2564
2572
  else idp = saml.IdentityProvider({
2565
2573
  metadata: idpData.metadata,
2566
- privateKey: idpData.privateKey,
2574
+ privateKey: normalizePem(idpData.privateKey),
2567
2575
  privateKeyPass: idpData.privateKeyPass,
2568
2576
  isAssertionEncrypted: idpData.isAssertionEncrypted,
2569
- encPrivateKey: idpData.encPrivateKey,
2577
+ encPrivateKey: normalizePem(idpData.encPrivateKey),
2570
2578
  encPrivateKeyPass: idpData.encPrivateKeyPass
2571
2579
  });
2572
2580
  const loginRequest = sp.createLoginRequest(idp, "redirect");
@@ -3007,7 +3015,7 @@ async function handleLogoutRequest(ctx, sp, idp, relayState, providerId) {
3007
3015
  if (stored) {
3008
3016
  const data = safeJsonParse(stored.value);
3009
3017
  if (data) if (!sessionIndex || !data.sessionIndex || sessionIndex === data.sessionIndex) {
3010
- await ctx.context.internalAdapter.deleteSession(data.sessionId).catch((e) => ctx.context.logger.warn("Failed to delete session during SLO", { error: e }));
3018
+ await ctx.context.internalAdapter.deleteSession(data.sessionToken).catch((e) => ctx.context.logger.warn("Failed to delete session during SLO", { error: e }));
3011
3019
  await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${SAML_SESSION_BY_ID_PREFIX}${data.sessionId}`).catch((e) => ctx.context.logger.warn("Failed to delete SAML session lookup during SLO", e));
3012
3020
  } else ctx.context.logger.warn("SessionIndex mismatch in LogoutRequest - skipping session deletion", {
3013
3021
  providerId,
@@ -3017,10 +3025,9 @@ async function handleLogoutRequest(ctx, sp, idp, relayState, providerId) {
3017
3025
  await ctx.context.internalAdapter.deleteVerificationByIdentifier(key).catch((e) => ctx.context.logger.warn("Failed to delete SAML session key during SLO", e));
3018
3026
  }
3019
3027
  const currentSession = await getSessionFromCtx(ctx);
3020
- if (currentSession?.session) await ctx.context.internalAdapter.deleteSession(currentSession.session.id);
3028
+ if (currentSession?.session) await ctx.context.internalAdapter.deleteSession(currentSession.session.token);
3021
3029
  deleteSessionCookie(ctx);
3022
- const requestId = parsed.extract.request?.id || "";
3023
- const res = sp.createLogoutResponse(idp, null, binding, relayState || "", (template) => template.replace("{InResponseTo}", requestId).replace("{StatusCode}", SAML_STATUS_SUCCESS));
3030
+ const res = sp.createLogoutResponse(idp, parsed, binding, relayState || "");
3024
3031
  if (binding === "post" && res.entityEndpoint) return createSAMLPostForm(res.entityEndpoint, "SAMLResponse", res.context, relayState);
3025
3032
  throw ctx.redirect(res.context);
3026
3033
  }
@@ -3073,7 +3080,7 @@ const initiateSLO = (options) => {
3073
3080
  });
3074
3081
  if (samlSessionKey) await ctx.context.internalAdapter.deleteVerificationByIdentifier(samlSessionKey).catch((e) => ctx.context.logger.warn("Failed to delete SAML session key during logout", e));
3075
3082
  await ctx.context.internalAdapter.deleteVerificationByIdentifier(sessionLookupKey).catch((e) => ctx.context.logger.warn("Failed to delete session lookup key during logout", e));
3076
- await ctx.context.internalAdapter.deleteSession(session.session.id);
3083
+ await ctx.context.internalAdapter.deleteSession(session.session.token);
3077
3084
  deleteSessionCookie(ctx);
3078
3085
  throw ctx.redirect(logoutRequest.context);
3079
3086
  });
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.12";
3
+ const PACKAGE_VERSION = "1.6.13";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/sso",
3
- "version": "1.6.12",
3
+ "version": "1.6.13",
4
4
  "description": "SSO plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -58,7 +58,7 @@
58
58
  "dependencies": {
59
59
  "fast-xml-parser": "^5.8.0",
60
60
  "jose": "^6.1.3",
61
- "samlify": "~2.10.2",
61
+ "samlify": "^2.13.1",
62
62
  "tldts": "^6.1.0",
63
63
  "zod": "^4.3.6"
64
64
  },
@@ -70,15 +70,15 @@
70
70
  "express": "^5.2.1",
71
71
  "oauth2-mock-server": "^8.2.2",
72
72
  "tsdown": "0.21.1",
73
- "@better-auth/core": "1.6.12",
74
- "better-auth": "1.6.12"
73
+ "@better-auth/core": "1.6.13",
74
+ "better-auth": "1.6.13"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "@better-auth/utils": "0.4.1",
78
78
  "@better-fetch/fetch": "1.1.21",
79
79
  "better-call": "1.3.5",
80
- "@better-auth/core": "^1.6.12",
81
- "better-auth": "^1.6.12"
80
+ "@better-auth/core": "^1.6.13",
81
+ "better-auth": "^1.6.13"
82
82
  },
83
83
  "scripts": {
84
84
  "build": "tsdown",