@voltro/plugin-sso-saml 0.84.0 → 0.86.0
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 +1215 -0
- package/THIRD-PARTY-NOTICES.md +5 -471
- package/dist/index.d.ts +12 -15
- package/dist/index.js +145 -378
- package/dist/saml-cache.js +73 -0
- package/dist/saml-errors.js +11 -0
- package/dist/saml-logout.js +79 -0
- package/dist/saml-metadata.js +66 -0
- package/dist/saml.js +34 -0
- package/package.json +4 -5
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { id as e, table as t, text as n } from "@voltro/database";
|
|
2
|
+
//#region src/saml-cache.ts
|
|
3
|
+
var r = "_voltro_saml_replay", i = t(r, {
|
|
4
|
+
id: e({ prefix: "samlrq" }),
|
|
5
|
+
requestId: n().unique(),
|
|
6
|
+
instant: n(),
|
|
7
|
+
createdAtMs: n()
|
|
8
|
+
}).index("bySamlRequestId", ["requestId"]), a = [i], o = (e, t) => ({
|
|
9
|
+
kind: "binary",
|
|
10
|
+
op: "eq",
|
|
11
|
+
column: e,
|
|
12
|
+
value: t
|
|
13
|
+
}), s = (e, t = 6e5) => {
|
|
14
|
+
let n = async (t) => (await e.query({
|
|
15
|
+
table: r,
|
|
16
|
+
predicate: o("requestId", t),
|
|
17
|
+
order: [{
|
|
18
|
+
column: "id",
|
|
19
|
+
direction: "asc"
|
|
20
|
+
}],
|
|
21
|
+
take: 1,
|
|
22
|
+
skip: void 0,
|
|
23
|
+
projection: void 0
|
|
24
|
+
}))[0], i = (e, n) => {
|
|
25
|
+
let r = Number(e.createdAtMs);
|
|
26
|
+
return Number.isFinite(r) && n - r > t;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
saveAsync: async (t, n) => {
|
|
30
|
+
let i = Date.now();
|
|
31
|
+
try {
|
|
32
|
+
return await e.insert(r, {
|
|
33
|
+
requestId: t,
|
|
34
|
+
instant: n,
|
|
35
|
+
createdAtMs: String(i)
|
|
36
|
+
}), {
|
|
37
|
+
value: n,
|
|
38
|
+
createdAt: i
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
getAsync: async (t) => {
|
|
45
|
+
let a = await n(t);
|
|
46
|
+
return a ? i(a, Date.now()) ? (await e.delete(r, String(a.id)).catch(() => void 0), null) : String(a.instant) : null;
|
|
47
|
+
},
|
|
48
|
+
removeAsync: async (t) => {
|
|
49
|
+
if (t == null) return null;
|
|
50
|
+
let i = await n(t);
|
|
51
|
+
return i ? (await e.delete(r, String(i.id)).catch(() => void 0), t) : null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}, c = (e = 6e5) => {
|
|
55
|
+
let t = /* @__PURE__ */ new Map();
|
|
56
|
+
return {
|
|
57
|
+
saveAsync: async (e, n) => {
|
|
58
|
+
if (t.has(e)) return null;
|
|
59
|
+
let r = {
|
|
60
|
+
value: n,
|
|
61
|
+
createdAt: Date.now()
|
|
62
|
+
};
|
|
63
|
+
return t.set(e, r), r;
|
|
64
|
+
},
|
|
65
|
+
getAsync: async (n) => {
|
|
66
|
+
let r = t.get(n);
|
|
67
|
+
return r ? Date.now() - r.createdAt > e ? (t.delete(n), null) : r.value : null;
|
|
68
|
+
},
|
|
69
|
+
removeAsync: async (e) => e == null || !t.has(e) ? null : (t.delete(e), e)
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { r as REPLAY_CACHE_TABLE, s as dataStoreReplayCache, c as memoryReplayCache, i as samlReplayTable, a as samlReplayTables };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
//#region src/saml-errors.ts
|
|
3
|
+
var t = class extends e.TaggedError()("SamlMetadataFetchError", {
|
|
4
|
+
url: e.String,
|
|
5
|
+
reason: e.String
|
|
6
|
+
}) {}, n = class extends e.TaggedError()("SamlMetadataParseError", {
|
|
7
|
+
url: e.String,
|
|
8
|
+
reason: e.String
|
|
9
|
+
}) {};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { t as SamlMetadataFetchError, n as SamlMetadataParseError };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { id as e, table as t, text as n } from "@voltro/database";
|
|
2
|
+
//#region src/saml-logout.ts
|
|
3
|
+
var r = "_voltro_saml_logout", i = t(r, {
|
|
4
|
+
id: e({ prefix: "samllo" }),
|
|
5
|
+
sloKey: n().unique(),
|
|
6
|
+
nameId: n(),
|
|
7
|
+
nameIdFormat: n(),
|
|
8
|
+
sessionIndex: n(),
|
|
9
|
+
createdAtMs: n()
|
|
10
|
+
}).index("bySamlSloKey", ["sloKey"]), a = [i], o = (e, t) => ({
|
|
11
|
+
kind: "binary",
|
|
12
|
+
op: "eq",
|
|
13
|
+
column: e,
|
|
14
|
+
value: t
|
|
15
|
+
}), s = (e) => ({
|
|
16
|
+
nameId: String(e.nameId ?? ""),
|
|
17
|
+
...e.nameIdFormat ? { nameIdFormat: String(e.nameIdFormat) } : {},
|
|
18
|
+
...e.sessionIndex ? { sessionIndex: String(e.sessionIndex) } : {}
|
|
19
|
+
}), c = (e, t = 6048e5) => {
|
|
20
|
+
let n = async (t) => (await e.query({
|
|
21
|
+
table: r,
|
|
22
|
+
predicate: o("sloKey", t),
|
|
23
|
+
order: [{
|
|
24
|
+
column: "id",
|
|
25
|
+
direction: "asc"
|
|
26
|
+
}],
|
|
27
|
+
take: 1,
|
|
28
|
+
skip: void 0,
|
|
29
|
+
projection: void 0
|
|
30
|
+
}))[0], i = (e, n) => {
|
|
31
|
+
let r = Number(e.createdAtMs);
|
|
32
|
+
return Number.isFinite(r) && n - r > t;
|
|
33
|
+
}, a = async (t) => {
|
|
34
|
+
await e.delete(r, String(t.id)).catch(() => void 0);
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
save: async (t) => {
|
|
38
|
+
let i = await n(t.sloKey);
|
|
39
|
+
i && await a(i), await e.insert(r, {
|
|
40
|
+
sloKey: t.sloKey,
|
|
41
|
+
nameId: t.nameId,
|
|
42
|
+
nameIdFormat: t.nameIdFormat ?? "",
|
|
43
|
+
sessionIndex: t.sessionIndex ?? "",
|
|
44
|
+
createdAtMs: String(Date.now())
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
load: async (e) => {
|
|
48
|
+
let t = await n(e);
|
|
49
|
+
return t ? i(t, Date.now()) ? (await a(t), null) : s(t) : null;
|
|
50
|
+
},
|
|
51
|
+
remove: async (e) => {
|
|
52
|
+
let t = await n(e);
|
|
53
|
+
t && await a(t);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, l = (e = 6048e5) => {
|
|
57
|
+
let t = /* @__PURE__ */ new Map();
|
|
58
|
+
return {
|
|
59
|
+
save: async (e) => {
|
|
60
|
+
t.set(e.sloKey, {
|
|
61
|
+
subject: {
|
|
62
|
+
nameId: e.nameId,
|
|
63
|
+
...e.nameIdFormat ? { nameIdFormat: e.nameIdFormat } : {},
|
|
64
|
+
...e.sessionIndex ? { sessionIndex: e.sessionIndex } : {}
|
|
65
|
+
},
|
|
66
|
+
createdAt: Date.now()
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
load: async (n) => {
|
|
70
|
+
let r = t.get(n);
|
|
71
|
+
return r ? Date.now() - r.createdAt > e ? (t.delete(n), null) : r.subject : null;
|
|
72
|
+
},
|
|
73
|
+
remove: async (e) => {
|
|
74
|
+
t.delete(e);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { r as LOGOUT_SESSION_TABLE, c as dataStoreLogoutStore, l as memoryLogoutStore, i as samlLogoutTable, a as samlLogoutTables };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SamlMetadataFetchError as e, SamlMetadataParseError as t } from "./saml-errors.js";
|
|
2
|
+
import { Effect as n } from "effect";
|
|
3
|
+
import { HttpClient as r } from "effect/http";
|
|
4
|
+
//#region src/saml-metadata.ts
|
|
5
|
+
var i = "urn:oasis:names:tc:SAML:2.0:metadata", a = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", o = (e) => e.slice(e.indexOf(":") + 1), s = (e, t) => (RegExp(`\\b${t}\\s*=\\s*"([^"]*)"`).exec(e) ?? RegExp(`\\b${t}\\s*=\\s*'([^']*)'`).exec(e))?.[1], c = (e, t) => {
|
|
6
|
+
let n = [], r = /<([A-Za-z][\w.:-]*)\b([^>]*)>/g, i;
|
|
7
|
+
for (; (i = r.exec(e)) !== null;) o(i[1]) === t && n.push(i[0]);
|
|
8
|
+
return n;
|
|
9
|
+
}, l = (e) => {
|
|
10
|
+
let t = [], n = /<([A-Za-z][\w.:-]*KeyDescriptor)\b([^>]*)>/g, r;
|
|
11
|
+
for (; (r = n.exec(e)) !== null;) {
|
|
12
|
+
if (o(r[1]) !== "KeyDescriptor") continue;
|
|
13
|
+
let n = RegExp(`</${r[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}>`), i = e.slice(r.index + r[0].length), a = n.exec(i), c = a ? i.slice(0, a.index) : i;
|
|
14
|
+
t.push({
|
|
15
|
+
use: s(r[0], "use"),
|
|
16
|
+
body: c
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
}, u = (e) => {
|
|
21
|
+
let t = l(e), n = t.find((e) => e.use === "signing") ?? t.find((e) => e.use === void 0) ?? t[0];
|
|
22
|
+
if (!n) return;
|
|
23
|
+
let r = /<[^>]*X509Certificate[^>]*>([\s\S]*?)<\/[^>]*X509Certificate>/.exec(n.body)?.[1]?.replace(/\s+/g, "");
|
|
24
|
+
return r && r.length > 0 ? r : void 0;
|
|
25
|
+
}, d = (e, r) => n.gen(function* () {
|
|
26
|
+
if (!e.includes(i) && !/EntityDescriptor/.test(e)) return yield* n.fail(new t({
|
|
27
|
+
url: r,
|
|
28
|
+
reason: "not-saml-metadata"
|
|
29
|
+
}));
|
|
30
|
+
let o = c(e, "SingleSignOnService"), l = o.find((e) => s(e, "Binding") === a) ?? o[0], d = l ? s(l, "Location") : void 0;
|
|
31
|
+
if (!d) return yield* n.fail(new t({
|
|
32
|
+
url: r,
|
|
33
|
+
reason: "no-entryPoint"
|
|
34
|
+
}));
|
|
35
|
+
let f = u(e);
|
|
36
|
+
if (!f) return yield* n.fail(new t({
|
|
37
|
+
url: r,
|
|
38
|
+
reason: "no-signing-cert"
|
|
39
|
+
}));
|
|
40
|
+
let p = c(e, "SingleLogoutService"), m = p.find((e) => s(e, "Binding") === a) ?? p[0], h = m ? s(m, "Location") : void 0, g = c(e, "EntityDescriptor")[0], _ = g ? s(g, "entityID") : void 0;
|
|
41
|
+
return {
|
|
42
|
+
entryPoint: d,
|
|
43
|
+
idpCert: f,
|
|
44
|
+
...h ? { logoutUrl: h } : {},
|
|
45
|
+
..._ ? { issuer: _ } : {}
|
|
46
|
+
};
|
|
47
|
+
}), f = (t) => n.gen(function* () {
|
|
48
|
+
let i = yield* (yield* r.HttpClient).get(t).pipe(n.mapError(() => new e({
|
|
49
|
+
url: t,
|
|
50
|
+
reason: "network"
|
|
51
|
+
})));
|
|
52
|
+
if (i.status < 200 || i.status >= 300) return yield* n.fail(new e({
|
|
53
|
+
url: t,
|
|
54
|
+
reason: `status ${i.status}`
|
|
55
|
+
}));
|
|
56
|
+
let a = yield* i.text.pipe(n.mapError(() => new e({
|
|
57
|
+
url: t,
|
|
58
|
+
reason: "body"
|
|
59
|
+
})));
|
|
60
|
+
return a.trim() ? yield* d(a, t) : yield* n.fail(new e({
|
|
61
|
+
url: t,
|
|
62
|
+
reason: "empty-body"
|
|
63
|
+
}));
|
|
64
|
+
});
|
|
65
|
+
//#endregion
|
|
66
|
+
export { f as fetchIdpMetadata, d as parseIdpMetadata };
|
package/dist/saml.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/saml.ts
|
|
2
|
+
var e = (e) => e.replace(/-----(BEGIN|END) CERTIFICATE-----/g, "").replace(/\s+/g, ""), t = (t) => {
|
|
3
|
+
let r = t.signingCert ? `
|
|
4
|
+
<KeyDescriptor use="signing">
|
|
5
|
+
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
6
|
+
<X509Data><X509Certificate>${n(e(t.signingCert))}</X509Certificate></X509Data>
|
|
7
|
+
</KeyInfo>
|
|
8
|
+
</KeyDescriptor>` : "", i = t.sloUrl ? `
|
|
9
|
+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="${n(t.sloUrl)}"/>` : "";
|
|
10
|
+
return `<?xml version="1.0"?>
|
|
11
|
+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="${n(t.entityId)}">
|
|
12
|
+
<SPSSODescriptor AuthnRequestsSigned="${t.authnRequestsSigned ? "true" : "false"}" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">${r}${i}
|
|
13
|
+
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress</NameIDFormat>
|
|
14
|
+
<AssertionConsumerService index="0" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="${n(t.acsUrl)}"/>
|
|
15
|
+
</SPSSODescriptor>
|
|
16
|
+
</EntityDescriptor>`;
|
|
17
|
+
}, n = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """), r = (e) => {
|
|
18
|
+
let t = e.attributes ?? {}, n = (...n) => {
|
|
19
|
+
for (let r of n) {
|
|
20
|
+
let n = e[r] ?? t[r];
|
|
21
|
+
if (typeof n == "string" && n.length > 0) return n;
|
|
22
|
+
if (Array.isArray(n) && typeof n[0] == "string") return n[0];
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}, r = String(e.nameID ?? e.nameId ?? n("nameID") ?? "");
|
|
26
|
+
return {
|
|
27
|
+
nameId: r,
|
|
28
|
+
email: n("email", "mail", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress") ?? (r.includes("@") ? r : null),
|
|
29
|
+
displayName: n("displayName", "name", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "cn"),
|
|
30
|
+
attributes: t
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { r as extractProfile, t as spMetadataXml };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-sso-saml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, and SP metadata. Signature verification via @node-saml/node-saml (optional, lazy); the framework integration (routes, session minting, attribute mapping) is built in.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -33,15 +33,14 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@voltro/
|
|
38
|
-
"@voltro/protocol": "0.84.0"
|
|
36
|
+
"@voltro/database": "0.86.0",
|
|
37
|
+
"@voltro/protocol": "0.86.0"
|
|
39
38
|
},
|
|
40
39
|
"optionalDependencies": {
|
|
41
40
|
"@node-saml/node-saml": "^5.1.0"
|
|
42
41
|
},
|
|
43
42
|
"peerDependencies": {
|
|
44
|
-
"effect": "
|
|
43
|
+
"effect": "4.0.1"
|
|
45
44
|
},
|
|
46
45
|
"publishConfig": {
|
|
47
46
|
"access": "public"
|