@voltro/protocol 0.80.0 → 0.82.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.
@@ -63,49 +63,62 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
63
63
  tenantId: n.Null,
64
64
  scopes: c,
65
65
  metadata: n.optional(s)
66
- }), m = n.Union(l, u, d, f, p), h = n.Union(l.omit("scopes"), u.omit("scopes"), d.omit("scopes"), f, p.omit("scopes")), ee = (e) => {
66
+ }), m = n.Struct({
67
+ type: n.Literal("service"),
68
+ id: n.String,
69
+ service: n.String,
70
+ tenantId: n.Null,
71
+ scopes: c,
72
+ metadata: n.optional(s)
73
+ }), ee = n.Union(l, u, d, f, p, m), te = n.Union(l.omit("scopes"), u.omit("scopes"), d.omit("scopes"), f, p.omit("scopes"), m.omit("scopes")), ne = (e) => {
67
74
  if (e.type === "anonymous") return e;
68
75
  let { scopes: t, ...n } = e;
69
76
  return n;
70
- }, g = (e) => e.scopes ?? [], _ = (e, t) => ({
77
+ }, h = (e) => e.scopes ?? [], g = (e, t) => ({
71
78
  type: "anonymous",
72
79
  id: null,
73
80
  tenantId: e,
74
81
  ...t === void 0 ? {} : { credentialRejected: t }
75
- }), v = (e, t = ["admin:full"]) => ({
82
+ }), re = (e, t = ["admin:full"]) => ({
76
83
  type: "system",
77
84
  id: e,
78
85
  tenantId: null,
79
86
  scopes: t
80
- }), y = (e) => {
87
+ }), ie = (e) => ({
88
+ type: "service",
89
+ id: `service:${e}`,
90
+ service: e,
91
+ tenantId: null,
92
+ scopes: []
93
+ }), ae = (e) => e.type === "service", oe = (e) => e.type === "system" || e.type === "service", _ = (e) => {
81
94
  if (e.type === "system") return null;
82
95
  if (e.type === "serviceAccount" && e.auditActorId !== void 0) return e.auditActorId;
83
96
  if (e.type !== "apiKey") return e.id;
84
97
  let t = e.metadata?.userId;
85
98
  return typeof t == "string" && t !== "" ? t : e.id;
86
- }, te = (e, t) => {
99
+ }, v = (e, t) => {
87
100
  let n = e;
88
101
  return {
89
102
  type: "serviceAccount",
90
103
  id: e.id ?? "system",
91
104
  tenantId: t,
92
- auditActorId: y(e),
93
- scopes: g(e),
105
+ auditActorId: _(e),
106
+ scopes: h(e),
94
107
  ...n.metadata === void 0 ? {} : { metadata: n.metadata }
95
108
  };
96
- }, ne = (e) => e.type === "system", b = "impersonation", x = (e) => e.metadata?.impersonation ?? null, S = class extends e.Tag("@voltro/Subject")() {}, C = class extends e.Tag("@voltro/ConnectionInfo")() {}, w = class extends n.TaggedError()("Unauthenticated", { reason: n.optional(n.String) }) {}, T = class extends r.Tag()("@voltro/AuthMiddleware", {
97
- provides: S,
98
- failure: w
99
- }) {}, re = class extends r.Tag()("@voltro/ConnectionInfoMiddleware", { provides: C }) {}, ie = (e) => typeof e.mountRoutes == "function", ae = (e) => Array.isArray(e) ? {
109
+ }, se = (e) => e.type === "system", ce = "impersonation", le = (e) => e.metadata?.impersonation ?? null, y = class extends e.Tag("@voltro/Subject")() {}, b = class extends e.Tag("@voltro/ConnectionInfo")() {}, x = class extends n.TaggedError()("Unauthenticated", { reason: n.optional(n.String) }) {}, ue = class extends r.Tag()("@voltro/AuthMiddleware", {
110
+ provides: y,
111
+ failure: x
112
+ }) {}, de = class extends r.Tag()("@voltro/ConnectionInfoMiddleware", { provides: b }) {}, fe = (e) => typeof e.mountRoutes == "function", pe = (e) => Array.isArray(e) ? {
100
113
  kind: "grant",
101
114
  scopes: e
102
- } : e, E = (e, t) => {
103
- if (t.kind === "vetoed") throw new w({ reason: `${t.code}: ${t.message}` });
115
+ } : e, S = (e, t) => {
116
+ if (t.kind === "vetoed") throw new x({ reason: `${t.code}: ${t.message}` });
104
117
  if (e.type === "anonymous") return {
105
118
  subject: e,
106
119
  removed: []
107
120
  };
108
- let n = g(e);
121
+ let n = h(e);
109
122
  if (t.kind === "grant") {
110
123
  if (t.scopes.length === 0) return {
111
124
  subject: e,
@@ -128,14 +141,14 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
128
141
  },
129
142
  removed: i
130
143
  };
131
- }, oe = 3e4, se = 1e4, D = (e) => `${e.type}\u0000${e.tenantId ?? ""}\u0000${e.id ?? ""}`, ce = () => {
144
+ }, me = 3e4, he = 1e4, C = (e) => `${e.type}\u0000${e.tenantId ?? ""}\u0000${e.id ?? ""}`, ge = () => {
132
145
  if (typeof process > "u") return;
133
146
  let e = process.env.VOLTRO_AUTH_SCOPE_CACHE_TTL_MS;
134
147
  if (e === void 0 || e === "") return;
135
148
  let t = Number(e);
136
149
  return Number.isFinite(t) && t >= 0 ? t : void 0;
137
- }, O = (e = {}) => {
138
- let t = e.ttlMs ?? ce() ?? 3e4, n = e.maxEntries ?? 1e4, r = e.now ?? Date.now, i = /* @__PURE__ */ new Map(), a = () => {
150
+ }, w = (e = {}) => {
151
+ let t = e.ttlMs ?? ge() ?? 3e4, n = e.maxEntries ?? 1e4, r = e.now ?? Date.now, i = /* @__PURE__ */ new Map(), a = () => {
139
152
  if (i.size <= n) return;
140
153
  let e = r();
141
154
  for (let [t, n] of i) n.freshUntil <= e && i.delete(t);
@@ -171,10 +184,10 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
171
184
  };
172
185
  }
173
186
  };
174
- }, le = (e) => typeof e.resolve == "function", ue = (e) => {
187
+ }, _e = (e) => typeof e.resolve == "function", ve = (e) => {
175
188
  let t = e.authorization ?? e.Authorization;
176
189
  if (t !== void 0) return /^Bearer\s+(.+)$/i.exec(t.trim())?.[1]?.trim();
177
- }, de = async (e, t, n, r) => {
190
+ }, ye = async (e, t, n, r) => {
178
191
  if (!i(e)) return e;
179
192
  let s = {
180
193
  type: e.type,
@@ -203,40 +216,52 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
203
216
  }
204
217
  return e;
205
218
  }
206
- }, k = (e, t) => {
207
- let n = ue(t);
219
+ }, T = (e, t) => {
220
+ let n = ve(t);
208
221
  if (n === void 0) return e;
209
222
  let r = e.filter((e) => e.claimsBearerPrefix !== void 0 && e.claimsBearerPrefix !== "" && n.startsWith(e.claimsBearerPrefix));
210
223
  if (r.length === 0) return e;
211
224
  let i = new Set(r);
212
225
  return [...r, ...e.filter((e) => !i.has(e))];
213
- }, A = (e, t) => {
226
+ }, be = (e, t) => {
227
+ if (e === "refuse") throw new x({ reason: "anonymous callers are refused" });
228
+ if (e === "no-tenant") return "";
229
+ if (e === "header") {
230
+ if (t === void 0 || t === "") throw new x({ reason: "tenant required (x-tenant header missing)" });
231
+ return t;
232
+ }
233
+ return "tenantId" in e ? e.tenantId : t !== void 0 && t !== "" ? t : e.headerOrTenant;
234
+ }, E = (e, t) => {
214
235
  let n = t?.resolveScopes, r = t?.subjectGuards ?? [];
215
236
  if (t?.memberships === void 0 != (t?.tenantSelection === void 0)) throw Error("composeAuthStrategies: `memberships` and `tenantSelection` come together — a tenant selection is verified against the memberships and read from the store; configure both or neither.");
216
237
  let i = t?.memberships !== void 0 && t.tenantSelection !== void 0 ? {
217
238
  memberships: t.memberships,
218
239
  store: t.tenantSelection
219
- } : void 0, a = n !== void 0 || r.length > 0, o = !a || t?.scopeCache === !1 ? null : t?.scopeCache !== void 0 && le(t.scopeCache) ? t.scopeCache : O(t?.scopeCache ?? {});
240
+ } : void 0, a = n !== void 0 || r.length > 0, o = !a || t?.scopeCache === !1 ? null : t?.scopeCache !== void 0 && _e(t.scopeCache) ? t.scopeCache : w(t?.scopeCache ?? {});
220
241
  return async (s) => {
221
242
  let c = t?.getStore?.(), l = c === void 0 ? s : {
222
243
  ...s,
223
244
  store: c
224
245
  }, u;
225
- for (let s of k(e, l.headers)) {
246
+ for (let s of T(e, l.headers)) {
226
247
  let e = await s.resolve(l);
227
248
  if (e.kind === "matched") {
228
- let c = i === void 0 ? e.subject : await de(e.subject, l, i, (e) => (t?.onStrategyFailed?.({
249
+ if (e.subject.type === "service") return { subject: {
250
+ ...e.subject,
251
+ scopes: []
252
+ } };
253
+ let c = i === void 0 ? e.subject : await ye(e.subject, l, i, (e) => (t?.onStrategyFailed?.({
229
254
  strategyId: s.id,
230
255
  reason: e
231
- }), new w({ reason: e }))), u = c;
256
+ }), new x({ reason: e }))), u = c;
232
257
  if (a) {
233
258
  let e = async () => {
234
259
  let e = n === void 0 ? {
235
260
  kind: "grant",
236
261
  scopes: []
237
- } : ae(await n(c, l));
262
+ } : pe(await n(c, l));
238
263
  if (e.kind === "unavailable" || r.length === 0) return e;
239
- let t = E(c, e).subject, i = {
264
+ let t = S(c, e).subject, i = {
240
265
  origin: "request",
241
266
  headers: l.headers,
242
267
  clientId: l.clientId,
@@ -254,22 +279,22 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
254
279
  }, { decision: i, fresh: a } = o === null ? {
255
280
  decision: await e(),
256
281
  fresh: !0
257
- } : await o.resolve(D(c), e);
282
+ } : await o.resolve(C(c), e);
258
283
  if (i.kind === "vetoed") {
259
284
  let e = `${i.code}: ${i.message}`;
260
285
  throw t?.onStrategyFailed?.({
261
286
  strategyId: s.id,
262
287
  reason: e
263
- }), new w({ reason: e });
288
+ }), new x({ reason: e });
264
289
  }
265
290
  if (i.kind === "unavailable") {
266
291
  let e = `scope resolution unavailable: ${i.reason}`;
267
292
  throw t?.onStrategyFailed?.({
268
293
  strategyId: s.id,
269
294
  reason: e
270
- }), new w({ reason: e });
295
+ }), new x({ reason: e });
271
296
  }
272
- let d = E(c, i);
297
+ let d = S(c, i);
273
298
  a && d.removed.length > 0 && t?.onScopesNarrowed?.({
274
299
  strategyId: s.id,
275
300
  subjectType: c.type,
@@ -298,52 +323,63 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
298
323
  credentialRejected: u
299
324
  } : e };
300
325
  }
301
- if (u !== void 0) throw new w({ reason: `credential rejected: ${u}` });
302
- if (t?.anonymousTenantId !== void 0) return { subject: _(t.anonymousTenantId) };
303
- let d = l.headers["x-tenant"] ?? t?.anonymousTenantDefault ?? null;
304
- if (d === null && t?.anonymousTenantRequired === !0) throw new w({ reason: "tenant required (x-tenant header missing)" });
305
- return { subject: _(d) };
326
+ if (u !== void 0) throw new x({ reason: `credential rejected: ${u}` });
327
+ return { subject: g(be(t?.anonymous ?? "no-tenant", l.headers["x-tenant"])) };
306
328
  };
307
- }, j = (e, t) => {
308
- if (e.type === "anonymous") throw new w(t === void 0 ? {} : { reason: t });
309
- }, M = "admin:full", N = "apikeys:issue:self", P = "apikeys:issue:org", F = "apikeys:issue:other", I = "devices:manage", L = class extends n.TaggedError()("ScopeError", {
329
+ }, D = (e, t) => {
330
+ if (e.type === "anonymous") throw new x(t === void 0 ? {} : { reason: t });
331
+ }, O = "admin:full", k = "apikeys:issue:self", A = "apikeys:issue:org", j = "apikeys:issue:other", M = "devices:manage", N = class extends n.TaggedError()("ScopeError", {
310
332
  required: n.String,
311
333
  message: n.String
312
- }) {}, R = (e) => "scopes" in e && e.scopes ? e.scopes : [], z = /* @__PURE__ */ new WeakMap(), B = (e, t) => {
313
- typeof e == "object" && e && z.set(e, t);
314
- }, V = (e) => {
334
+ }) {}, P = class extends n.TaggedError()("NoSuchResource", {
335
+ resourceType: n.NullOr(n.String),
336
+ resourceId: n.String,
337
+ message: n.String
338
+ }) {}, F = (e) => "scopes" in e && e.scopes ? e.scopes : [], I = /* @__PURE__ */ new WeakMap(), xe = (e, t) => {
339
+ typeof e == "object" && e && I.set(e, t);
340
+ }, L = (e) => {
315
341
  if (typeof e == "object" && e) {
316
- let t = z.get(e);
342
+ let t = I.get(e);
317
343
  if (t) return t;
318
344
  }
319
- return R(e);
320
- }, H = (e, t) => {
321
- let n = V(e);
345
+ return F(e);
346
+ }, R = (e, t) => {
347
+ let n = L(e);
322
348
  return n.includes("admin:full") || n.includes(t);
323
- }, fe = (e, t) => {
324
- let n = R(e);
349
+ }, Se = (e, t) => {
350
+ let n = F(e);
325
351
  return n.includes("admin:full") || n.includes(t);
326
- }, pe = (e, n) => H(e, n) ? t.void : t.fail(new L({
352
+ }, Ce = (e, n) => R(e, n) ? t.void : t.fail(new N({
327
353
  required: n,
328
354
  message: `missing required scope '${n}'`
329
- })), U = (e, t) => {
355
+ })), z = (e, t) => {
330
356
  let n;
331
357
  try {
332
358
  n = typeof e.action == "function" ? e.action(t) : e.action;
333
359
  } catch {
334
- throw new L({
360
+ throw new N({
335
361
  required: e.resourceType,
336
362
  message: "policy action selector failed"
337
363
  });
338
364
  }
339
- if (typeof n != "string" || n.trim() === "") throw new L({
365
+ if (typeof n != "string" || n.trim() === "") throw new N({
340
366
  required: e.resourceType,
341
367
  message: "policy action selector returned no action"
342
368
  });
343
369
  return n;
344
- }, W = (e) => (typeof e.action == "string" || typeof e.action == "function") && typeof e.resourceType == "string", G = (e) => typeof e.open == "string", K = (e) => e.authenticated === !0, me = (e) => ({ open: e }), q = (e) => typeof e == "string" ? [e] : e, J, he = (e) => {
345
- J = e;
346
- }, ge = (e) => J === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !W(e) && e.resource !== void 0)).map((e) => e.tag) : [], _e = (e) => [
370
+ }, B = (e) => (typeof e.action == "string" || typeof e.action == "function") && typeof e.resourceType == "string", we = (e) => e !== void 0 && e.some((e) => B(e) || typeof e.resource == "function"), V = (e) => typeof e.open == "string", H = (e) => Array.isArray(e.service), Te = (e) => ({ service: [...e] }), U = (e) => e?.find(H)?.service, W = (e, t) => {
371
+ let n = U(t);
372
+ if (e.type === "service") return n !== void 0 && n.includes(e.service) ? null : new N({
373
+ required: "service-procedure",
374
+ 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(", ")}]`
375
+ });
376
+ if (n !== void 0) return Z(e, new N({
377
+ required: `service:${n.join("|")}`,
378
+ message: `this procedure is callable only by the service${n.length === 1 ? "" : "s"} [${n.join(", ")}]`
379
+ }));
380
+ }, G = (e) => e.authenticated === !0, Ee = (e) => ({ open: e }), K = (e) => typeof e == "string" ? [e] : e, q, De = (e) => {
381
+ q = e;
382
+ }, Oe = (e) => q === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !B(e) && e.resource !== void 0)).map((e) => e.tag) : [], ke = (e) => [
347
383
  `${e.length} procedure${e.length === 1 ? "" : "s"} declare a per-resource guard that nothing enforces:`,
348
384
  ...e.slice(0, 10).map((e) => ` ${e}`),
349
385
  ...e.length > 10 ? [` … and ${e.length - 10} more`] : [],
@@ -357,57 +393,59 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
357
393
  " • setResourceScopeResolver((req) => Effect<boolean>) — answers \"does this",
358
394
  " subject hold `req.scope` on `req.resource`\" from your own tables.",
359
395
  " • Drop the `resource` extractor if a global scope check is what you meant."
360
- ].join("\n"), ve = () => J, Y, ye = (e) => {
361
- Y = e;
362
- }, X = () => Y, Z = (e) => new L({
396
+ ].join("\n"), Ae = () => q, J, je = (e) => {
397
+ J = e;
398
+ }, Y = () => J, X = (e) => new N({
363
399
  required: "access-decision",
364
400
  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>'\`.`
365
- }), Q = (e, t) => {
401
+ }), Z = (e, t) => {
366
402
  let n = e.credentialRejected;
367
- return n === void 0 ? t : new w({ reason: `${n} — the request carried a credential and it was REJECTED, so this caller resolved to anonymous. The guard then refused (${t.message}); the scope is not the problem. Re-authenticate and retry.` });
368
- }, $ = (e, t, n) => {
369
- if (!t || t.length === 0) return n?.defaultDeny === !0 ? Z(n.procedure) : null;
403
+ return n === void 0 ? t : new x({ reason: `${n} — the request carried a credential and it was REJECTED, so this caller resolved to anonymous. The guard then refused (${t.message}); the scope is not the problem. Re-authenticate and retry.` });
404
+ }, Q = (e, t, n) => {
405
+ let r = W(e, t);
406
+ if (r !== void 0) return r;
407
+ if (!t || t.length === 0) return n?.defaultDeny === !0 ? X(n.procedure) : null;
370
408
  for (let n of t) {
371
- if (G(n)) continue;
372
- if (K(n)) {
409
+ if (V(n) || H(n)) continue;
410
+ if (G(n)) {
373
411
  if (e.id !== null && e.id !== void 0) continue;
374
- return Q(e, new L({
412
+ return Z(e, new N({
375
413
  required: "authenticated",
376
414
  message: "this procedure requires an authenticated caller"
377
415
  }));
378
416
  }
379
- if (W(n)) {
380
- if (H(e, "admin:full")) continue;
381
- return Q(e, new L({
417
+ if (B(n)) {
418
+ if (R(e, "admin:full")) continue;
419
+ return Z(e, new N({
382
420
  required: `${n.resourceType}:${n.action}`,
383
421
  message: `relationship guard '${n.action}' on '${n.resourceType}' cannot be checked synchronously`
384
422
  }));
385
423
  }
386
- let t = q(n.scope);
424
+ let t = K(n.scope);
387
425
  if (t.length === 0) continue;
388
- let r = t.filter((t) => H(e, t));
426
+ let r = t.filter((t) => R(e, t));
389
427
  if (!((n.mode ?? "all") === "any" ? r.length > 0 : r.length === t.length)) {
390
- let r = (n.mode ?? "all") === "any" ? t[0] : t.find((t) => !H(e, t));
391
- return Q(e, new L({
428
+ let r = (n.mode ?? "all") === "any" ? t[0] : t.find((t) => !R(e, t));
429
+ return Z(e, new N({
392
430
  required: r,
393
431
  message: (n.mode ?? "all") === "any" ? `missing all of required scopes [${t.join(", ")}]` : `missing required scope '${r}'`
394
432
  }));
395
433
  }
396
434
  }
397
435
  return null;
398
- }, be = (e, n, r) => {
436
+ }, Me = (e, n, r) => {
399
437
  let i;
400
438
  try {
401
- i = U(n, r);
439
+ i = z(n, r);
402
440
  } catch (e) {
403
441
  return t.succeed(e);
404
442
  }
405
- let a = (e) => new L({
443
+ let a = (e) => new N({
406
444
  required: `${n.resourceType}:${i}`,
407
445
  message: e
408
446
  });
409
- if (H(e, "admin:full")) return t.succeed(null);
410
- let o = Y;
447
+ if (R(e, "admin:full")) return t.succeed(null);
448
+ let o = J;
411
449
  if (o === void 0) return t.succeed(a(`no policy resolver registered — cannot authorize '${i}' on '${n.resourceType}'`));
412
450
  let s = n.resource(r);
413
451
  return s === void 0 || s === "" ? t.succeed(a(`input does not identify a '${n.resourceType}' to authorize`)) : t.suspend(() => o({
@@ -415,48 +453,61 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
415
453
  action: i,
416
454
  resourceType: n.resourceType,
417
455
  resourceId: s
418
- })).pipe(t.catchAllCause(() => t.succeed(!1)), t.map((e) => e ? null : a(`no '${i}' relation on ${n.resourceType}:${s}`)));
419
- }, xe = (e, n, r, i) => {
420
- if (!n || n.length === 0) return t.succeed($(e, n, i));
421
- let a = J;
422
- return !n.some(W) && (!a || !n.some((e) => e.resource !== void 0)) ? t.succeed($(e, n, i)) : t.gen(function* () {
456
+ })).pipe(t.catchAllCause(() => t.succeed(!1)), t.map((t) => $(e, t, n.resourceType, s, () => a(`no '${i}' relation on ${n.resourceType}:${s}`))));
457
+ }, $ = (e, t, n, r, i) => t === !0 ? null : t === "missing" && e.id !== null && e.id !== void 0 ? new P({
458
+ resourceType: n,
459
+ resourceId: r,
460
+ message: n === null ? `no such resource '${r}'` : `no such ${n} '${r}'`
461
+ }) : i(), Ne = (e, n, r, i) => {
462
+ let a = W(e, n);
463
+ if (a !== void 0) return t.succeed(a);
464
+ if (!n || n.length === 0) return t.succeed(Q(e, n, i));
465
+ let o = q;
466
+ return !n.some(B) && (!o || !n.some((e) => e.resource !== void 0)) ? t.succeed(Q(e, n, i)) : t.gen(function* () {
423
467
  for (let i of n) {
424
- if (G(i)) continue;
425
- if (K(i)) {
468
+ if (V(i) || H(i)) continue;
469
+ if (G(i)) {
426
470
  if (e.id !== null && e.id !== void 0) continue;
427
- return Q(e, new L({
471
+ return Z(e, new N({
428
472
  required: "authenticated",
429
473
  message: "this procedure requires an authenticated caller"
430
474
  }));
431
475
  }
432
- if (W(i)) {
433
- let t = yield* be(e, i, r);
476
+ if (B(i)) {
477
+ let t = yield* Me(e, i, r);
434
478
  if (t) return t;
435
479
  continue;
436
480
  }
437
- let n = q(i.scope);
481
+ let n = K(i.scope);
438
482
  if (n.length === 0) continue;
439
- let o = i.resource ? i.resource(r) : void 0, s = [];
483
+ let a = i.resource ? i.resource(r) : void 0, s = [], c = !1;
440
484
  for (let r of n) {
441
- if (H(e, r)) {
485
+ if (R(e, r)) {
442
486
  s.push(!0);
443
487
  continue;
444
488
  }
445
- if (o !== void 0 && a !== void 0) {
446
- let n = yield* t.suspend(() => a({
489
+ if (a !== void 0 && o !== void 0) {
490
+ let n = yield* t.suspend(() => o({
447
491
  subject: e,
448
492
  scope: r,
449
- resource: o
493
+ resource: a
450
494
  })).pipe(t.catchAllCause(() => t.succeed(!1)));
451
- s.push(n);
495
+ n === "missing" && (c = !0), s.push(n === !0);
452
496
  } else s.push(!1);
453
497
  }
454
- let c = i.mode ?? "all";
455
- if (!(c === "any" ? s.some(Boolean) : s.every(Boolean))) {
456
- let e = c === "any" ? n[0] : n[n.findIndex((e, t) => !s[t])];
457
- return new L({
458
- required: e,
459
- message: c === "any" ? `missing all of required scopes [${n.join(", ")}]` : `missing required scope '${e}'` + (o === void 0 ? "" : ` on resource '${o}'`)
498
+ let l = i.mode ?? "all";
499
+ if (!(l === "any" ? s.some(Boolean) : s.every(Boolean))) {
500
+ let t = l === "any" ? n[0] : n[n.findIndex((e, t) => !s[t])];
501
+ if (c && a !== void 0) {
502
+ let n = $(e, "missing", null, a, () => new N({
503
+ required: t,
504
+ message: `missing required scope '${t}' on resource '${a}'`
505
+ }));
506
+ if (n !== null) return n;
507
+ }
508
+ return new N({
509
+ required: t,
510
+ message: l === "any" ? `missing all of required scopes [${n.join(", ")}]` : `missing required scope '${t}'` + (a === void 0 ? "" : ` on resource '${a}'`)
460
511
  });
461
512
  }
462
513
  }
@@ -464,4 +515,4 @@ var i = (e) => e.type === "user" || e.type === "apiKey" || e.type === "serviceAc
464
515
  });
465
516
  };
466
517
  //#endregion
467
- export { o as $, se as A, A as B, B as C, T as D, R as E, S as F, x as G, ne as H, w as I, ee as J, D as K, _ as L, b as M, m as N, C as O, h as P, i as Q, E as R, U as S, he as T, O as U, ie as V, k as W, te as X, v as Y, a as Z, G as _, I as a, me as b, $ as c, ge as d, X as f, K as g, fe as h, N as i, oe as j, re as k, xe as l, H as m, P as n, L as o, ve as p, y as q, F as r, _e as s, M as t, V as u, W as v, ye as w, pe as x, Z as y, j as z };
518
+ export { C as $, De as A, y as B, Ee as C, U as D, Te as E, he as F, E as G, g as H, me as I, ae as J, fe as K, ce as L, ue as M, b as N, xe as O, de as P, le as Q, ee as R, X as S, z as T, S as U, x as V, D as W, w as X, se as Y, T as Z, Se as _, M as a, a as at, B as b, ke as c, we as d, ie as et, L as f, R as g, Ae as h, k as i, v as it, F as j, je as k, Q as l, Y as m, A as n, ne as nt, P as o, i as ot, Oe as p, oe as q, j as r, re as rt, N as s, o as st, O as t, _ as tt, Ne as u, G as v, Ce as w, H as x, V as y, te as z };
package/dist/session.d.ts CHANGED
@@ -74,6 +74,14 @@ export declare const MIN_SESSION_SECRET_LENGTH: 32;
74
74
  */
75
75
  export declare const readCookie: (header: string | undefined, name: string) => string | undefined;
76
76
 
77
+ /**
78
+ * The session value this request presents — the `x-voltro-session` header a
79
+ * cookie-less client (React Native, a desktop shell) sets on every rpc frame,
80
+ * or the session cookie a browser carries. See `./sessionHeader.ts` for why
81
+ * it is a dedicated header and why the header wins.
82
+ */
83
+ export declare const readSessionValue: (headers: Readonly<Record<string, string | undefined>>, cookieName?: string) => string | undefined;
84
+
77
85
  /**
78
86
  * Reset the once-per-process warning latch. Tests only — the latch is what
79
87
  * makes the diagnostic readable in production and unobservable in a second
@@ -125,6 +133,9 @@ export declare const resolveSessionSecrets: () => SessionSecrets;
125
133
  */
126
134
  export declare const SESSION_COOKIE_NAME: string;
127
135
 
136
+ /** The request header carrying a framework session value outside a cookie. */
137
+ export declare const SESSION_HEADER_NAME: "x-voltro-session";
138
+
128
139
  /**
129
140
  * The session payload format this build mints and accepts.
130
141
  *
@@ -189,6 +200,12 @@ declare const SessionPayload_2: Schema.Struct<{
189
200
  id: typeof Schema.String;
190
201
  tenantId: typeof Schema.Null;
191
202
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
203
+ }>, Schema.Struct<{
204
+ type: Schema.Literal<["service"]>;
205
+ id: typeof Schema.String;
206
+ tenantId: typeof Schema.Null;
207
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
208
+ service: typeof Schema.String;
192
209
  }>]>;
193
210
  /** Unix seconds when this session expires. Verify rejects past expiry. */
194
211
  exp: typeof Schema.Number;
@@ -311,6 +328,14 @@ declare const Subject: Schema.Union<[Schema.Struct<{
311
328
  tenantId: typeof Schema.Null;
312
329
  scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
313
330
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
331
+ }>, Schema.Struct<{
332
+ type: Schema.Literal<["service"]>;
333
+ id: typeof Schema.String;
334
+ /** The calling app's name, as the callee lists it in `serviceCallers`. */
335
+ service: typeof Schema.String;
336
+ tenantId: typeof Schema.Null;
337
+ scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
338
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
314
339
  }>]>;
315
340
 
316
341
  declare type Subject = typeof Subject.Type;
@@ -359,6 +384,12 @@ declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
359
384
  id: typeof Schema.String;
360
385
  tenantId: typeof Schema.Null;
361
386
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
387
+ }>, Schema.Struct<{
388
+ type: Schema.Literal<["service"]>;
389
+ id: typeof Schema.String;
390
+ tenantId: typeof Schema.Null;
391
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
392
+ service: typeof Schema.String;
362
393
  }>]>;
363
394
 
364
395
  declare type SubjectIdentity = typeof SubjectIdentity.Type;