wire-mesh-core 3.7.1 → 3.8.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.
Files changed (107) hide show
  1. package/dist/adapters/byte-stream-connection.d.cts +2 -2
  2. package/dist/adapters/byte-stream-connection.d.mts +2 -2
  3. package/dist/adapters/frame-codec.d.cts +1 -1
  4. package/dist/adapters/frame-codec.d.mts +1 -1
  5. package/dist/adapters/node-identity.d.cts +2 -2
  6. package/dist/adapters/node-identity.d.mts +2 -2
  7. package/dist/adapters/tcp-transport.d.cts +1 -1
  8. package/dist/adapters/tcp-transport.d.mts +1 -1
  9. package/dist/adapters/threshold-identity.d.cts +2 -2
  10. package/dist/adapters/threshold-identity.d.mts +2 -2
  11. package/dist/adapters/threshold-wasm.d.cts +1 -1
  12. package/dist/adapters/threshold-wasm.d.mts +1 -1
  13. package/dist/adapters/tls-transport.d.cts +1 -1
  14. package/dist/adapters/tls-transport.d.mts +1 -1
  15. package/dist/domain/advert-extension-policy.d.cts +1 -1
  16. package/dist/domain/advert-extension-policy.d.mts +1 -1
  17. package/dist/domain/bulk.d.cts +2 -2
  18. package/dist/domain/bulk.d.mts +2 -2
  19. package/dist/domain/capability-grant.cjs +1 -1
  20. package/dist/domain/capability-grant.d.cts +3 -3
  21. package/dist/domain/capability-grant.d.mts +3 -3
  22. package/dist/domain/capability-grant.mjs +1 -1
  23. package/dist/domain/capability-request.cjs +52 -4
  24. package/dist/domain/capability-request.d.cts +14 -3
  25. package/dist/domain/capability-request.d.mts +14 -3
  26. package/dist/domain/capability-request.mjs +52 -5
  27. package/dist/domain/coordinator-election.d.cts +1 -1
  28. package/dist/domain/coordinator-election.d.mts +1 -1
  29. package/dist/domain/data-sync.d.cts +2 -2
  30. package/dist/domain/data-sync.d.mts +2 -2
  31. package/dist/domain/device-id.d.cts +1 -1
  32. package/dist/domain/device-id.d.mts +1 -1
  33. package/dist/domain/direct-manage-request.d.cts +2 -2
  34. package/dist/domain/direct-manage-request.d.mts +2 -2
  35. package/dist/domain/gossip-expansion.d.cts +1 -1
  36. package/dist/domain/gossip-expansion.d.mts +1 -1
  37. package/dist/domain/grant-candidates.d.cts +1 -1
  38. package/dist/domain/grant-candidates.d.mts +1 -1
  39. package/dist/domain/handshake.d.cts +1 -1
  40. package/dist/domain/handshake.d.mts +1 -1
  41. package/dist/domain/hub-mailbox.d.cts +1 -1
  42. package/dist/domain/hub-mailbox.d.mts +1 -1
  43. package/dist/domain/mesh-session.cjs +1 -1
  44. package/dist/domain/mesh-session.d.cts +3 -3
  45. package/dist/domain/mesh-session.d.mts +3 -3
  46. package/dist/domain/mesh-session.mjs +1 -1
  47. package/dist/domain/notice-board.d.cts +3 -3
  48. package/dist/domain/notice-board.d.mts +3 -3
  49. package/dist/domain/path-trace.d.cts +1 -1
  50. package/dist/domain/path-trace.d.mts +1 -1
  51. package/dist/domain/peer-advert.d.cts +2 -2
  52. package/dist/domain/peer-advert.d.mts +2 -2
  53. package/dist/domain/relay-advert.d.cts +1 -1
  54. package/dist/domain/relay-advert.d.mts +1 -1
  55. package/dist/domain/relay-hub.d.cts +2 -2
  56. package/dist/domain/relay-hub.d.mts +2 -2
  57. package/dist/domain/relay-use-gate.d.cts +2 -2
  58. package/dist/domain/relay-use-gate.d.mts +2 -2
  59. package/dist/domain/revocation-view.cjs +1 -1
  60. package/dist/domain/revocation-view.d.cts +2 -2
  61. package/dist/domain/revocation-view.d.mts +2 -2
  62. package/dist/domain/revocation-view.mjs +1 -1
  63. package/dist/domain/room-rekey.d.cts +3 -3
  64. package/dist/domain/room-rekey.d.mts +3 -3
  65. package/dist/domain/room-token-verification.cjs +1 -1
  66. package/dist/domain/room-token-verification.d.cts +1 -1
  67. package/dist/domain/room-token-verification.d.mts +1 -1
  68. package/dist/domain/room-token-verification.mjs +1 -1
  69. package/dist/domain/room.d.cts +4 -4
  70. package/dist/domain/room.d.mts +4 -4
  71. package/dist/domain/shard-manifest.d.cts +1 -1
  72. package/dist/domain/shard-manifest.d.mts +1 -1
  73. package/dist/domain/threshold-subject.cjs +1 -1
  74. package/dist/domain/threshold-subject.mjs +1 -1
  75. package/dist/domain/tokens.cjs +3 -1
  76. package/dist/domain/tokens.d.cts +2 -2
  77. package/dist/domain/tokens.d.mts +2 -2
  78. package/dist/domain/tokens.mjs +2 -2
  79. package/dist/domain/topology-snapshot.d.cts +1 -1
  80. package/dist/domain/topology-snapshot.d.mts +1 -1
  81. package/dist/domain/topology.d.cts +1 -1
  82. package/dist/domain/topology.d.mts +1 -1
  83. package/dist/domain/webrtc-signaling.cjs +1 -1
  84. package/dist/domain/webrtc-signaling.d.cts +2 -2
  85. package/dist/domain/webrtc-signaling.d.mts +2 -2
  86. package/dist/domain/webrtc-signaling.mjs +1 -1
  87. package/dist/generated/protocol.cjs +4 -1
  88. package/dist/generated/protocol.d.cts +1 -1
  89. package/dist/generated/protocol.d.mts +1 -1
  90. package/dist/generated/protocol.mjs +4 -1
  91. package/dist/{identity-DVCNuFLG.d.cts → identity-DJbr96J3.d.cts} +1 -1
  92. package/dist/{identity-DcaYgjXT.d.mts → identity-ViYdZrIU.d.mts} +1 -1
  93. package/dist/ports/identity.d.cts +1 -1
  94. package/dist/ports/identity.d.mts +1 -1
  95. package/dist/ports/transport.d.cts +1 -1
  96. package/dist/ports/transport.d.mts +1 -1
  97. package/dist/{protocol-DCDae3z4.d.cts → protocol-BgR1e_NI.d.cts} +3 -0
  98. package/dist/{protocol-DCDae3z4.d.mts → protocol-BgR1e_NI.d.mts} +3 -0
  99. package/dist/{room-token-verification-CkGnFuQc.d.mts → room-token-verification-7zD4ib-A.d.mts} +2 -2
  100. package/dist/{room-token-verification-PSv4eQ1x.d.cts → room-token-verification-hFtBDZPO.d.cts} +2 -2
  101. package/dist/{tokens-DqFu7iMX.d.cts → tokens-BMWCnQEF.d.cts} +37 -5
  102. package/dist/{tokens-CMdWiRBb.mjs → tokens-D8JlTJNA.mjs} +175 -17
  103. package/dist/{tokens-CBiSYuzp.d.mts → tokens-DRkm_QOR.d.mts} +37 -5
  104. package/dist/{tokens-DSG3SDr8.cjs → tokens-tPJQ7Vzt.cjs} +186 -16
  105. package/dist/{transport-B0GywEzG.d.mts → transport-DP3hdehk.d.cts} +1 -1
  106. package/dist/{transport-DIXjoQKn.d.cts → transport-Dv_LTCsF.d.mts} +1 -1
  107. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  const require_generated_protocol = require("./generated/protocol.cjs");
2
+ const require_domain_device_id = require("./domain/device-id.cjs");
2
3
  const require_token_scope = require("./token-scope-CxHXTT3u.cjs");
3
4
  let cbor2 = require("cbor2");
4
5
  let zod = require("zod");
@@ -24,6 +25,24 @@ const NARROWING_SYSTEMS = [
24
25
  function isNarrowingSystem(system) {
25
26
  return system === "bearer-is" || system === "expires-at" || system === "scope-narrows" || system === "capability-is" || system === "depth-remaining";
26
27
  }
28
+ /** The two subject-mode delegate systems (wire-mesh#323), registered only when a ConditionsContext carries a subject, and indeterminate (fail-closed) whenever one does not. */
29
+ const GRANTEE_IS = "grantee-is";
30
+ const GRANTED_CAPABILITY_IS = "granted-capability-is";
31
+ function isSubjectSystem(system) {
32
+ return system === "grantee-is" || system === "granted-capability-is";
33
+ }
34
+ const subjectHandlers = {
35
+ [GRANTEE_IS]: (payload, context) => {
36
+ if (context.subject === void 0) return { found: false };
37
+ if (typeof payload !== "string") return { found: false };
38
+ return booleanFound(require_domain_device_id.deviceIdToHex(context.subject.bearer) === payload);
39
+ },
40
+ [GRANTED_CAPABILITY_IS]: (payload, context) => {
41
+ if (context.subject === void 0) return { found: false };
42
+ if (typeof payload !== "string") return { found: false };
43
+ return booleanFound(context.subject.capability === payload);
44
+ }
45
+ };
27
46
  function booleanFound(value) {
28
47
  return {
29
48
  found: true,
@@ -58,15 +77,15 @@ const narrowingHandlers = {
58
77
  return booleanFound(context.candidate.delegationsRemaining !== void 0 && context.candidate.delegationsRemaining < parentRemaining);
59
78
  }
60
79
  };
61
- /** `{kind:"compare", op:"eq", left:{kind:"delegate", system, payload:null}, right:{kind:"booleanLiteral", value:true}}` -- delegate is an ExpressionNode, not itself a PredicateNode (trilean's PredicateNodeSchema has no `delegate` member), so every predicate op is expressed this way: resolve the delegate to a boolean ComputedValue, then compare it against the literal `true`. */
62
- function delegateIsTrue(system) {
80
+ /** `{kind:"compare", op:"eq", left:{kind:"delegate", system, payload}, right:{kind:"booleanLiteral", value:true}}` -- delegate is an ExpressionNode, not itself a PredicateNode (trilean's PredicateNodeSchema has no `delegate` member), so every predicate op is expressed this way: resolve the delegate to a boolean ComputedValue, then compare it against the literal `true`. The narrowing checks pass no payload (every fact they need arrives via context); the subject-mode systems take their comparison value as the payload. */
81
+ function delegateIsTrue(system, payload = null) {
63
82
  return {
64
83
  kind: "compare",
65
84
  op: "eq",
66
85
  left: {
67
86
  kind: "delegate",
68
87
  system,
69
- payload: null
88
+ payload
70
89
  },
71
90
  right: {
72
91
  kind: "booleanLiteral",
@@ -104,16 +123,25 @@ async function evaluateNarrowing(parentClaims, childIssuer, candidate) {
104
123
  /**
105
124
  * Evaluates token-claims' own `conditions` field (issue #85): additional, issuer-chosen predicates strictly additive to the five mandatory narrowing checks above, which are computed structurally (from a delegated token's own claims and its parent's) and never read from this field -- the five built-ins are inherently about a parent/child relationship, so they are not meaningfully reusable inside a bare, parent-less conditions evaluation, unlike a genuine domain-specific condition (a message TTL, #84's future revoke-authorization check) which reasons about the presented token's own claims directly. `extraHandlers` is the registration point for those: none exist yet, so a `conditions` entry naming any system with no registered handler resolves `{found: false}` -- indeterminate, fail-closed. Absent `nodes` (an empty array, the caller's own signal for "no conditions field present") is trivially `{ok: true}` -- no extra restrictions, exactly today's behaviour. Every entry MUST evaluate to a definite `true`; the loop short-circuits on the first indeterminate or false result.
106
125
  */
107
- async function evaluateConditions(nodes, claims, clock, extraHandlers = {}) {
108
- const context = {
126
+ async function evaluateConditions(nodes, claims, clock, extraHandlers = {}, subject) {
127
+ const context = subject === void 0 ? {
109
128
  clock,
110
129
  claims
130
+ } : {
131
+ clock,
132
+ claims,
133
+ subject
111
134
  };
112
135
  const resolvers = {
113
136
  resolveValue: unusedResolveValue,
114
137
  resolveLookup: unusedResolveLookup,
115
138
  resolveCollection: unusedResolveCollection,
116
139
  resolveDelegate: async (system, payload) => {
140
+ if (subject !== void 0 && isSubjectSystem(system)) try {
141
+ return await subjectHandlers[system](payload, context);
142
+ } catch {
143
+ return { found: false };
144
+ }
117
145
  const handler = Object.hasOwn(extraHandlers, system) ? extraHandlers[system] : void 0;
118
146
  if (handler === void 0) return { found: false };
119
147
  try {
@@ -134,6 +162,10 @@ async function evaluateConditions(nodes, claims, clock, extraHandlers = {}) {
134
162
  }
135
163
  //#endregion
136
164
  //#region src/domain/tokens.ts
165
+ /** The capability verb of a grant-capability (wire-mesh#323): authority to mint other capability tokens, the same register manage:revoke already established for authority over tokens. */
166
+ const MANAGE_GRANT_CAPABILITY = "manage:grant";
167
+ /** Both link kinds walk at most this many hops before the verifier gives up, mirroring Rust's own MAX_CHAIN_DEPTH. Two colluding issuers can construct a mutually-bearing parent cycle that would otherwise recurse forever, and the authorised-by link makes the attack cheaper to mount, so TS gains the bound rather than relying on JS recursion limits. */
168
+ const MAX_CHAIN_DEPTH = 64;
137
169
  /** RFC 9052 §4.4 Sig_structure for a COSE_Sign1 with no external AAD: ["Signature1", protected, external_aad, payload]. Exported (not just used internally by verifyTokenChain) so any other self-certifying COSE_Sign1 construction in this codebase -- e.g. threshold-subject.ts's own to-be-signed bytes for a FROST-signed capability-token/revocation-entry/handle-record/room-notice -- reuses this single construction rather than a second hand-rolled one. */
138
170
  function sig1ToBeSigned(protectedHeader, payload) {
139
171
  return (0, cbor2.encode)([
@@ -207,12 +239,22 @@ async function revocationEntryGrantsRevoke(entry, targetClaims, options) {
207
239
  });
208
240
  return authorizationVerdict.ok && authorizationVerdict.claims.capability === "manage:revoke" && require_token_scope.scopeNarrows(authorizationVerdict.claims.scope, targetClaims.scope);
209
241
  }
210
- async function verifyTokenChain(token, options) {
242
+ async function verifyTokenChain(token, options, subjectClaims, depth = 0, seen = /* @__PURE__ */ new Set()) {
211
243
  const [protectedHeader, , payload, signature] = token;
212
244
  if (payload === null) return {
213
245
  ok: false,
214
246
  reason: "malformed"
215
247
  };
248
+ if (depth >= MAX_CHAIN_DEPTH) return {
249
+ ok: false,
250
+ reason: "chain_too_deep"
251
+ };
252
+ const payloadHex = require_domain_device_id.bytesToHex(payload);
253
+ if (seen.has(payloadHex)) return {
254
+ ok: false,
255
+ reason: "chain_cycle"
256
+ };
257
+ const deeperSeen = new Set(seen).add(payloadHex);
216
258
  let decodedClaims;
217
259
  try {
218
260
  decodedClaims = (0, cbor2.decode)(payload, cbor2.cdeDecodeOptions);
@@ -271,15 +313,25 @@ async function verifyTokenChain(token, options) {
271
313
  ok: false,
272
314
  reason: "conditions_invalid"
273
315
  };
274
- if (!(await evaluateConditions(conditionsResult.data, claims, options.clock, options.extraPredicateResolvers)).ok) return {
316
+ if (!(await evaluateConditions(conditionsResult.data, claims, options.clock, options.extraPredicateResolvers, subjectClaims)).ok) return {
275
317
  ok: false,
276
318
  reason: "conditions_not_satisfied"
277
319
  };
278
320
  }
279
- if (claims.parent !== void 0) {
321
+ const parentBytes = claims.parent;
322
+ const authorisationBytes = claims["authorised-by"];
323
+ if (claims.capability === "manage:grant" && parentBytes !== void 0) return {
324
+ ok: false,
325
+ reason: "authorisation_invalid"
326
+ };
327
+ if (parentBytes !== void 0 && authorisationBytes !== void 0) return {
328
+ ok: false,
329
+ reason: "malformed"
330
+ };
331
+ if (parentBytes !== void 0) {
280
332
  let decodedParent;
281
333
  try {
282
- decodedParent = (0, cbor2.decode)(claims.parent, cbor2.cdeDecodeOptions);
334
+ decodedParent = (0, cbor2.decode)(parentBytes, cbor2.cdeDecodeOptions);
283
335
  } catch {
284
336
  return {
285
337
  ok: false,
@@ -291,11 +343,17 @@ async function verifyTokenChain(token, options) {
291
343
  ok: false,
292
344
  reason: "parent_invalid"
293
345
  };
294
- const parentVerdict = await verifyTokenChain(parentResult.data, options);
295
- if (!parentVerdict.ok) return {
296
- ok: false,
297
- reason: "parent_invalid"
298
- };
346
+ const parentVerdict = await verifyTokenChain(parentResult.data, options, void 0, depth + 1, deeperSeen);
347
+ if (!parentVerdict.ok) {
348
+ if (parentVerdict.reason === "chain_too_deep" || parentVerdict.reason === "chain_cycle") return {
349
+ ok: false,
350
+ reason: parentVerdict.reason
351
+ };
352
+ return {
353
+ ok: false,
354
+ reason: "parent_invalid"
355
+ };
356
+ }
299
357
  const candidate = {
300
358
  capability: claims.capability,
301
359
  scope: claims.scope,
@@ -314,6 +372,46 @@ async function verifyTokenChain(token, options) {
314
372
  depth: parentVerdict.depth + 1
315
373
  };
316
374
  }
375
+ if (authorisationBytes !== void 0) {
376
+ let decodedAuthoriser;
377
+ try {
378
+ decodedAuthoriser = (0, cbor2.decode)(authorisationBytes, cbor2.cdeDecodeOptions);
379
+ } catch {
380
+ return {
381
+ ok: false,
382
+ reason: "authorisation_invalid"
383
+ };
384
+ }
385
+ const authoriserResult = require_generated_protocol.capabilityTokenSchema.safeParse(decodedAuthoriser);
386
+ if (!authoriserResult.success) return {
387
+ ok: false,
388
+ reason: "authorisation_invalid"
389
+ };
390
+ const authoriserVerdict = await verifyTokenChain(authoriserResult.data, options, claims, depth + 1, deeperSeen);
391
+ if (!authoriserVerdict.ok) {
392
+ if (authoriserVerdict.reason === "chain_too_deep" || authoriserVerdict.reason === "chain_cycle") return {
393
+ ok: false,
394
+ reason: authoriserVerdict.reason
395
+ };
396
+ return {
397
+ ok: false,
398
+ reason: "authorisation_invalid"
399
+ };
400
+ }
401
+ const authoriser = authoriserVerdict.claims;
402
+ const grantsCapability = authoriser["grants-capability"];
403
+ if (!(require_token_scope.bytesEqual(authoriser.bearer, claims.issuer) && authoriser.capability === "manage:grant" && (grantsCapability === void 0 || grantsCapability === claims.capability) && require_token_scope.scopeNarrows(authoriser.scope, claims.scope) && claims.expires <= authoriser.expires && (authoriser["delegations-remaining"] === void 0 || claims["delegations-remaining"] !== void 0 && claims["delegations-remaining"] < authoriser["delegations-remaining"]))) return {
404
+ ok: false,
405
+ reason: "authorisation_invalid"
406
+ };
407
+ return {
408
+ ok: true,
409
+ claims,
410
+ rootIssuer: authoriserVerdict.rootIssuer,
411
+ rootIssuerKey: authoriserVerdict.rootIssuerKey,
412
+ depth: authoriserVerdict.depth + 1
413
+ };
414
+ }
317
415
  return {
318
416
  ok: true,
319
417
  claims,
@@ -373,8 +471,30 @@ async function checkNarrowing(parentClaims, granterDeviceId, candidate) {
373
471
  const failedSystem = await evaluateNarrowing(parentClaims, granterDeviceId, candidate);
374
472
  return failedSystem === void 0 ? void 0 : NARROWING_SYSTEM_TO_MINT_REFUSAL[failedSystem];
375
473
  }
474
+ /** The authorised-by link's own arithmetic (wire-mesh#323), the analogue checkNarrowing already is for the parent link: everything tokens.cddl's authorised-by obligations require, checked against a not-yet-minted candidate, shared between mintCapabilityToken and canGrantVia so the two can never drift. The authoriser's conditions are evaluated in subject mode against the candidate, the same evaluation the verifier performs against the minted child. Returns the specific refusal reason, or undefined when the authorisation holds. */
475
+ async function checkAuthorisation(authorisingClaims, minterDeviceId, candidate) {
476
+ if (authorisingClaims.parent !== void 0) return "authorisation_parent_forbidden";
477
+ if (!require_token_scope.bytesEqual(authorisingClaims.bearer, minterDeviceId)) return "authorisation_bearer_mismatch";
478
+ const grantsCapability = authorisingClaims["grants-capability"];
479
+ if (authorisingClaims.capability !== "manage:grant" || grantsCapability !== void 0 && grantsCapability !== candidate.capability) return "authorisation_capability_mismatch";
480
+ if (!require_token_scope.scopeNarrows(authorisingClaims.scope, candidate.scope)) return "authorisation_scope_does_not_narrow";
481
+ if (candidate.expires > authorisingClaims.expires) return "authorisation_exceeds_authoriser";
482
+ const remaining = authorisingClaims["delegations-remaining"];
483
+ if (remaining !== void 0 && (candidate.delegationsRemaining === void 0 || candidate.delegationsRemaining >= remaining)) return "authorisation_delegation_exceeds";
484
+ if (authorisingClaims.conditions !== void 0) {
485
+ let decodedConditions;
486
+ try {
487
+ decodedConditions = (0, cbor2.decode)(authorisingClaims.conditions, cbor2.cdeDecodeOptions);
488
+ } catch {
489
+ return "authorisation_malformed";
490
+ }
491
+ const conditionsResult = conditionsListSchema.safeParse(decodedConditions);
492
+ if (!conditionsResult.success) return "authorisation_malformed";
493
+ if (!(await evaluateConditions(conditionsResult.data, authorisingClaims, { now: () => 0 }, {}, candidate)).ok) return "authorisation_conditions_not_satisfied";
494
+ }
495
+ }
376
496
  /**
377
- * A pure query: could deviceId, presenting heldToken as its own delegation authority, successfully mint a delegation matching candidate right now -- without attempting (and potentially failing) a real mint just to find out. Reuses mintCapabilityToken's own narrowing arithmetic via checkNarrowing, so the two can never silently drift into different ideas of what "narrows" means.
497
+ * A pure query: could deviceId, presenting heldToken as its own delegation authority," successfully mint a delegation matching candidate right now -- without attempting (and potentially failing) a real mint just to find out. Reuses mintCapabilityToken's own narrowing arithmetic via checkNarrowing, so the two can never silently drift into different ideas of what "narrows" means.
378
498
  *
379
499
  * Deliberately narrower than a full mint attempt in one respect: this checks only the narrowing rules tokens.cddl's own delegation obligations require (bearer match, expiry, scope, capability, delegations-remaining), the same scope mintCapabilityToken itself checks a *parent* against -- it does not verify heldToken's own signature or revocation status, exactly as mintCapabilityToken never re-verifies its own parent's signature either. A caller that also needs heldToken's cryptographic validity confirmed calls verifyCapabilityToken separately.
380
500
  *
@@ -386,6 +506,13 @@ async function canGrant(heldToken, deviceId, candidate, now) {
386
506
  if (heldClaims === void 0) return false;
387
507
  return await checkNarrowing(heldClaims, deviceId, candidate) === void 0;
388
508
  }
509
+ /** A pure query: could deviceId, presenting heldGrantCapability as its minting authority, mint a token matching candidate right now (wire-mesh#323) -- canGrant's authorised-by analogue, with the same deliberately-narrower-than-verify caveat canGrant's own doc comment states: the held token's signature and revocation status are not checked here; a caller needing those confirmed calls verifyCapabilityToken separately. The candidate carries a bearer because the authoriser's subject-mode conditions read it (the no-self-grant bar). */
510
+ async function canGrantVia(heldGrantCapability, deviceId, candidate, now) {
511
+ if (candidate.expires <= now) return false;
512
+ const heldClaims = decodeTokenClaims(heldGrantCapability);
513
+ if (heldClaims === void 0) return false;
514
+ return await checkAuthorisation(heldClaims, deviceId, candidate) === void 0;
515
+ }
389
516
  /**
390
517
  * Mints one capability token: builds token-claims from the given fields, signs it as a COSE_Sign1 under `identity`'s own key, with a protected header matching what the frozen conformance vectors actually encode (`{1: alg, 4: issuer device-id}`, not the empty header a token merely needs to verify against itself).
391
518
  *
@@ -396,6 +523,14 @@ async function mintCapabilityToken(options) {
396
523
  ok: false,
397
524
  reason: "already_expired"
398
525
  };
526
+ if (options.parent !== void 0 && options.authorisedBy !== void 0) return {
527
+ ok: false,
528
+ reason: "links_exclusive"
529
+ };
530
+ if (options.capability === "manage:grant" && options.parent !== void 0) return {
531
+ ok: false,
532
+ reason: "authorisation_parent_forbidden"
533
+ };
399
534
  let parentBytes;
400
535
  if (options.parent !== void 0) {
401
536
  const parentClaims = decodeTokenClaims(options.parent);
@@ -415,6 +550,26 @@ async function mintCapabilityToken(options) {
415
550
  };
416
551
  parentBytes = encodeBuf(options.parent);
417
552
  }
553
+ let authorisationBytes;
554
+ if (options.authorisedBy !== void 0) {
555
+ const authorisingClaims = decodeTokenClaims(options.authorisedBy);
556
+ if (authorisingClaims === void 0) return {
557
+ ok: false,
558
+ reason: "authorisation_malformed"
559
+ };
560
+ const refusal = await checkAuthorisation(authorisingClaims, options.identity.deviceId, {
561
+ capability: options.capability,
562
+ scope: options.scope,
563
+ bearer: options.bearer,
564
+ expires: options.expires,
565
+ ...options.delegationsRemaining !== void 0 ? { delegationsRemaining: options.delegationsRemaining } : {}
566
+ });
567
+ if (refusal !== void 0) return {
568
+ ok: false,
569
+ reason: refusal
570
+ };
571
+ authorisationBytes = encodeBuf(options.authorisedBy);
572
+ }
418
573
  const payload = encodeBuf({
419
574
  "token-id": options.tokenId,
420
575
  issuer: options.identity.deviceId,
@@ -426,7 +581,10 @@ async function mintCapabilityToken(options) {
426
581
  ...options.notBefore !== void 0 ? { "not-before": options.notBefore } : {},
427
582
  ...parentBytes !== void 0 ? { parent: parentBytes } : {},
428
583
  ...options.delegationsRemaining !== void 0 ? { "delegations-remaining": options.delegationsRemaining } : {},
429
- ...options.conditions !== void 0 ? { conditions: encodeBuf(options.conditions) } : {}
584
+ ...options.conditions !== void 0 ? { conditions: encodeBuf(options.conditions) } : {},
585
+ ...authorisationBytes !== void 0 ? { "authorised-by": authorisationBytes } : {},
586
+ ...options.grantsCapability !== void 0 ? { "grants-capability": options.grantsCapability } : {},
587
+ ...options.requestsCapability !== void 0 ? { "requests-capability": options.requestsCapability } : {}
430
588
  });
431
589
  const protectedHeader = protectedHeaderFor(options.identity);
432
590
  return {
@@ -456,12 +614,24 @@ async function mintRevocationEntry(options) {
456
614
  ];
457
615
  }
458
616
  //#endregion
617
+ Object.defineProperty(exports, "MANAGE_GRANT_CAPABILITY", {
618
+ enumerable: true,
619
+ get: function() {
620
+ return MANAGE_GRANT_CAPABILITY;
621
+ }
622
+ });
459
623
  Object.defineProperty(exports, "canGrant", {
460
624
  enumerable: true,
461
625
  get: function() {
462
626
  return canGrant;
463
627
  }
464
628
  });
629
+ Object.defineProperty(exports, "canGrantVia", {
630
+ enumerable: true,
631
+ get: function() {
632
+ return canGrantVia;
633
+ }
634
+ });
465
635
  Object.defineProperty(exports, "mintCapabilityToken", {
466
636
  enumerable: true,
467
637
  get: function() {
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, N as Frame } from "./protocol-DCDae3z4.mjs";
1
+ import { D as DeviceId, N as Frame } from "./protocol-BgR1e_NI.cjs";
2
2
  //#region src/ports/transport.d.ts
3
3
  /**
4
4
  * An async, serialisable-data contract for exchanging frame values over some connection -- deliberately without baking in any specific transport's own primitives (no raw socket types, no WebSocket-specific options), so a TCP adapter, a WebSocket adapter, or an in-memory adapter for tests can all satisfy it.
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, N as Frame } from "./protocol-DCDae3z4.cjs";
1
+ import { D as DeviceId, N as Frame } from "./protocol-BgR1e_NI.mjs";
2
2
  //#region src/ports/transport.d.ts
3
3
  /**
4
4
  * An async, serialisable-data contract for exchanging frame values over some connection -- deliberately without baking in any specific transport's own primitives (no raw socket types, no WebSocket-specific options), so a TCP adapter, a WebSocket adapter, or an in-memory adapter for tests can all satisfy it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wire-mesh-core",
3
- "version": "3.7.1",
3
+ "version": "3.8.0",
4
4
  "dependencies": {
5
5
  "cbor2": "2.3.0",
6
6
  "cddl.js": "1.0.1",