@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
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
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:
|
|
1798
|
-
email: (
|
|
1799
|
-
name: [
|
|
1800
|
-
emailVerified: options?.trustEmailVerified && mapping.emailVerified ?
|
|
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.
|
|
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.
|
|
3028
|
+
if (currentSession?.session) await ctx.context.internalAdapter.deleteSession(currentSession.session.token);
|
|
3021
3029
|
deleteSessionCookie(ctx);
|
|
3022
|
-
const
|
|
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.
|
|
3083
|
+
await ctx.context.internalAdapter.deleteSession(session.session.token);
|
|
3077
3084
|
deleteSessionCookie(ctx);
|
|
3078
3085
|
throw ctx.redirect(logoutRequest.context);
|
|
3079
3086
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/sso",
|
|
3
|
-
"version": "1.6.
|
|
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": "
|
|
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.
|
|
74
|
-
"better-auth": "1.6.
|
|
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.
|
|
81
|
-
"better-auth": "^1.6.
|
|
80
|
+
"@better-auth/core": "^1.6.13",
|
|
81
|
+
"better-auth": "^1.6.13"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsdown",
|