@voltro/protocol 0.85.0 → 0.87.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 +5987 -0
- package/README.md +14 -0
- package/THIRD-PARTY-NOTICES.md +1122 -31
- package/dist/apiRoleEndpoints.js +108 -0
- package/dist/apiRoleObservation.js +68 -0
- package/dist/apikey.d.ts +10 -0
- package/dist/approval.js +64 -0
- package/dist/approvalDescriptors.js +26 -0
- package/dist/auth.js +389 -0
- package/dist/boundedRing.js +27 -0
- package/dist/businessRule.js +11 -0
- package/dist/canonicalJson.js +14 -0
- package/dist/connection.js +73 -0
- package/dist/devActivity.js +4 -0
- package/dist/errorSchema.js +24 -0
- package/dist/errorValue.js +21 -0
- package/dist/event.js +110 -0
- package/dist/formCapture.js +12 -0
- package/dist/httpStreamMaxDuration.js +9 -0
- package/dist/idempotency.js +121 -0
- package/dist/idempotencyInput.js +123 -0
- package/dist/identity.js +5 -9
- package/dist/index.d.ts +1095 -139
- package/dist/index.js +54 -1700
- package/dist/inspect.d.ts +1 -1
- package/dist/inspect.js +14 -240
- package/dist/inspectBackfill.js +52 -0
- package/dist/inspectCommandRejection.js +21 -0
- package/dist/inspectProjection.js +133 -0
- package/dist/inspectStorageProjection.js +86 -0
- package/dist/inspectValueIdentity.js +4 -0
- package/dist/inspectWorkflowExecution.js +34 -0
- package/dist/inspectWorkflowQueuedStart.js +31 -0
- package/dist/jsonSchema.js +54 -0
- package/dist/jwt.d.ts +10 -0
- package/dist/jwt.js +1 -1
- package/dist/outbox.js +10 -0
- package/dist/pairing.js +75 -0
- package/dist/plugin.js +87 -0
- package/dist/pluginAccountMerge.js +88 -0
- package/dist/pluginNaming.js +32 -0
- package/dist/pluginPrivacy.js +166 -0
- package/dist/procedure.js +280 -0
- package/dist/publicApi.js +325 -0
- package/dist/queryOptionalSources.js +14 -0
- package/dist/reactivityChannel.js +34 -0
- package/dist/rest.d.ts +260 -64
- package/dist/rest.js +345 -499
- package/dist/restFormat.js +163 -0
- package/dist/restReceipt.js +43 -0
- package/dist/revalidate.js +13 -0
- package/dist/rowEquality.js +31 -0
- package/dist/rowPatch.js +182 -0
- package/dist/schemaShape.js +67 -0
- package/dist/scopes.js +273 -0
- package/dist/secretWindows.js +71 -0
- package/dist/serverErrorBus.js +15 -0
- package/dist/serviceCall.js +14 -0
- package/dist/session.d.ts +89 -6
- package/dist/session.js +62 -63
- package/dist/{sessionHeader-DriTZhmv.js → sessionHeader.js} +1 -1
- package/dist/ssrfPolicy.js +30 -0
- package/dist/storageAccess.js +14 -0
- package/dist/storeErrors.js +66 -0
- package/dist/storeScreenshots.js +90 -0
- package/dist/strictInput.js +27 -0
- package/dist/subscriptionEvent.js +37 -0
- package/dist/tenantSelection.js +40 -0
- package/dist/time.js +7 -0
- package/dist/undo.js +38 -0
- package/dist/validation.js +39 -0
- package/dist/webSocketGateway.js +16 -0
- package/dist/workflow.js +297 -0
- package/dist/workflowAccess.js +61 -0
- package/dist/workflowRestart.js +45 -0
- package/dist/workflowRetry.js +44 -0
- package/dist/workflowStartOutcome.js +30 -0
- package/package.json +5 -3
- package/dist/restFormat-BlgTLxm1.js +0 -298
- package/dist/scopes-ClywBij8.js +0 -548
package/dist/scopes.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { RequestedTenantRefused as e } from "./tenantSelection.js";
|
|
2
|
+
import { Unauthenticated as t, currentSubjectAuthority as n, reauthorizeSubject as r } from "./auth.js";
|
|
3
|
+
import { Effect as i, Schema as a } from "effect";
|
|
4
|
+
//#region src/scopes.ts
|
|
5
|
+
var o = "admin:full", s = "apikeys:issue:self", c = "apikeys:issue:org", l = "apikeys:issue:other", u = "devices:manage", d = class extends a.TaggedError()("ScopeError", {
|
|
6
|
+
required: a.String,
|
|
7
|
+
message: a.String
|
|
8
|
+
}) {}, f = class extends a.TaggedError()("NoSuchResource", {
|
|
9
|
+
resourceType: a.NullOr(a.String),
|
|
10
|
+
resourceId: a.String,
|
|
11
|
+
message: a.String
|
|
12
|
+
}) {}, p = (e) => "scopes" in e && e.scopes ? e.scopes : [], m = /* @__PURE__ */ new WeakMap(), h = /* @__PURE__ */ new WeakMap(), g = (e, t) => {
|
|
13
|
+
h.set(e, t);
|
|
14
|
+
}, _ = async (e, t) => {
|
|
15
|
+
let n = h.get(e);
|
|
16
|
+
n !== void 0 && (await n(t), h.set(t, n));
|
|
17
|
+
}, v = (e, t) => {
|
|
18
|
+
typeof e == "object" && e && m.set(e, t);
|
|
19
|
+
}, y = (e) => {
|
|
20
|
+
if (e = n(e), typeof e == "object" && e) {
|
|
21
|
+
let t = m.get(e);
|
|
22
|
+
if (t) return t;
|
|
23
|
+
}
|
|
24
|
+
return p(e);
|
|
25
|
+
}, b = (e, t) => {
|
|
26
|
+
let n = y(e);
|
|
27
|
+
return n.includes("admin:full") || n.includes(t);
|
|
28
|
+
}, x = (e, t) => {
|
|
29
|
+
let n = p(e);
|
|
30
|
+
return n.includes("admin:full") || n.includes(t);
|
|
31
|
+
}, S = (e, t) => b(e, t) ? i.void : i.fail(new d({
|
|
32
|
+
required: t,
|
|
33
|
+
message: `missing required scope '${t}'`
|
|
34
|
+
})), C = (e, t) => {
|
|
35
|
+
let n;
|
|
36
|
+
try {
|
|
37
|
+
n = typeof e.action == "function" ? e.action(t) : e.action;
|
|
38
|
+
} catch {
|
|
39
|
+
throw new d({
|
|
40
|
+
required: e.resourceType,
|
|
41
|
+
message: "policy action selector failed"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (typeof n != "string" || n.trim() === "") throw new d({
|
|
45
|
+
required: e.resourceType,
|
|
46
|
+
message: "policy action selector returned no action"
|
|
47
|
+
});
|
|
48
|
+
return n;
|
|
49
|
+
}, w = (e) => (typeof e.action == "string" || typeof e.action == "function") && typeof e.resourceType == "string", T = (e) => e !== void 0 && e.some((e) => w(e) || typeof e.resource == "function"), E = (e) => typeof e.open == "string", D = (e) => Array.isArray(e.service), O = (e) => ({ service: [...e] }), k = (e) => e?.find(D)?.service, A = (e, t) => {
|
|
50
|
+
for (let n of t ?? []) {
|
|
51
|
+
let t = N(n);
|
|
52
|
+
if (t) return K(e, t);
|
|
53
|
+
}
|
|
54
|
+
if (t?.some(M)) {
|
|
55
|
+
let t = P(e);
|
|
56
|
+
if (t) return t;
|
|
57
|
+
}
|
|
58
|
+
let n = k(t);
|
|
59
|
+
if (e.type === "service") return n !== void 0 && n.includes(e.service) ? null : new d({
|
|
60
|
+
required: "service-procedure",
|
|
61
|
+
message: n === void 0 ? `service '${e.service}' may only call procedures that declare it in \`service: { callers }\`` : `service '${e.service}' is not among this procedure's callers [${n.join(", ")}]`
|
|
62
|
+
});
|
|
63
|
+
if (n !== void 0) return K(e, new d({
|
|
64
|
+
required: `service:${n.join("|")}`,
|
|
65
|
+
message: `this procedure is callable only by the service${n.length === 1 ? "" : "s"} [${n.join(", ")}]`
|
|
66
|
+
}));
|
|
67
|
+
}, j = (e) => e.authenticated === !0, M = (e) => e.upgraded === !0, N = (e) => typeof e != "object" || !e || !("upgraded" in e) || e.upgraded !== !0 ? null : [
|
|
68
|
+
"scope",
|
|
69
|
+
"mode",
|
|
70
|
+
"resource",
|
|
71
|
+
"action",
|
|
72
|
+
"resourceType",
|
|
73
|
+
"authenticated",
|
|
74
|
+
"open",
|
|
75
|
+
"service"
|
|
76
|
+
].some((t) => t in e) ? new d({
|
|
77
|
+
required: "guard-shape",
|
|
78
|
+
message: "An upgraded guard must be a separate entry; put each additional scope, policy or identity requirement in its own guard entry."
|
|
79
|
+
}) : null, P = (e) => e.type === "user" && e.kind !== "guest" ? null : K(e, new d({
|
|
80
|
+
required: "upgraded",
|
|
81
|
+
message: "this procedure requires a registered user account"
|
|
82
|
+
})), F = (e) => ({ open: e }), I = (e) => typeof e == "string" ? [e] : e, L, R = (e) => {
|
|
83
|
+
L = e;
|
|
84
|
+
}, z = (e) => L === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !w(e) && e.resource !== void 0)).map((e) => e.tag) : [], B = (e) => [
|
|
85
|
+
`${e.length} procedure${e.length === 1 ? "" : "s"} declare a per-resource guard that nothing enforces:`,
|
|
86
|
+
...e.slice(0, 10).map((e) => ` ${e}`),
|
|
87
|
+
...e.length > 10 ? [` … and ${e.length - 10} more`] : [],
|
|
88
|
+
"",
|
|
89
|
+
" `guards: [{ scope, resource }]` reads as \"may you do this to THIS resource\",",
|
|
90
|
+
" but with no resource-scope resolver registered the extractor is advisory and",
|
|
91
|
+
" the check runs against the caller's GLOBAL scopes. For an app whose",
|
|
92
|
+
" authorization is per-resource, that guard passes callers it should refuse.",
|
|
93
|
+
"",
|
|
94
|
+
" Fix it one of two ways:",
|
|
95
|
+
" • setResourceScopeResolver((req) => Effect<boolean>) — answers \"does this",
|
|
96
|
+
" subject hold `req.scope` on `req.resource`\" from your own tables.",
|
|
97
|
+
" • Drop the `resource` extractor if a global scope check is what you meant."
|
|
98
|
+
].join("\n"), V = () => L, H, U = (e) => {
|
|
99
|
+
H = e;
|
|
100
|
+
}, W = () => H, G = (e) => new d({
|
|
101
|
+
required: "access-decision",
|
|
102
|
+
message: `${e ?? "this procedure"} declares no access decision: it has neither \`guards:\` nor \`openAccess:\`, and the app runs with \`security.defaultDeny\`. Add the scope(s) a caller must hold (\`guards: [{ scope: '…' }]\`), or declare it open on purpose with \`openAccess: '<why anyone may call this>'\`.`
|
|
103
|
+
}), K = (e, n) => {
|
|
104
|
+
let r = e.credentialRejected;
|
|
105
|
+
return r === void 0 ? n : new t({ reason: `${r} — the request carried a credential and it was REJECTED, so this caller resolved to anonymous. The guard then refused (${n.message}); the scope is not the problem. Re-authenticate and retry.` });
|
|
106
|
+
}, q = (e) => {
|
|
107
|
+
let t = Array.isArray(e), n = Object.getPrototypeOf(e);
|
|
108
|
+
if (t ? n !== Array.prototype : n !== Object.prototype && n !== null) return !1;
|
|
109
|
+
for (let n of Reflect.ownKeys(e)) if (t && (typeof n != "string" || n !== "length" && !/^(0|[1-9][0-9]*)$/.test(n)) || !("value" in Object.getOwnPropertyDescriptor(e, n))) return !1;
|
|
110
|
+
return !0;
|
|
111
|
+
}, J = (e, t) => {
|
|
112
|
+
let n, r = 0, i, a = 0, o;
|
|
113
|
+
return (s) => {
|
|
114
|
+
let c = y(e);
|
|
115
|
+
return (c !== n || c.length !== r) && (n = c, r = c.length, i = void 0, a = 0, o = void 0), i === void 0 && c.length >= 32 && ++a > 1 && (o ??= q(c) && q(e) && q(t) && t.every((e) => q(e) && (!("scope" in e) || typeof e.scope == "string" || q(e.scope))), o && (i = new Set(c))), i === void 0 ? c.includes("admin:full") || c.includes(s) : i.has("admin:full") || i.has(s);
|
|
116
|
+
};
|
|
117
|
+
}, Y = (e, t, n) => {
|
|
118
|
+
let r = A(e, t);
|
|
119
|
+
if (r !== void 0) return r;
|
|
120
|
+
if (!t || t.length === 0) return n?.defaultDeny === !0 ? G(n.procedure) : null;
|
|
121
|
+
let i = 0, a;
|
|
122
|
+
for (let n of t) {
|
|
123
|
+
if (E(n) || D(n)) continue;
|
|
124
|
+
if (M(n)) {
|
|
125
|
+
let t = P(e);
|
|
126
|
+
if (t) return t;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (j(n)) {
|
|
130
|
+
if (e.id !== null && e.id !== void 0) continue;
|
|
131
|
+
return K(e, new d({
|
|
132
|
+
required: "authenticated",
|
|
133
|
+
message: "this procedure requires an authenticated caller"
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
if (w(n)) {
|
|
137
|
+
if (i++, a === void 0 && (t.length >= 512 || i >= 512) && (a = J(e, t)), a === void 0 ? b(e, "admin:full") : a("admin:full")) continue;
|
|
138
|
+
return K(e, new d({
|
|
139
|
+
required: `${n.resourceType}:${n.action}`,
|
|
140
|
+
message: `relationship guard '${n.action}' on '${n.resourceType}' cannot be checked synchronously`
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
let r = I(n.scope);
|
|
144
|
+
if (n.resourceOnly === !0) return K(e, new d({
|
|
145
|
+
required: r[0] ?? "resource",
|
|
146
|
+
message: "resource-only guard cannot be checked synchronously"
|
|
147
|
+
}));
|
|
148
|
+
if (r.length === 0) continue;
|
|
149
|
+
i += r.length, a === void 0 && (t.length >= 512 || i >= 512) && (a = J(e, t));
|
|
150
|
+
let o = n.mode ?? "all", s;
|
|
151
|
+
if (o === "any") {
|
|
152
|
+
if (a === void 0 ? r.some((t) => b(e, t)) : r.some(a)) continue;
|
|
153
|
+
s = r[0];
|
|
154
|
+
} else {
|
|
155
|
+
let t = a === void 0 ? r.find((t) => !b(e, t)) : r.find((e) => !a(e));
|
|
156
|
+
if (t === void 0) continue;
|
|
157
|
+
s = t;
|
|
158
|
+
}
|
|
159
|
+
return K(e, new d({
|
|
160
|
+
required: s,
|
|
161
|
+
message: o === "any" ? `missing all of required scopes [${r.join(", ")}]` : `missing required scope '${s}'`
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}, X = (e, t, n) => {
|
|
166
|
+
let r;
|
|
167
|
+
try {
|
|
168
|
+
r = C(t, n);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return i.succeed(e);
|
|
171
|
+
}
|
|
172
|
+
let a = (e) => new d({
|
|
173
|
+
required: `${t.resourceType}:${r}`,
|
|
174
|
+
message: e
|
|
175
|
+
});
|
|
176
|
+
if (b(e, "admin:full")) return i.succeed(null);
|
|
177
|
+
let o = H;
|
|
178
|
+
if (o === void 0) return i.succeed(a(`no policy resolver registered — cannot authorize '${r}' on '${t.resourceType}'`));
|
|
179
|
+
let s = t.resource(n);
|
|
180
|
+
return s === void 0 || s === "" ? i.succeed(a(`input does not identify a '${t.resourceType}' to authorize`)) : i.suspend(() => o({
|
|
181
|
+
subject: e,
|
|
182
|
+
action: r,
|
|
183
|
+
resourceType: t.resourceType,
|
|
184
|
+
resourceId: s
|
|
185
|
+
})).pipe(i.catchCause(() => i.succeed(!1)), i.map((n) => Z(e, n, t.resourceType, s, () => a(`no '${r}' relation on ${t.resourceType}:${s}`))));
|
|
186
|
+
}, Z = (e, t, n, r, i) => t === !0 ? null : t === "missing" && e.id !== null && e.id !== void 0 ? new f({
|
|
187
|
+
resourceType: n,
|
|
188
|
+
resourceId: r,
|
|
189
|
+
message: n === null ? `no such resource '${r}'` : `no such ${n} '${r}'`
|
|
190
|
+
}) : i(), Q = (e, t, n, r) => {
|
|
191
|
+
let a = A(e, t);
|
|
192
|
+
if (a !== void 0) return i.succeed(a);
|
|
193
|
+
if (!t || t.length === 0) return i.succeed(Y(e, t, r));
|
|
194
|
+
let o = L, s = t.some(w), c = t.some((e) => "scope" in e && e.resourceOnly === !0);
|
|
195
|
+
return !s && !c && (!o || !t.some((e) => e.resource !== void 0)) ? i.succeed(Y(e, t, r)) : i.gen(function* () {
|
|
196
|
+
for (let r of t) {
|
|
197
|
+
if (E(r) || D(r)) continue;
|
|
198
|
+
if (M(r)) {
|
|
199
|
+
let t = P(e);
|
|
200
|
+
if (t) return t;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (j(r)) {
|
|
204
|
+
if (e.id !== null && e.id !== void 0) continue;
|
|
205
|
+
return K(e, new d({
|
|
206
|
+
required: "authenticated",
|
|
207
|
+
message: "this procedure requires an authenticated caller"
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
if (w(r)) {
|
|
211
|
+
let t = yield* X(e, r, n);
|
|
212
|
+
if (t) return t;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
let t = I(r.scope);
|
|
216
|
+
if (t.length === 0) {
|
|
217
|
+
if (r.resourceOnly === !0) return K(e, new d({
|
|
218
|
+
required: "resource",
|
|
219
|
+
message: "resource-only guard requires a non-empty scope list"
|
|
220
|
+
}));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
let a;
|
|
224
|
+
try {
|
|
225
|
+
a = r.resource ? r.resource(n) : void 0;
|
|
226
|
+
} catch (n) {
|
|
227
|
+
if (r.resourceOnly !== !0) throw n;
|
|
228
|
+
return K(e, new d({
|
|
229
|
+
required: t[0],
|
|
230
|
+
message: "resource selector failed"
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
if (r.resourceOnly === !0 && (o === void 0 || typeof a != "string" || a === "")) return K(e, new d({
|
|
234
|
+
required: t[0],
|
|
235
|
+
message: "resource-only guard requires a resolver and a non-empty resource id"
|
|
236
|
+
}));
|
|
237
|
+
let s = [], c = !1;
|
|
238
|
+
for (let n of t) if (r.resourceOnly !== !0 && b(e, n)) s.push(!0);
|
|
239
|
+
else if (a !== void 0 && o !== void 0) {
|
|
240
|
+
let t = yield* i.suspend(() => o({
|
|
241
|
+
subject: e,
|
|
242
|
+
scope: n,
|
|
243
|
+
resource: a
|
|
244
|
+
})).pipe(i.catchCause(() => i.succeed(!1)));
|
|
245
|
+
t === "missing" && (c = !0), s.push(t === !0);
|
|
246
|
+
} else s.push(!1);
|
|
247
|
+
let l = r.mode ?? "all";
|
|
248
|
+
if (!(l === "any" ? s.some(Boolean) : s.every(Boolean))) {
|
|
249
|
+
let n = l === "any" ? t[0] : t[t.findIndex((e, t) => !s[t])];
|
|
250
|
+
if (c && a !== void 0) {
|
|
251
|
+
let t = Z(e, "missing", null, a, () => new d({
|
|
252
|
+
required: n,
|
|
253
|
+
message: `missing required scope '${n}' on resource '${a}'`
|
|
254
|
+
}));
|
|
255
|
+
if (t !== null) return t;
|
|
256
|
+
}
|
|
257
|
+
return new d({
|
|
258
|
+
required: n,
|
|
259
|
+
message: l === "any" ? `missing all of required scopes [${t.join(", ")}]` : `missing required scope '${n}'` + (a === void 0 ? "" : ` on resource '${a}'`)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
});
|
|
265
|
+
}, $ = (n, o, s, c) => i.tryPromise({
|
|
266
|
+
try: async () => r(n, (e) => _(n, e)),
|
|
267
|
+
catch: (e) => e
|
|
268
|
+
}).pipe(i.matchEffect({
|
|
269
|
+
onFailure: (n) => a.is(t)(n) || a.is(e)(n) ? i.succeed(n) : i.die(n),
|
|
270
|
+
onSuccess: (e) => Q(e, o, s, c)
|
|
271
|
+
}));
|
|
272
|
+
//#endregion
|
|
273
|
+
export { o as ADMIN_SCOPE, c as APIKEY_ISSUE_ORG_SCOPE, l as APIKEY_ISSUE_OTHER_SCOPE, s as APIKEY_ISSUE_SELF_SCOPE, u as DEVICES_MANAGE_SCOPE, f as NoSuchResource, d as ScopeError, B as advisoryResourceGuardWarning, Y as checkGuards, Q as checkGuardsEffect, T as declaresResourceGuard, y as effectiveScopes, z as findAdvisoryResourceGuards, W as getPolicyGuardResolver, V as getResourceScopeResolver, b as hasEffectiveScope, x as hasScope, j as isAuthenticatedCheck, E as isOpenAccess, w as isPolicyCheck, D as isServiceAccess, M as isUpgradedCheck, G as missingAccessDecision, F as openAccessSpec, $ as recheckGuardsEffect, _ as refreshEffectiveScopes, S as requireScope, C as resolvePolicyAction, O as serviceAccessSpec, k as serviceCallersOf, v as setEffectiveScopes, g as setEffectiveScopesRefresher, U as setPolicyGuardResolver, R as setResourceScopeResolver, p as subjectScopes, N as upgradedGuardShapeError };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHash as e, createHmac as t, timingSafeEqual as n } from "node:crypto";
|
|
2
|
+
//#region src/secretWindows.ts
|
|
3
|
+
var r = (e) => t("sha256", e).update("voltro:secret-fingerprint:v1").digest("hex").slice(0, 16), i = Symbol.for("voltro.secretWindows"), a = () => {
|
|
4
|
+
let e = globalThis;
|
|
5
|
+
return e[i] ??= {
|
|
6
|
+
signed: /* @__PURE__ */ new Map(),
|
|
7
|
+
configured: /* @__PURE__ */ new Map(),
|
|
8
|
+
presented: /* @__PURE__ */ new Map(),
|
|
9
|
+
declared: /* @__PURE__ */ new Map(),
|
|
10
|
+
windows: /* @__PURE__ */ new Map()
|
|
11
|
+
}, e[i];
|
|
12
|
+
}, o = (e, t) => {
|
|
13
|
+
let n = e.get(t);
|
|
14
|
+
if (n !== void 0) return n;
|
|
15
|
+
let r = /* @__PURE__ */ new Map();
|
|
16
|
+
return e.set(t, r), r;
|
|
17
|
+
}, s = (e, t, n, i = Date.now()) => {
|
|
18
|
+
let s = o(a().signed, e), c = r(t), l = n ?? Infinity, u = s.get(c), d = Math.max(u?.longestMs ?? 0, l - i);
|
|
19
|
+
s.set(c, {
|
|
20
|
+
until: Math.max(u?.until ?? -Infinity, l),
|
|
21
|
+
longestMs: d
|
|
22
|
+
});
|
|
23
|
+
}, c = (e, t) => {
|
|
24
|
+
let n = a().declared;
|
|
25
|
+
n.set(e, Math.max(n.get(e) ?? 0, t));
|
|
26
|
+
}, l = (e, t, n) => {
|
|
27
|
+
a().configured.set(e, {
|
|
28
|
+
current: r(t),
|
|
29
|
+
previous: n === void 0 || n === "" || n === t ? null : r(n)
|
|
30
|
+
});
|
|
31
|
+
}, u = () => {
|
|
32
|
+
let e = a(), t = [...e.signed].flatMap(([e, t]) => [...t].map(([t, n]) => ({
|
|
33
|
+
purpose: e,
|
|
34
|
+
fingerprint: t,
|
|
35
|
+
signedUntil: n.until,
|
|
36
|
+
longestMs: n.longestMs
|
|
37
|
+
}))), n = [...e.presented].flatMap(([e, t]) => [...t].map(([t, n]) => ({
|
|
38
|
+
purpose: e,
|
|
39
|
+
fingerprint: t,
|
|
40
|
+
at: n
|
|
41
|
+
}))), r = [...e.configured].map(([e, t]) => ({
|
|
42
|
+
purpose: e,
|
|
43
|
+
...t
|
|
44
|
+
})), i = [...e.declared].map(([e, t]) => ({
|
|
45
|
+
purpose: e,
|
|
46
|
+
lifetimeMs: t
|
|
47
|
+
}));
|
|
48
|
+
return e.signed.clear(), e.presented.clear(), {
|
|
49
|
+
signed: t,
|
|
50
|
+
configured: r,
|
|
51
|
+
presented: n,
|
|
52
|
+
declared: i
|
|
53
|
+
};
|
|
54
|
+
}, d = (e) => {
|
|
55
|
+
let t = /* @__PURE__ */ new Map();
|
|
56
|
+
for (let n of e) o(t, n.purpose).set(n.fingerprint, n.closesAt);
|
|
57
|
+
a().windows = t;
|
|
58
|
+
}, f = (e, t, n) => {
|
|
59
|
+
o(a().windows, e).set(r(t), n);
|
|
60
|
+
}, p = () => {
|
|
61
|
+
let e = a();
|
|
62
|
+
e.windows = /* @__PURE__ */ new Map(), e.signed.clear(), e.configured.clear(), e.presented.clear(), e.declared.clear();
|
|
63
|
+
}, m = (e, t, n = Date.now()) => {
|
|
64
|
+
let i = a().windows.get(e)?.get(r(t));
|
|
65
|
+
return i == null || n <= i;
|
|
66
|
+
}, h = (t) => e("sha256").update(t).digest(), g = (e, t, i, s) => {
|
|
67
|
+
let c = h(t), l = i !== "" && n(c, h(i)), u = s !== void 0 && s !== "" && s !== i && n(c, h(s));
|
|
68
|
+
return l ? !0 : !u || !m(e, s) ? !1 : (o(a().presented, e).set(r(s), Date.now()), !0);
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
export { d as applySecretWindows, g as bearerMatches, p as clearSecretWindows, u as drainSecretActivity, l as noteConfigured, s as noteSigned, c as noteSigningLifetime, m as previousAccepted, r as secretFingerprint, f as setSecretWindow };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/serverErrorBus.ts
|
|
2
|
+
var e = (e) => {
|
|
3
|
+
let t = e.error, n = t instanceof Error ? t.name : typeof t?._tag == "string" ? String(t._tag) : "Error", r = [e.name === void 0 ? e.source : `${e.source} '${e.name}'`, ...typeof e.fields?.runId == "string" ? [`run ${e.fields.runId}`] : []].join(", "), i = /* @__PURE__ */ Error(`[message withheld: ${r} is declared encrypted — read it where the run is shown]`);
|
|
4
|
+
i.name = n;
|
|
5
|
+
let a = t instanceof Error && typeof t.stack == "string" ? t.stack : "", o = a.search(/^\s+at /m);
|
|
6
|
+
return i.stack = `${n}: ${i.message}${o === -1 ? "" : `\n${a.slice(o)}`}`, i;
|
|
7
|
+
}, t = /* @__PURE__ */ new Set(), n = (e) => {
|
|
8
|
+
for (let n of t) try {
|
|
9
|
+
n(e);
|
|
10
|
+
} catch {}
|
|
11
|
+
}, r = (e) => (t.add(e), () => {
|
|
12
|
+
t.delete(e);
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { n as publishServerError, r as subscribeServerErrors, e as withheldServerError };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
//#region src/serviceCall.ts
|
|
3
|
+
var t = class extends e.TaggedError()("ApiUnavailable", {
|
|
4
|
+
api: e.String,
|
|
5
|
+
tag: e.String,
|
|
6
|
+
reason: e.Literals([
|
|
7
|
+
"timeout",
|
|
8
|
+
"transport",
|
|
9
|
+
"closed"
|
|
10
|
+
]),
|
|
11
|
+
message: e.String
|
|
12
|
+
}) {}, n = (e) => typeof e == "object" && !!e && e._tag === "ApiUnavailable";
|
|
13
|
+
//#endregion
|
|
14
|
+
export { t as ApiUnavailable, n as isApiUnavailable };
|
package/dist/session.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Schema } from 'effect';
|
|
2
2
|
|
|
3
|
+
/** Replace every recorded window with the boot's computed ones. */
|
|
4
|
+
export declare const applySecretWindows: (windows: ReadonlyArray<{
|
|
5
|
+
readonly purpose: string;
|
|
6
|
+
readonly fingerprint: string;
|
|
7
|
+
readonly closesAt: number | null;
|
|
8
|
+
}>) => void;
|
|
9
|
+
|
|
3
10
|
/**
|
|
4
11
|
* Fail-closed check that a real session secret is configured. Throws a plain
|
|
5
12
|
* `Error` (a fatal boot condition, not a wire error) when
|
|
@@ -8,6 +15,14 @@ import { Schema } from 'effect';
|
|
|
8
15
|
*/
|
|
9
16
|
export declare const assertProductionSessionSecret: (env?: NodeJS.ProcessEnv) => void;
|
|
10
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Does a presented bearer token match the current value or — during a
|
|
20
|
+
* rotation, while its window is open — the previous one? Both comparisons run,
|
|
21
|
+
* in constant time over digests, so neither which one matched nor a length
|
|
22
|
+
* leaks through timing. A match on the previous value is recorded.
|
|
23
|
+
*/
|
|
24
|
+
export declare const bearerMatches: (purpose: string, presented: string, current: string, previous?: string) => boolean;
|
|
25
|
+
|
|
11
26
|
/**
|
|
12
27
|
* Build a `Set-Cookie` header value. Pure helper — the caller adds it
|
|
13
28
|
* to the actual HTTP response. Used by the framework's login route
|
|
@@ -46,6 +61,9 @@ export declare const checkBearer: (headers: Readonly<Record<string, string | und
|
|
|
46
61
|
readonly openWhenUnset?: boolean;
|
|
47
62
|
}) => boolean;
|
|
48
63
|
|
|
64
|
+
/** Forget every record — a test seam, and a process that stops verifying. */
|
|
65
|
+
export declare const clearSecretWindows: () => void;
|
|
66
|
+
|
|
49
67
|
/** Key id assigned to `VOLTRO_SESSION_SECRET_PREVIOUS` when no explicit
|
|
50
68
|
* `VOLTRO_SESSION_KID_PREVIOUS` is set. The kid is a non-secret label —
|
|
51
69
|
* rotation works with just the `_PREVIOUS` secret var. */
|
|
@@ -56,6 +74,9 @@ export declare const DEFAULT_PREVIOUS_SESSION_KID: "k-previous";
|
|
|
56
74
|
* string produce the same label the resolver would. */
|
|
57
75
|
export declare const DEFAULT_SESSION_KID: "k0";
|
|
58
76
|
|
|
77
|
+
/** What this process signed, holds and was presented since the last drain — for the boot to persist. */
|
|
78
|
+
export declare const drainSecretActivity: () => SecretActivity;
|
|
79
|
+
|
|
59
80
|
/** A keyed signing secret. `kid` is stamped into the payload on sign and
|
|
60
81
|
* matched on verify; it's an opaque, non-secret label (e.g. `'2026-06'`). */
|
|
61
82
|
export declare interface KeyedSecret {
|
|
@@ -68,6 +89,31 @@ export declare interface KeyedSecret {
|
|
|
68
89
|
* placeholders, not real secrets. */
|
|
69
90
|
export declare const MIN_SESSION_SECRET_LENGTH: 32;
|
|
70
91
|
|
|
92
|
+
/** A resolver read `current` (and, during a rotation, `previous`) for `purpose`. */
|
|
93
|
+
export declare const noteConfigured: (purpose: string, current: string, previous: string | undefined) => void;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A signer signed something under `secret` that is valid until `expiresAtMs`
|
|
97
|
+
* (undefined: no expiry). The lifetime it signed with is recorded too: it is
|
|
98
|
+
* what bounds the tokens signed before anything was recorded.
|
|
99
|
+
*/
|
|
100
|
+
export declare const noteSigned: (purpose: string, secret: string, expiresAtMs: number | undefined, now?: number) => void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A configured signer can sign `purpose` tokens valid for up to `lifetimeMs` —
|
|
104
|
+
* declared when it is built, before it signs anything. The first rotation after
|
|
105
|
+
* an upgrade needs it: the previous value signed tokens nothing recorded, and
|
|
106
|
+
* only the configuration says how long they can live.
|
|
107
|
+
*/
|
|
108
|
+
export declare const noteSigningLifetime: (purpose: string, lifetimeMs: number) => void;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Is this previous value still accepted? Yes while its window is open, and
|
|
112
|
+
* when no window is recorded for it (nothing has decided otherwise). No once
|
|
113
|
+
* the latest expiry it signed has passed, or a compromise closed it.
|
|
114
|
+
*/
|
|
115
|
+
export declare const previousAccepted: (purpose: string, previous: string, now?: number) => boolean;
|
|
116
|
+
|
|
71
117
|
/**
|
|
72
118
|
* Read a cookie value by name from a `Cookie:` header string.
|
|
73
119
|
* Returns undefined if the cookie isn't present.
|
|
@@ -123,6 +169,37 @@ export declare const resolveSessionSecret: () => string;
|
|
|
123
169
|
*/
|
|
124
170
|
export declare const resolveSessionSecrets: () => SessionSecrets;
|
|
125
171
|
|
|
172
|
+
/** What a process recorded since the last drain: signatures, configured values, previous bearer values that answered. */
|
|
173
|
+
export declare interface SecretActivity {
|
|
174
|
+
/** Per purpose and fingerprint: the latest expiry signed and the longest lifetime signed with. */
|
|
175
|
+
readonly signed: ReadonlyArray<{
|
|
176
|
+
readonly purpose: string;
|
|
177
|
+
readonly fingerprint: string;
|
|
178
|
+
readonly signedUntil: number;
|
|
179
|
+
readonly longestMs: number;
|
|
180
|
+
}>;
|
|
181
|
+
/** Per purpose: the fingerprints of the current and the previous value this process holds. */
|
|
182
|
+
readonly configured: ReadonlyArray<{
|
|
183
|
+
readonly purpose: string;
|
|
184
|
+
readonly current: string;
|
|
185
|
+
readonly previous: string | null;
|
|
186
|
+
}>;
|
|
187
|
+
/** Per purpose: when a previous bearer value last answered. */
|
|
188
|
+
readonly presented: ReadonlyArray<{
|
|
189
|
+
readonly purpose: string;
|
|
190
|
+
readonly fingerprint: string;
|
|
191
|
+
readonly at: number;
|
|
192
|
+
}>;
|
|
193
|
+
/** Per purpose: the longest lifetime a configured signer declared. */
|
|
194
|
+
readonly declared: ReadonlyArray<{
|
|
195
|
+
readonly purpose: string;
|
|
196
|
+
readonly lifetimeMs: number;
|
|
197
|
+
}>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A non-reversible name for a secret value: 16 hex of an HMAC under a fixed label. */
|
|
201
|
+
export declare const secretFingerprint: (value: string) => string;
|
|
202
|
+
|
|
126
203
|
/**
|
|
127
204
|
* The cookie the framework's own session strategy writes and reads.
|
|
128
205
|
*
|
|
@@ -166,15 +243,16 @@ export declare const SESSION_PAYLOAD_VERSION: 2;
|
|
|
166
243
|
*/
|
|
167
244
|
export declare const sessionExpiryFromHeaders: (headers: Record<string, string | undefined>) => number | undefined;
|
|
168
245
|
|
|
169
|
-
export declare type SessionPayload = typeof
|
|
246
|
+
export declare type SessionPayload = typeof SessionPayloadSchema.Type;
|
|
170
247
|
|
|
171
|
-
declare const
|
|
248
|
+
declare const SessionPayloadSchema: Schema.Struct<{
|
|
172
249
|
/** Payload format version. Required and pinned — see the header. */
|
|
173
250
|
readonly v: Schema.Literal<2>;
|
|
174
251
|
/** WHO, never WHAT-THEY-MAY-DO. `SubjectIdentity` has no `scopes` field. */
|
|
175
252
|
readonly subject: Schema.Union<readonly [Schema.Struct<{
|
|
176
253
|
readonly type: Schema.Literal<"user">;
|
|
177
254
|
readonly id: Schema.String;
|
|
255
|
+
readonly kind: Schema.optional<Schema.Literal<"guest">>;
|
|
178
256
|
readonly tenantId: Schema.String;
|
|
179
257
|
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
180
258
|
}>, Schema.Struct<{
|
|
@@ -201,9 +279,9 @@ declare const SessionPayload_2: Schema.Struct<{
|
|
|
201
279
|
}>, Schema.Struct<{
|
|
202
280
|
readonly type: Schema.Literal<"service">;
|
|
203
281
|
readonly id: Schema.String;
|
|
282
|
+
readonly service: Schema.String;
|
|
204
283
|
readonly tenantId: Schema.Null;
|
|
205
284
|
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
206
|
-
readonly service: Schema.String;
|
|
207
285
|
}>]>;
|
|
208
286
|
/** Unix seconds when this session expires. Verify rejects past expiry. */
|
|
209
287
|
readonly exp: Schema.Number;
|
|
@@ -217,8 +295,6 @@ declare const SessionPayload_2: Schema.Struct<{
|
|
|
217
295
|
readonly kid: Schema.optional<Schema.String>;
|
|
218
296
|
}>;
|
|
219
297
|
|
|
220
|
-
declare type SessionPayload = typeof SessionPayload_2.Type;
|
|
221
|
-
|
|
222
298
|
/** The resolved secret set: `current` always signs; `previous` is accepted
|
|
223
299
|
* on verify during a rotation window. */
|
|
224
300
|
export declare interface SessionSecrets {
|
|
@@ -242,6 +318,9 @@ export declare interface SetCookieOptions {
|
|
|
242
318
|
readonly domain?: string;
|
|
243
319
|
}
|
|
244
320
|
|
|
321
|
+
/** Record one window by value — a test seam and a single-process embedder's. */
|
|
322
|
+
export declare const setSecretWindow: (purpose: string, previous: string, closesAt: number | null) => void;
|
|
323
|
+
|
|
245
324
|
export declare interface SignOptions {
|
|
246
325
|
/** TTL in seconds. Defaults to 7 days. */
|
|
247
326
|
readonly ttlSeconds?: number;
|
|
@@ -278,6 +357,9 @@ export declare const signSession: (subject: Subject | SubjectIdentity, secret: s
|
|
|
278
357
|
|
|
279
358
|
declare const Subject: Schema.Union<readonly [Schema.Struct<{
|
|
280
359
|
readonly type: Schema.Literal<"user">;
|
|
360
|
+
/** A guest has a stable identity but has not linked a registered account.
|
|
361
|
+
* This identity mark survives session signing; it never grants authority. */
|
|
362
|
+
readonly kind: Schema.optional<Schema.Literal<"guest">>;
|
|
281
363
|
readonly id: Schema.String;
|
|
282
364
|
readonly tenantId: Schema.String;
|
|
283
365
|
readonly scopes: Schema.optional<Schema.$Array<Schema.String>>;
|
|
@@ -339,6 +421,7 @@ declare type Subject = typeof Subject.Type;
|
|
|
339
421
|
declare const SubjectIdentity: Schema.Union<readonly [Schema.Struct<{
|
|
340
422
|
readonly type: Schema.Literal<"user">;
|
|
341
423
|
readonly id: Schema.String;
|
|
424
|
+
readonly kind: Schema.optional<Schema.Literal<"guest">>;
|
|
342
425
|
readonly tenantId: Schema.String;
|
|
343
426
|
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
344
427
|
}>, Schema.Struct<{
|
|
@@ -381,9 +464,9 @@ declare const SubjectIdentity: Schema.Union<readonly [Schema.Struct<{
|
|
|
381
464
|
}>, Schema.Struct<{
|
|
382
465
|
readonly type: Schema.Literal<"service">;
|
|
383
466
|
readonly id: Schema.String;
|
|
467
|
+
readonly service: Schema.String;
|
|
384
468
|
readonly tenantId: Schema.Null;
|
|
385
469
|
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
386
|
-
readonly service: Schema.String;
|
|
387
470
|
}>]>;
|
|
388
471
|
|
|
389
472
|
declare type SubjectIdentity = typeof SubjectIdentity.Type;
|