@voltro/plugin-sso-saml 0.85.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/dist/index.js CHANGED
@@ -1,463 +1,230 @@
1
- import { Effect as e, Schema as t } from "effect";
2
- import { FetchHttpClient as n, HttpClient as r } from "effect/http";
3
- import { randomBytes as i } from "node:crypto";
4
- import { definePlugin as a, pluginInstanceName as o } from "@voltro/protocol";
5
- import { buildSetCookie as s, readCookie as c, signSession as l } from "@voltro/protocol/session";
6
- import { id as u, table as d, text as f } from "@voltro/database";
7
- //#region src/saml.ts
8
- var p = (e) => e.replace(/-----(BEGIN|END) CERTIFICATE-----/g, "").replace(/\s+/g, ""), m = (e) => {
9
- let t = e.signingCert ? `
10
- <KeyDescriptor use="signing">
11
- <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
12
- <X509Data><X509Certificate>${h(p(e.signingCert))}</X509Certificate></X509Data>
13
- </KeyInfo>
14
- </KeyDescriptor>` : "", n = e.sloUrl ? `
15
- <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="${h(e.sloUrl)}"/>` : "";
16
- return `<?xml version="1.0"?>
17
- <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="${h(e.entityId)}">
18
- <SPSSODescriptor AuthnRequestsSigned="${e.authnRequestsSigned ? "true" : "false"}" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">${t}${n}
19
- <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress</NameIDFormat>
20
- <AssertionConsumerService index="0" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="${h(e.acsUrl)}"/>
21
- </SPSSODescriptor>
22
- </EntityDescriptor>`;
23
- }, h = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"), g = (e) => {
24
- let t = e.attributes ?? {}, n = (...n) => {
25
- for (let r of n) {
26
- let n = e[r] ?? t[r];
27
- if (typeof n == "string" && n.length > 0) return n;
28
- if (Array.isArray(n) && typeof n[0] == "string") return n[0];
29
- }
30
- return null;
31
- }, r = String(e.nameID ?? e.nameId ?? n("nameID") ?? "");
32
- return {
33
- nameId: r,
34
- email: n("email", "mail", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress") ?? (r.includes("@") ? r : null),
35
- displayName: n("displayName", "name", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "cn"),
36
- attributes: t
37
- };
38
- }, _ = "_voltro_saml_replay", v = d(_, {
39
- id: u({ prefix: "samlrq" }),
40
- requestId: f().unique(),
41
- instant: f(),
42
- createdAtMs: f()
43
- }).index("bySamlRequestId", ["requestId"]), y = [v], b = (e, t) => ({
44
- kind: "binary",
45
- op: "eq",
46
- column: e,
47
- value: t
48
- }), x = (e, t = 6e5) => {
49
- let n = async (t) => (await e.query({
50
- table: _,
51
- predicate: b("requestId", t),
52
- order: [{
53
- column: "id",
54
- direction: "asc"
55
- }],
56
- take: 1,
57
- skip: void 0,
58
- projection: void 0
59
- }))[0], r = (e, n) => {
60
- let r = Number(e.createdAtMs);
61
- return Number.isFinite(r) && n - r > t;
62
- };
63
- return {
64
- saveAsync: async (t, n) => {
65
- let r = Date.now();
66
- try {
67
- return await e.insert(_, {
68
- requestId: t,
69
- instant: n,
70
- createdAtMs: String(r)
71
- }), {
72
- value: n,
73
- createdAt: r
74
- };
75
- } catch {
76
- return null;
77
- }
78
- },
79
- getAsync: async (t) => {
80
- let i = await n(t);
81
- return i ? r(i, Date.now()) ? (await e.delete(_, String(i.id)).catch(() => void 0), null) : String(i.instant) : null;
82
- },
83
- removeAsync: async (t) => {
84
- if (t == null) return null;
85
- let r = await n(t);
86
- return r ? (await e.delete(_, String(r.id)).catch(() => void 0), t) : null;
87
- }
88
- };
89
- }, S = (e = 6e5) => {
90
- let t = /* @__PURE__ */ new Map();
91
- return {
92
- saveAsync: async (e, n) => {
93
- if (t.has(e)) return null;
94
- let r = {
95
- value: n,
96
- createdAt: Date.now()
97
- };
98
- return t.set(e, r), r;
99
- },
100
- getAsync: async (n) => {
101
- let r = t.get(n);
102
- return r ? Date.now() - r.createdAt > e ? (t.delete(n), null) : r.value : null;
103
- },
104
- removeAsync: async (e) => e == null || !t.has(e) ? null : (t.delete(e), e)
105
- };
106
- }, C = "_voltro_saml_logout", w = d(C, {
107
- id: u({ prefix: "samllo" }),
108
- sloKey: f().unique(),
109
- nameId: f(),
110
- nameIdFormat: f(),
111
- sessionIndex: f(),
112
- createdAtMs: f()
113
- }).index("bySamlSloKey", ["sloKey"]), T = [w], E = (e, t) => ({
114
- kind: "binary",
115
- op: "eq",
116
- column: e,
117
- value: t
118
- }), D = (e) => ({
119
- nameId: String(e.nameId ?? ""),
120
- ...e.nameIdFormat ? { nameIdFormat: String(e.nameIdFormat) } : {},
121
- ...e.sessionIndex ? { sessionIndex: String(e.sessionIndex) } : {}
122
- }), O = (e, t = 6048e5) => {
123
- let n = async (t) => (await e.query({
124
- table: C,
125
- predicate: E("sloKey", t),
126
- order: [{
127
- column: "id",
128
- direction: "asc"
129
- }],
130
- take: 1,
131
- skip: void 0,
132
- projection: void 0
133
- }))[0], r = (e, n) => {
134
- let r = Number(e.createdAtMs);
135
- return Number.isFinite(r) && n - r > t;
136
- }, i = async (t) => {
137
- await e.delete(C, String(t.id)).catch(() => void 0);
138
- };
139
- return {
140
- save: async (t) => {
141
- let r = await n(t.sloKey);
142
- r && await i(r), await e.insert(C, {
143
- sloKey: t.sloKey,
144
- nameId: t.nameId,
145
- nameIdFormat: t.nameIdFormat ?? "",
146
- sessionIndex: t.sessionIndex ?? "",
147
- createdAtMs: String(Date.now())
148
- });
149
- },
150
- load: async (e) => {
151
- let t = await n(e);
152
- return t ? r(t, Date.now()) ? (await i(t), null) : D(t) : null;
153
- },
154
- remove: async (e) => {
155
- let t = await n(e);
156
- t && await i(t);
157
- }
158
- };
159
- }, k = (e = 6048e5) => {
160
- let t = /* @__PURE__ */ new Map();
161
- return {
162
- save: async (e) => {
163
- t.set(e.sloKey, {
164
- subject: {
165
- nameId: e.nameId,
166
- ...e.nameIdFormat ? { nameIdFormat: e.nameIdFormat } : {},
167
- ...e.sessionIndex ? { sessionIndex: e.sessionIndex } : {}
168
- },
169
- createdAt: Date.now()
170
- });
171
- },
172
- load: async (n) => {
173
- let r = t.get(n);
174
- return r ? Date.now() - r.createdAt > e ? (t.delete(n), null) : r.subject : null;
175
- },
176
- remove: async (e) => {
177
- t.delete(e);
178
- }
179
- };
180
- }, A = class extends t.TaggedError()("SamlMetadataFetchError", {
181
- url: t.String,
182
- reason: t.String
183
- }) {}, j = class extends t.TaggedError()("SamlMetadataParseError", {
184
- url: t.String,
185
- reason: t.String
186
- }) {}, M = "urn:oasis:names:tc:SAML:2.0:metadata", N = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", P = (e) => e.slice(e.indexOf(":") + 1), F = (e, t) => (RegExp(`\\b${t}\\s*=\\s*"([^"]*)"`).exec(e) ?? RegExp(`\\b${t}\\s*=\\s*'([^']*)'`).exec(e))?.[1], I = (e, t) => {
187
- let n = [], r = /<([A-Za-z][\w.:-]*)\b([^>]*)>/g, i;
188
- for (; (i = r.exec(e)) !== null;) P(i[1]) === t && n.push(i[0]);
189
- return n;
190
- }, L = (e) => {
191
- let t = [], n = /<([A-Za-z][\w.:-]*KeyDescriptor)\b([^>]*)>/g, r;
192
- for (; (r = n.exec(e)) !== null;) {
193
- if (P(r[1]) !== "KeyDescriptor") continue;
194
- let n = RegExp(`</${r[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}>`), i = e.slice(r.index + r[0].length), a = n.exec(i), o = a ? i.slice(0, a.index) : i;
195
- t.push({
196
- use: F(r[0], "use"),
197
- body: o
198
- });
199
- }
200
- return t;
201
- }, R = (e) => {
202
- let t = L(e), n = t.find((e) => e.use === "signing") ?? t.find((e) => e.use === void 0) ?? t[0];
203
- if (!n) return;
204
- let r = /<[^>]*X509Certificate[^>]*>([\s\S]*?)<\/[^>]*X509Certificate>/.exec(n.body)?.[1]?.replace(/\s+/g, "");
205
- return r && r.length > 0 ? r : void 0;
206
- }, z = (t, n) => e.gen(function* () {
207
- if (!t.includes(M) && !/EntityDescriptor/.test(t)) return yield* e.fail(new j({
208
- url: n,
209
- reason: "not-saml-metadata"
210
- }));
211
- let r = I(t, "SingleSignOnService"), i = r.find((e) => F(e, "Binding") === N) ?? r[0], a = i ? F(i, "Location") : void 0;
212
- if (!a) return yield* e.fail(new j({
213
- url: n,
214
- reason: "no-entryPoint"
215
- }));
216
- let o = R(t);
217
- if (!o) return yield* e.fail(new j({
218
- url: n,
219
- reason: "no-signing-cert"
220
- }));
221
- let s = I(t, "SingleLogoutService"), c = s.find((e) => F(e, "Binding") === N) ?? s[0], l = c ? F(c, "Location") : void 0, u = I(t, "EntityDescriptor")[0], d = u ? F(u, "entityID") : void 0;
222
- return {
223
- entryPoint: a,
224
- idpCert: o,
225
- ...l ? { logoutUrl: l } : {},
226
- ...d ? { issuer: d } : {}
227
- };
228
- }), B = (t) => e.gen(function* () {
229
- let n = yield* (yield* r.HttpClient).get(t).pipe(e.mapError(() => new A({
230
- url: t,
231
- reason: "network"
232
- })));
233
- if (n.status < 200 || n.status >= 300) return yield* e.fail(new A({
234
- url: t,
235
- reason: `status ${n.status}`
236
- }));
237
- let i = yield* n.text.pipe(e.mapError(() => new A({
238
- url: t,
239
- reason: "body"
240
- })));
241
- return i.trim() ? yield* z(i, t) : yield* e.fail(new A({
242
- url: t,
243
- reason: "empty-body"
244
- }));
245
- }), V = (e, t) => typeof e != "string" || e[0] !== "/" || /[\u0000-]/.test(e) ? t : e === "/" ? e : e[1] === "/" || e[1] === "\\" ? t : e, H = (e, t, n = "text/plain") => ({
1
+ import { extractProfile as e, spMetadataXml as t } from "./saml.js";
2
+ import { REPLAY_CACHE_TABLE as n, dataStoreReplayCache as r, memoryReplayCache as i, samlReplayTable as a, samlReplayTables as o } from "./saml-cache.js";
3
+ import { LOGOUT_SESSION_TABLE as s, dataStoreLogoutStore as c, memoryLogoutStore as l, samlLogoutTable as u, samlLogoutTables as d } from "./saml-logout.js";
4
+ import { SamlMetadataFetchError as f, SamlMetadataParseError as p } from "./saml-errors.js";
5
+ import { fetchIdpMetadata as m, parseIdpMetadata as h } from "./saml-metadata.js";
6
+ import { Effect as g } from "effect";
7
+ import { FetchHttpClient as _ } from "effect/http";
8
+ import { randomBytes as v } from "node:crypto";
9
+ import { definePlugin as y, pluginInstanceName as b } from "@voltro/protocol";
10
+ import { buildSetCookie as x, readCookie as S, signSession as C } from "@voltro/protocol/session";
11
+ //#region src/index.ts
12
+ var w = (e, t) => typeof e != "string" || e[0] !== "/" || /[\u0000-]/.test(e) ? t : e === "/" ? e : e[1] === "/" || e[1] === "\\" ? t : e, T = (e, t, n = "text/plain") => ({
246
13
  status: e,
247
14
  body: t,
248
15
  contentType: n
249
- }), U = (e, t) => {
16
+ }), E = (e, t) => {
250
17
  let n = { location: e };
251
18
  return t !== void 0 && t.length > 0 && (n["set-cookie"] = t), {
252
19
  status: 302,
253
20
  headers: n
254
21
  };
255
- }, W = ":slo", G = "@voltro/plugin-sso-saml", K = (t) => {
256
- if (!t.sessionSecret?.trim()) throw Error("@voltro/plugin-sso-saml: `sessionSecret` must be a non-empty string");
257
- if (!t.idp.metadataUrl && (!t.idp.entryPoint || !t.idp.idpCert)) throw Error("@voltro/plugin-sso-saml: provide idp.entryPoint + idp.idpCert, or idp.metadataUrl to load them from the IdP metadata");
258
- let r = t.basePath ?? "/saml", u = t.cookieName ?? "voltro:session", d = `${u}${W}`, f = t.sessionTtlSeconds ?? 604800, p = t.successRedirect ?? "/", h = t.logoutRedirect ?? "/", _ = o({
259
- base: G,
260
- alias: t.alias,
261
- instance: t.name
262
- }), v = t.idp.metadataRefreshMs ?? 432e5, b = t.replayProtection ?? { store: !0 }, C = typeof b == "object" && b.store === !0, w = typeof b == "object" ? b.ttlMs ?? 6e5 : 6e5, E = typeof t.sloStore == "object" && t.sloStore.store === !0, D = C || E, A, j, M, N, P, F = 0, I, L = () => {
263
- if (b) {
264
- if (C) {
265
- if (!A) throw Error("@voltro/plugin-sso-saml: replayProtection { store: true } but the DataStore is not bound yet");
266
- return x(A, w);
22
+ }, D = ":slo", O = "@voltro/plugin-sso-saml", k = (n) => {
23
+ if (!n.sessionSecret?.trim()) throw Error("@voltro/plugin-sso-saml: `sessionSecret` must be a non-empty string");
24
+ if (!n.idp.metadataUrl && (!n.idp.entryPoint || !n.idp.idpCert)) throw Error("@voltro/plugin-sso-saml: provide idp.entryPoint + idp.idpCert, or idp.metadataUrl to load them from the IdP metadata");
25
+ let a = n.basePath ?? "/saml", s = n.cookieName ?? "voltro:session", u = `${s}${D}`, f = n.sessionTtlSeconds ?? 604800, p = n.successRedirect ?? "/", h = n.logoutRedirect ?? "/", k = b({
26
+ base: O,
27
+ alias: n.alias,
28
+ instance: n.name
29
+ }), A = n.idp.metadataRefreshMs ?? 432e5, j = n.replayProtection ?? { store: !0 }, M = typeof j == "object" && j.store === !0, N = typeof j == "object" ? j.ttlMs ?? 6e5 : 6e5, P = typeof n.sloStore == "object" && n.sloStore.store === !0, F = M || P, I, L, R, z, B, V = 0, H, U = () => {
30
+ if (j) {
31
+ if (M) {
32
+ if (!I) throw Error("@voltro/plugin-sso-saml: replayProtection { store: true } but the DataStore is not bound yet");
33
+ return r(I, N);
267
34
  }
268
- return S(w);
35
+ return i(N);
269
36
  }
270
- }, R = () => {
271
- if (I) return I;
272
- if (E) {
273
- if (!A) throw Error("@voltro/plugin-sso-saml: sloStore { store: true } but the DataStore is not bound yet");
274
- I = O(A, f * 1e3);
275
- } else I = k(f * 1e3);
276
- return I;
277
- }, z = async () => {
278
- let r = t.idp.metadataUrl;
279
- if (r) {
280
- let i = v > 0 && Date.now() - F > v;
281
- if (!P || i) {
282
- let t = await e.runPromiseExit(B(r).pipe(e.provide(n.layer)));
283
- t._tag === "Success" && (P = t.value, F = Date.now());
37
+ }, W = () => {
38
+ if (H) return H;
39
+ if (P) {
40
+ if (!I) throw Error("@voltro/plugin-sso-saml: sloStore { store: true } but the DataStore is not bound yet");
41
+ H = c(I, f * 1e3);
42
+ } else H = l(f * 1e3);
43
+ return H;
44
+ }, G = async () => {
45
+ let e = n.idp.metadataUrl;
46
+ if (e) {
47
+ let t = A > 0 && Date.now() - V > A;
48
+ if (!B || t) {
49
+ let t = await g.runPromiseExit(m(e).pipe(g.provide(_.layer)));
50
+ t._tag === "Success" && (B = t.value, V = Date.now());
284
51
  }
285
- if (P) return {
286
- entryPoint: P.entryPoint,
287
- idpCert: P.idpCert,
288
- ...t.idp.logoutUrl ?? P.logoutUrl ? { logoutUrl: t.idp.logoutUrl ?? P.logoutUrl } : {},
289
- ...t.idp.issuer ?? P.issuer ? { idpIssuer: t.idp.issuer ?? P.issuer } : {}
52
+ if (B) return {
53
+ entryPoint: B.entryPoint,
54
+ idpCert: B.idpCert,
55
+ ...n.idp.logoutUrl ?? B.logoutUrl ? { logoutUrl: n.idp.logoutUrl ?? B.logoutUrl } : {},
56
+ ...n.idp.issuer ?? B.issuer ? { idpIssuer: n.idp.issuer ?? B.issuer } : {}
290
57
  };
291
58
  }
292
59
  return {
293
- entryPoint: t.idp.entryPoint,
294
- idpCert: t.idp.idpCert,
295
- ...t.idp.logoutUrl ? { logoutUrl: t.idp.logoutUrl } : {},
296
- ...t.idp.issuer ? { idpIssuer: t.idp.issuer } : {}
60
+ entryPoint: n.idp.entryPoint,
61
+ idpCert: n.idp.idpCert,
62
+ ...n.idp.logoutUrl ? { logoutUrl: n.idp.logoutUrl } : {},
63
+ ...n.idp.issuer ? { idpIssuer: n.idp.issuer } : {}
297
64
  };
298
- }, K = (e, n) => ({
299
- callbackUrl: t.sp.acsUrl,
65
+ }, K = (e, t) => ({
66
+ callbackUrl: n.sp.acsUrl,
300
67
  entryPoint: e.entryPoint,
301
- issuer: t.sp.entityId,
68
+ issuer: n.sp.entityId,
302
69
  idpCert: e.idpCert,
303
70
  ...e.idpIssuer ? { idpIssuer: e.idpIssuer } : {},
304
71
  ...e.logoutUrl ? { logoutUrl: e.logoutUrl } : {},
305
72
  wantAssertionsSigned: !0,
306
- wantAuthnResponseSigned: t.wantAuthnResponseSigned ?? !1,
307
- ...t.decryptionPvk ? { decryptionPvk: t.decryptionPvk } : {},
308
- ...t.privateKey ? { privateKey: t.privateKey } : {},
309
- ...t.signingCert ? { publicCert: t.signingCert } : {},
310
- ...t.acceptedClockSkewMs === void 0 ? {} : { acceptedClockSkewMs: t.acceptedClockSkewMs },
311
- ...n ? {
73
+ wantAuthnResponseSigned: n.wantAuthnResponseSigned ?? !1,
74
+ ...n.decryptionPvk ? { decryptionPvk: n.decryptionPvk } : {},
75
+ ...n.privateKey ? { privateKey: n.privateKey } : {},
76
+ ...n.signingCert ? { publicCert: n.signingCert } : {},
77
+ ...n.acceptedClockSkewMs === void 0 ? {} : { acceptedClockSkewMs: n.acceptedClockSkewMs },
78
+ ...t ? {
312
79
  validateInResponseTo: "always",
313
- cacheProvider: n,
314
- requestIdExpirationPeriodMs: w
80
+ cacheProvider: t,
81
+ requestIdExpirationPeriodMs: N
315
82
  } : {}
316
83
  }), q = async () => {
317
- let e = !!t.idp.metadataUrl && v > 0 && Date.now() - F > v;
318
- if (j && !e) return j;
319
- if (M) return;
320
- N = void 0;
321
- let n;
84
+ let e = !!n.idp.metadataUrl && A > 0 && Date.now() - V > A;
85
+ if (L && !e) return L;
86
+ if (R) return;
87
+ z = void 0;
88
+ let t;
322
89
  try {
323
- n = await import("@node-saml/node-saml");
90
+ t = await import("@node-saml/node-saml");
324
91
  } catch {
325
- M = "SAML SSO requires the optional dependency @node-saml/node-saml — run `pnpm add @node-saml/node-saml`";
92
+ R = "SAML SSO requires the optional dependency @node-saml/node-saml — run `pnpm add @node-saml/node-saml`";
326
93
  return;
327
94
  }
328
95
  try {
329
- let e = await z(), t = L();
330
- return j = new n.SAML(K(e, t)), j;
96
+ let e = await G(), n = U();
97
+ return L = new t.SAML(K(e, n)), L;
331
98
  } catch (e) {
332
- N = `@voltro/plugin-sso-saml: could not construct the SAML instance — ${String(e?.message ?? e)}`;
99
+ z = `@voltro/plugin-sso-saml: could not construct the SAML instance — ${String(e?.message ?? e)}`;
333
100
  return;
334
101
  }
335
102
  }, J = (() => {
336
103
  try {
337
- return new URL(`${r}/slo`, t.sp.acsUrl).toString();
104
+ return new URL(`${a}/slo`, n.sp.acsUrl).toString();
338
105
  } catch {
339
106
  return;
340
107
  }
341
108
  })(), Y = {
342
- ...t.sp,
343
- authnRequestsSigned: !!t.privateKey,
344
- ...t.signingCert ? { signingCert: t.signingCert } : {},
109
+ ...n.sp,
110
+ authnRequestsSigned: !!n.privateKey,
111
+ ...n.signingCert ? { signingCert: n.signingCert } : {},
345
112
  ...J ? { sloUrl: J } : {}
346
- }, X = (e) => c(e.headers.cookie ?? e.headers.Cookie, d), Z = () => [s(u, "", {
113
+ }, X = (e) => S(e.headers.cookie ?? e.headers.Cookie, u), Z = () => [x(s, "", {
347
114
  maxAgeSeconds: 0,
348
115
  httpOnly: !0,
349
116
  sameSite: "lax"
350
- }), s(d, "", {
117
+ }), x(u, "", {
351
118
  maxAgeSeconds: 0,
352
119
  httpOnly: !0,
353
120
  sameSite: "lax"
354
121
  })];
355
- return a({
356
- name: _,
357
- baseName: G,
122
+ return y({
123
+ name: k,
124
+ baseName: O,
358
125
  description: "SAML 2.0 SSO — SP-initiated login + Single Logout, ACS, metadata.",
359
- permissions: D ? ["store:write"] : [],
126
+ permissions: F ? ["store:write"] : [],
360
127
  httpRoutes: [{
361
128
  method: "*",
362
- path: r,
129
+ path: a,
363
130
  originGuard: "exempt",
364
- handle: async (e) => {
365
- let n = e.path.slice(r.length).replace(/\/+$/, ""), a = e.method.toUpperCase();
366
- if (n === "/metadata" && a === "GET") return H(200, m(Y), "application/xml");
367
- let o = await q();
368
- if (!o) return H(500, M ?? N ?? "SAML not available");
369
- if (n === "/login" && a === "GET") {
370
- let t = V(new URLSearchParams(e.query).get("returnTo"), p);
371
- return U(await o.getAuthorizeUrlAsync(t, void 0, {}));
131
+ handle: async (r) => {
132
+ let i = r.path.slice(a.length).replace(/\/+$/, ""), o = r.method.toUpperCase();
133
+ if (i === "/metadata" && o === "GET") return T(200, t(Y), "application/xml");
134
+ let c = await q();
135
+ if (!c) return T(500, R ?? z ?? "SAML not available");
136
+ if (i === "/login" && o === "GET") {
137
+ let e = w(new URLSearchParams(r.query).get("returnTo"), p);
138
+ return E(await c.getAuthorizeUrlAsync(e, void 0, {}));
372
139
  }
373
- if (n === "/acs" && a === "POST") {
374
- let n = new URLSearchParams(new TextDecoder().decode(e.rawBody)), r = n.get("SAMLResponse"), a = V(n.get("RelayState"), p);
375
- if (!r) return H(400, "missing SAMLResponse");
376
- let c;
140
+ if (i === "/acs" && o === "POST") {
141
+ let t = new URLSearchParams(new TextDecoder().decode(r.rawBody)), i = t.get("SAMLResponse"), a = w(t.get("RelayState"), p);
142
+ if (!i) return T(400, "missing SAMLResponse");
143
+ let o;
377
144
  try {
378
- ({profile: c} = await o.validatePostResponseAsync({ SAMLResponse: r }));
145
+ ({profile: o} = await c.validatePostResponseAsync({ SAMLResponse: i }));
379
146
  } catch (e) {
380
- return H(401, `SAML assertion rejected: ${String(e?.message ?? e)}`);
147
+ return T(401, `SAML assertion rejected: ${String(e?.message ?? e)}`);
381
148
  }
382
- if (!c) return H(401, "no SAML profile");
383
- let m = await t.onLogin(g(c));
384
- if (!m) return H(403, "login rejected");
385
- let h = l(m, t.sessionSecret, { ttlSeconds: f }), _ = s(u, h, {
149
+ if (!o) return T(401, "no SAML profile");
150
+ let l = await n.onLogin(e(o));
151
+ if (!l) return T(403, "login rejected");
152
+ let d = C(l, n.sessionSecret, { ttlSeconds: f }), m = x(s, d, {
386
153
  maxAgeSeconds: f,
387
154
  httpOnly: !0,
388
155
  sameSite: "lax"
389
- }), v = String(c.nameID ?? c.nameId ?? ""), y = [_];
390
- if (v) {
391
- let e = i(24).toString("base64url");
392
- await R().save({
156
+ }), h = String(o.nameID ?? o.nameId ?? ""), g = [m];
157
+ if (h) {
158
+ let e = v(24).toString("base64url");
159
+ await W().save({
393
160
  sloKey: e,
394
- nameId: v,
395
- ...c.nameIDFormat ? { nameIdFormat: String(c.nameIDFormat) } : {},
396
- ...c.sessionIndex ? { sessionIndex: String(c.sessionIndex) } : {}
397
- }), y.push(s(d, e, {
161
+ nameId: h,
162
+ ...o.nameIDFormat ? { nameIdFormat: String(o.nameIDFormat) } : {},
163
+ ...o.sessionIndex ? { sessionIndex: String(o.sessionIndex) } : {}
164
+ }), g.push(x(u, e, {
398
165
  maxAgeSeconds: f,
399
166
  httpOnly: !0,
400
167
  sameSite: "lax"
401
168
  }));
402
169
  }
403
- return U(a, y);
170
+ return E(a, g);
404
171
  }
405
- if (n === "/logout" && a === "GET") {
406
- let t = V(new URLSearchParams(e.query).get("returnTo"), h), n = X(e), r = n ? await R().load(n) : null;
407
- if (!r) return U(t, Z());
172
+ if (i === "/logout" && o === "GET") {
173
+ let e = w(new URLSearchParams(r.query).get("returnTo"), h), t = X(r), n = t ? await W().load(t) : null;
174
+ if (!n) return E(e, Z());
408
175
  let i = {
409
- nameID: r.nameId,
410
- ...r.nameIdFormat ? { nameIDFormat: r.nameIdFormat } : {},
411
- ...r.sessionIndex ? { sessionIndex: r.sessionIndex } : {}
176
+ nameID: n.nameId,
177
+ ...n.nameIdFormat ? { nameIDFormat: n.nameIdFormat } : {},
178
+ ...n.sessionIndex ? { sessionIndex: n.sessionIndex } : {}
412
179
  }, a;
413
180
  try {
414
- a = await o.getLogoutUrlAsync(i, t, {});
181
+ a = await c.getLogoutUrlAsync(i, e, {});
415
182
  } catch (e) {
416
- return H(500, `SAML logout request failed: ${String(e?.message ?? e)}`);
183
+ return T(500, `SAML logout request failed: ${String(e?.message ?? e)}`);
417
184
  }
418
- return n && await R().remove(n), U(a, Z());
185
+ return t && await W().remove(t), E(a, Z());
419
186
  }
420
- if (n === "/slo" && (a === "GET" || a === "POST")) {
421
- let t = a === "GET" ? e.query : new TextDecoder().decode(e.rawBody), n = new URLSearchParams(t), r = {};
422
- for (let [e, t] of n) r[e] = t;
423
- let i = V(n.get("RelayState"), h);
424
- if (!n.has("Signature")) return H(401, "SAML logout rejected: the logout message is not signed");
425
- let s;
187
+ if (i === "/slo" && (o === "GET" || o === "POST")) {
188
+ let e = o === "GET" ? r.query : new TextDecoder().decode(r.rawBody), t = new URLSearchParams(e), n = {};
189
+ for (let [e, r] of t) n[e] = r;
190
+ let i = w(t.get("RelayState"), h);
191
+ if (!t.has("Signature")) return T(401, "SAML logout rejected: the logout message is not signed");
192
+ let a;
426
193
  try {
427
- s = await o.validateRedirectAsync(r, t);
194
+ a = await c.validateRedirectAsync(n, e);
428
195
  } catch (e) {
429
- return H(401, `SAML logout rejected: ${String(e?.message ?? e)}`);
196
+ return T(401, `SAML logout rejected: ${String(e?.message ?? e)}`);
430
197
  }
431
- if (n.has("SAMLResponse")) return U(i, Z());
432
- let c = X(e);
433
- c && await R().remove(c);
198
+ if (t.has("SAMLResponse")) return E(i, Z());
199
+ let s = X(r);
200
+ s && await W().remove(s);
434
201
  try {
435
- return U(await o.getLogoutResponseUrlAsync(s.profile ?? {}, i, {}, !0), Z());
202
+ return E(await c.getLogoutResponseUrlAsync(a.profile ?? {}, i, {}, !0), Z());
436
203
  } catch {
437
- return U(i, Z());
204
+ return E(i, Z());
438
205
  }
439
206
  }
440
- return H(404, "unknown SAML endpoint");
207
+ return T(404, "unknown SAML endpoint");
441
208
  }
442
209
  }],
443
- ...D ? { extendSchema: { tables: [...C ? y : [], ...E ? T : []] } } : {},
210
+ ...F ? { extendSchema: { tables: [...M ? o : [], ...P ? d : []] } } : {},
444
211
  bindDataStore: (e) => {
445
- A = e;
212
+ I = e;
446
213
  },
447
- onActivate: (n) => e.sync(() => {
448
- n.logger.info("saml sso active", {
449
- basePath: r,
450
- entityId: t.sp.entityId,
451
- idpSource: t.idp.metadataUrl ? "metadata-url" : "static",
452
- replayProtection: b ? C ? "store" : "memory" : "off",
453
- sloStore: E ? "store" : "memory",
454
- requestSigning: t.privateKey ? "on" : "off",
455
- encryptedAssertions: t.decryptionPvk ? "on" : "off",
214
+ onActivate: (e) => g.sync(() => {
215
+ e.logger.info("saml sso active", {
216
+ basePath: a,
217
+ entityId: n.sp.entityId,
218
+ idpSource: n.idp.metadataUrl ? "metadata-url" : "static",
219
+ replayProtection: j ? M ? "store" : "memory" : "off",
220
+ sloStore: P ? "store" : "memory",
221
+ requestSigning: n.privateKey ? "on" : "off",
222
+ encryptedAssertions: n.decryptionPvk ? "on" : "off",
456
223
  wantAssertionsSigned: "required",
457
- wantAuthnResponseSigned: t.wantAuthnResponseSigned ?? !1 ? "required" : "optional"
458
- }), b && !C && n.logger.warn("saml sso: replayProtection uses an IN-PROCESS cache — correct for ONE replica only. Under >1 replica a login and its ACS can land on different processes (InResponseTo fails) and a cross-replica replay is not caught. Use replayProtection: { store: true } in production."), b || n.logger.warn("saml sso: replayProtection is OFF — a captured SAMLResponse can be replayed for as long as its assertion is valid. This also ENABLES IdP-initiated SSO (the app-tile flow), which is the only reason to turn it off. If you did not mean to accept unsolicited responses, remove `replayProtection: false`."), E || n.logger.warn("saml sso: SLO state uses an IN-PROCESS store — correct for ONE replica only. Under >1 replica a login and its logout can land on different processes. Use sloStore: { store: true } in production.");
224
+ wantAuthnResponseSigned: n.wantAuthnResponseSigned ?? !1 ? "required" : "optional"
225
+ }), j && !M && e.logger.warn("saml sso: replayProtection uses an IN-PROCESS cache — correct for ONE replica only. Under >1 replica a login and its ACS can land on different processes (InResponseTo fails) and a cross-replica replay is not caught. Use replayProtection: { store: true } in production."), j || e.logger.warn("saml sso: replayProtection is OFF — a captured SAMLResponse can be replayed for as long as its assertion is valid. This also ENABLES IdP-initiated SSO (the app-tile flow), which is the only reason to turn it off. If you did not mean to accept unsolicited responses, remove `replayProtection: false`."), P || e.logger.warn("saml sso: SLO state uses an IN-PROCESS store — correct for ONE replica only. Under >1 replica a login and its logout can land on different processes. Use sloStore: { store: true } in production.");
459
226
  })
460
227
  });
461
228
  };
462
229
  //#endregion
463
- export { C as LOGOUT_SESSION_TABLE, _ as REPLAY_CACHE_TABLE, A as SamlMetadataFetchError, j as SamlMetadataParseError, O as dataStoreLogoutStore, x as dataStoreReplayCache, g as extractProfile, B as fetchIdpMetadata, k as memoryLogoutStore, S as memoryReplayCache, z as parseIdpMetadata, w as samlLogoutTable, T as samlLogoutTables, v as samlReplayTable, y as samlReplayTables, K as samlSsoPlugin, m as spMetadataXml };
230
+ export { s as LOGOUT_SESSION_TABLE, n as REPLAY_CACHE_TABLE, f as SamlMetadataFetchError, p as SamlMetadataParseError, c as dataStoreLogoutStore, r as dataStoreReplayCache, e as extractProfile, m as fetchIdpMetadata, l as memoryLogoutStore, i as memoryReplayCache, h as parseIdpMetadata, u as samlLogoutTable, d as samlLogoutTables, a as samlReplayTable, o as samlReplayTables, k as samlSsoPlugin, t as spMetadataXml };