@voltro/plugin-sso-saml 0.1.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 ADDED
@@ -0,0 +1,443 @@
1
+ import { Effect as e, Schema as t } from "effect";
2
+ import { FetchHttpClient as n, HttpClient as r } from "@effect/platform";
3
+ import { randomBytes as i } from "node:crypto";
4
+ import { definePlugin as a } from "@voltro/protocol";
5
+ import { buildSetCookie as o, readCookie as s, signSession as c } from "@voltro/protocol/session";
6
+ import { id as l, table as u, text as d } from "@voltro/database";
7
+ //#region src/saml.ts
8
+ var f = (e) => e.replace(/-----(BEGIN|END) CERTIFICATE-----/g, "").replace(/\s+/g, ""), p = (e) => {
9
+ let t = e.signingCert ? `
10
+ <KeyDescriptor use="signing">
11
+ <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
12
+ <X509Data><X509Certificate>${m(f(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="${m(e.sloUrl)}"/>` : "";
16
+ return `<?xml version="1.0"?>
17
+ <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="${m(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="${m(e.acsUrl)}"/>
21
+ </SPSSODescriptor>
22
+ </EntityDescriptor>`;
23
+ }, m = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"), h = (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
+ }, g = "_voltro_saml_replay", _ = u(g, {
39
+ id: l({ prefix: "samlrq" }),
40
+ requestId: d().unique(),
41
+ instant: d(),
42
+ createdAtMs: d()
43
+ }).index("bySamlRequestId", ["requestId"]), v = [_], y = (e, t) => ({
44
+ kind: "binary",
45
+ op: "eq",
46
+ column: e,
47
+ value: t
48
+ }), b = (e, t = 10 * 6e4) => {
49
+ let n = async (t) => (await e.query({
50
+ table: g,
51
+ predicate: y("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(g, {
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(g, 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(g, String(r.id)).catch(() => void 0), t) : null;
87
+ }
88
+ };
89
+ }, x = (e = 10 * 6e4) => {
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
+ }, S = "_voltro_saml_logout", C = u(S, {
107
+ id: l({ prefix: "samllo" }),
108
+ sloKey: d().unique(),
109
+ nameId: d(),
110
+ nameIdFormat: d(),
111
+ sessionIndex: d(),
112
+ createdAtMs: d()
113
+ }).index("bySamlSloKey", ["sloKey"]), w = [C], T = (e, t) => ({
114
+ kind: "binary",
115
+ op: "eq",
116
+ column: e,
117
+ value: t
118
+ }), E = (e) => ({
119
+ nameId: String(e.nameId ?? ""),
120
+ ...e.nameIdFormat ? { nameIdFormat: String(e.nameIdFormat) } : {},
121
+ ...e.sessionIndex ? { sessionIndex: String(e.sessionIndex) } : {}
122
+ }), D = (e, t = 10080 * 6e4) => {
123
+ let n = async (t) => (await e.query({
124
+ table: S,
125
+ predicate: T("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(S, 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(S, {
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) : E(t) : null;
153
+ },
154
+ remove: async (e) => {
155
+ let t = await n(e);
156
+ t && await i(t);
157
+ }
158
+ };
159
+ }, O = (e = 10080 * 6e4) => {
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
+ }, k = class extends t.TaggedError()("SamlMetadataFetchError", {
181
+ url: t.String,
182
+ reason: t.String
183
+ }) {}, A = class extends t.TaggedError()("SamlMetadataParseError", {
184
+ url: t.String,
185
+ reason: t.String
186
+ }) {}, j = "urn:oasis:names:tc:SAML:2.0:metadata", M = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", N = (e) => e.slice(e.indexOf(":") + 1), P = (e, t) => (RegExp(`\\b${t}\\s*=\\s*"([^"]*)"`).exec(e) ?? RegExp(`\\b${t}\\s*=\\s*'([^']*)'`).exec(e))?.[1], F = (e, t) => {
187
+ let n = [], r = /<([A-Za-z][\w.:-]*)\b([^>]*)>/g, i;
188
+ for (; (i = r.exec(e)) !== null;) N(i[1]) === t && n.push(i[0]);
189
+ return n;
190
+ }, I = (e) => {
191
+ let t = [], n = /<([A-Za-z][\w.:-]*KeyDescriptor)\b([^>]*)>/g, r;
192
+ for (; (r = n.exec(e)) !== null;) {
193
+ if (N(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: P(r[0], "use"),
197
+ body: o
198
+ });
199
+ }
200
+ return t;
201
+ }, L = (e) => {
202
+ let t = I(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
+ }, R = (t, n) => e.gen(function* () {
207
+ if (!t.includes(j) && !/EntityDescriptor/.test(t)) return yield* e.fail(new A({
208
+ url: n,
209
+ reason: "not-saml-metadata"
210
+ }));
211
+ let r = F(t, "SingleSignOnService"), i = r.find((e) => P(e, "Binding") === M) ?? r[0], a = i ? P(i, "Location") : void 0;
212
+ if (!a) return yield* e.fail(new A({
213
+ url: n,
214
+ reason: "no-entryPoint"
215
+ }));
216
+ let o = L(t);
217
+ if (!o) return yield* e.fail(new A({
218
+ url: n,
219
+ reason: "no-signing-cert"
220
+ }));
221
+ let s = F(t, "SingleLogoutService"), c = s.find((e) => P(e, "Binding") === M) ?? s[0], l = c ? P(c, "Location") : void 0, u = F(t, "EntityDescriptor")[0], d = u ? P(u, "entityID") : void 0;
222
+ return {
223
+ entryPoint: a,
224
+ idpCert: o,
225
+ ...l ? { logoutUrl: l } : {},
226
+ ...d ? { issuer: d } : {}
227
+ };
228
+ }), z = (t) => e.gen(function* () {
229
+ let n = yield* (yield* r.HttpClient).get(t).pipe(e.mapError(() => new k({
230
+ url: t,
231
+ reason: "network"
232
+ })));
233
+ if (n.status < 200 || n.status >= 300) return yield* e.fail(new k({
234
+ url: t,
235
+ reason: `status ${n.status}`
236
+ }));
237
+ let i = yield* n.text.pipe(e.mapError(() => new k({
238
+ url: t,
239
+ reason: "body"
240
+ })));
241
+ return i.trim() ? yield* R(i, t) : yield* e.fail(new k({
242
+ url: t,
243
+ reason: "empty-body"
244
+ }));
245
+ }), B = (e, t) => typeof e != "string" || e[0] !== "/" || /[-]/.test(e) ? t : e === "/" ? e : e[1] === "/" || e[1] === "\\" ? t : e, V = (e, t, n = "text/plain") => ({
246
+ status: e,
247
+ body: t,
248
+ contentType: n
249
+ }), H = (e, t) => {
250
+ let n = { location: e };
251
+ return typeof t == "string" ? n["set-cookie"] = t : Array.isArray(t) && t.length > 0 && (n["set-cookie"] = t.join(", ")), {
252
+ status: 302,
253
+ headers: n
254
+ };
255
+ }, U = ":slo", W = (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", l = t.cookieName ?? "voltro:session", u = `${l}${U}`, d = t.sessionTtlSeconds ?? 3600 * 24 * 7, f = t.successRedirect ?? "/", m = t.logoutRedirect ?? "/", g = t.name ? `@voltro/plugin-sso-saml#${t.name}` : "@voltro/plugin-sso-saml", _ = t.idp.metadataRefreshMs ?? 720 * 6e4, y = t.replayProtection, S = typeof y == "object" && y.store === !0, C = typeof y == "object" ? y.ttlMs ?? 10 * 6e4 : 10 * 6e4, T = typeof t.sloStore == "object" && t.sloStore.store === !0, E = S || T, k, A, j, M, N = 0, P, F = () => {
259
+ if (y) {
260
+ if (S) {
261
+ if (!k) throw Error("@voltro/plugin-sso-saml: replayProtection { store: true } but the DataStore is not bound yet");
262
+ return b(k, C);
263
+ }
264
+ return x(C);
265
+ }
266
+ }, I = () => {
267
+ if (P) return P;
268
+ if (T) {
269
+ if (!k) throw Error("@voltro/plugin-sso-saml: sloStore { store: true } but the DataStore is not bound yet");
270
+ P = D(k, d * 1e3);
271
+ } else P = O(d * 1e3);
272
+ return P;
273
+ }, L = async () => {
274
+ let r = t.idp.metadataUrl;
275
+ if (r) {
276
+ let i = _ > 0 && Date.now() - N > _;
277
+ if (!M || i) {
278
+ let t = await e.runPromiseExit(z(r).pipe(e.provide(n.layer)));
279
+ t._tag === "Success" && (M = t.value, N = Date.now());
280
+ }
281
+ if (M) return {
282
+ entryPoint: M.entryPoint,
283
+ idpCert: M.idpCert,
284
+ ...t.idp.logoutUrl ?? M.logoutUrl ? { logoutUrl: t.idp.logoutUrl ?? M.logoutUrl } : {},
285
+ ...t.idp.issuer ?? M.issuer ? { idpIssuer: t.idp.issuer ?? M.issuer } : {}
286
+ };
287
+ }
288
+ return {
289
+ entryPoint: t.idp.entryPoint,
290
+ idpCert: t.idp.idpCert,
291
+ ...t.idp.logoutUrl ? { logoutUrl: t.idp.logoutUrl } : {},
292
+ ...t.idp.issuer ? { idpIssuer: t.idp.issuer } : {}
293
+ };
294
+ }, R = (e, n) => ({
295
+ callbackUrl: t.sp.acsUrl,
296
+ entryPoint: e.entryPoint,
297
+ issuer: t.sp.entityId,
298
+ idpCert: e.idpCert,
299
+ ...e.idpIssuer ? { idpIssuer: e.idpIssuer } : {},
300
+ ...e.logoutUrl ? { logoutUrl: e.logoutUrl } : {},
301
+ wantAssertionsSigned: !0,
302
+ ...t.decryptionPvk ? { decryptionPvk: t.decryptionPvk } : {},
303
+ ...t.privateKey ? { privateKey: t.privateKey } : {},
304
+ ...t.signingCert ? { publicCert: t.signingCert } : {},
305
+ ...t.acceptedClockSkewMs === void 0 ? {} : { acceptedClockSkewMs: t.acceptedClockSkewMs },
306
+ ...n ? {
307
+ validateInResponseTo: "always",
308
+ cacheProvider: n,
309
+ requestIdExpirationPeriodMs: C
310
+ } : {}
311
+ }), W = async () => {
312
+ let e = !!t.idp.metadataUrl && _ > 0 && Date.now() - N > _;
313
+ if (A && !e) return A;
314
+ if (!j) try {
315
+ let e = await import("@node-saml/node-saml"), t = await L(), n = F();
316
+ return A = new e.SAML(R(t, n)), A;
317
+ } catch {
318
+ j = "SAML SSO requires the optional dependency @node-saml/node-saml — run `pnpm add @node-saml/node-saml`";
319
+ return;
320
+ }
321
+ }, G = (() => {
322
+ try {
323
+ return new URL(`${r}/slo`, t.sp.acsUrl).toString();
324
+ } catch {
325
+ return;
326
+ }
327
+ })(), K = {
328
+ ...t.sp,
329
+ authnRequestsSigned: !!t.privateKey,
330
+ ...t.signingCert ? { signingCert: t.signingCert } : {},
331
+ ...G ? { sloUrl: G } : {}
332
+ }, q = (e) => s(e.headers.cookie ?? e.headers.Cookie, u), J = () => [o(l, "", {
333
+ maxAgeSeconds: 0,
334
+ httpOnly: !0,
335
+ sameSite: "lax"
336
+ }), o(u, "", {
337
+ maxAgeSeconds: 0,
338
+ httpOnly: !0,
339
+ sameSite: "lax"
340
+ })];
341
+ return a({
342
+ name: g,
343
+ description: "SAML 2.0 SSO — SP-initiated login + Single Logout, ACS, metadata.",
344
+ permissions: E ? ["store:write"] : [],
345
+ httpRoutes: [{
346
+ method: "*",
347
+ path: r,
348
+ handle: async (e) => {
349
+ let n = e.path.slice(r.length).replace(/\/+$/, ""), a = e.method.toUpperCase();
350
+ if (n === "/metadata" && a === "GET") return V(200, p(K), "application/xml");
351
+ let s = await W();
352
+ if (!s) return V(500, j ?? "SAML not available");
353
+ if (n === "/login" && a === "GET") {
354
+ let t = B(new URLSearchParams(e.query).get("returnTo"), f);
355
+ return H(await s.getAuthorizeUrlAsync(t, void 0, {}));
356
+ }
357
+ if (n === "/acs" && a === "POST") {
358
+ let n = new URLSearchParams(new TextDecoder().decode(e.rawBody)), r = n.get("SAMLResponse"), a = B(n.get("RelayState"), f);
359
+ if (!r) return V(400, "missing SAMLResponse");
360
+ let p;
361
+ try {
362
+ ({profile: p} = await s.validatePostResponseAsync({ SAMLResponse: r }));
363
+ } catch (e) {
364
+ return V(401, `SAML assertion rejected: ${String(e?.message ?? e)}`);
365
+ }
366
+ if (!p) return V(401, "no SAML profile");
367
+ let m = await t.onLogin(h(p));
368
+ if (!m) return V(403, "login rejected");
369
+ let g = c(m, t.sessionSecret, { ttlSeconds: d }), _ = o(l, g, {
370
+ maxAgeSeconds: d,
371
+ httpOnly: !0,
372
+ sameSite: "lax"
373
+ }), v = String(p.nameID ?? p.nameId ?? ""), y = [_];
374
+ if (v) {
375
+ let e = i(24).toString("base64url");
376
+ await I().save({
377
+ sloKey: e,
378
+ nameId: v,
379
+ ...p.nameIDFormat ? { nameIdFormat: String(p.nameIDFormat) } : {},
380
+ ...p.sessionIndex ? { sessionIndex: String(p.sessionIndex) } : {}
381
+ }), y.push(o(u, e, {
382
+ maxAgeSeconds: d,
383
+ httpOnly: !0,
384
+ sameSite: "lax"
385
+ }));
386
+ }
387
+ return H(a, y);
388
+ }
389
+ if (n === "/logout" && a === "GET") {
390
+ let t = B(new URLSearchParams(e.query).get("returnTo"), m), n = q(e), r = n ? await I().load(n) : null;
391
+ if (!r) return H(t, J());
392
+ let i = {
393
+ nameID: r.nameId,
394
+ ...r.nameIdFormat ? { nameIDFormat: r.nameIdFormat } : {},
395
+ ...r.sessionIndex ? { sessionIndex: r.sessionIndex } : {}
396
+ }, a;
397
+ try {
398
+ a = await s.getLogoutUrlAsync(i, t, {});
399
+ } catch (e) {
400
+ return V(500, `SAML logout request failed: ${String(e?.message ?? e)}`);
401
+ }
402
+ return n && await I().remove(n), H(a, J());
403
+ }
404
+ if (n === "/slo" && (a === "GET" || a === "POST")) {
405
+ let t = a === "GET" ? e.query : new TextDecoder().decode(e.rawBody), n = new URLSearchParams(t), r = {};
406
+ for (let [e, t] of n) r[e] = t;
407
+ let i = B(n.get("RelayState"), m), o;
408
+ try {
409
+ o = await s.validateRedirectAsync(r, t);
410
+ } catch (e) {
411
+ return V(401, `SAML logout rejected: ${String(e?.message ?? e)}`);
412
+ }
413
+ if (n.has("SAMLResponse")) return H(i, J());
414
+ let c = q(e);
415
+ c && await I().remove(c);
416
+ try {
417
+ return H(await s.getLogoutResponseUrlAsync(o.profile ?? {}, i, {}, !0), J());
418
+ } catch {
419
+ return H(i, J());
420
+ }
421
+ }
422
+ return V(404, "unknown SAML endpoint");
423
+ }
424
+ }],
425
+ ...E ? { extendSchema: { tables: [...S ? v : [], ...T ? w : []] } } : {},
426
+ bindDataStore: (e) => {
427
+ k = e;
428
+ },
429
+ onActivate: (n) => e.sync(() => {
430
+ n.logger.info("saml sso active", {
431
+ basePath: r,
432
+ entityId: t.sp.entityId,
433
+ idpSource: t.idp.metadataUrl ? "metadata-url" : "static",
434
+ replayProtection: y ? S ? "store" : "memory" : "off",
435
+ sloStore: T ? "store" : "memory",
436
+ requestSigning: t.privateKey ? "on" : "off",
437
+ encryptedAssertions: t.decryptionPvk ? "on" : "off"
438
+ }), y && !S && 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."), T || 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.");
439
+ })
440
+ });
441
+ };
442
+ //#endregion
443
+ export { S as LOGOUT_SESSION_TABLE, g as REPLAY_CACHE_TABLE, k as SamlMetadataFetchError, A as SamlMetadataParseError, D as dataStoreLogoutStore, b as dataStoreReplayCache, h as extractProfile, z as fetchIdpMetadata, O as memoryLogoutStore, x as memoryReplayCache, R as parseIdpMetadata, C as samlLogoutTable, w as samlLogoutTables, _ as samlReplayTable, v as samlReplayTables, W as samlSsoPlugin, p as spMetadataXml };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@voltro/plugin-sso-saml",
3
+ "version": "0.1.0",
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
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "sideEffects": false,
31
+ "engines": {
32
+ "node": ">=24.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@effect/platform": "^0.96.2",
36
+ "@voltro/database": "0.1.0",
37
+ "@voltro/protocol": "0.1.0"
38
+ },
39
+ "optionalDependencies": {
40
+ "@node-saml/node-saml": "^5.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "effect": "^3.21.4"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }