@tribe-nest/media-protocol 1.0.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 (84) hide show
  1. package/build/_fixtures/joinTokenVectors.json +103 -0
  2. package/build/claims.d.ts +327 -0
  3. package/build/claims.d.ts.map +1 -0
  4. package/build/claims.js +91 -0
  5. package/build/claims.js.map +1 -0
  6. package/build/codecs.d.ts +102 -0
  7. package/build/codecs.d.ts.map +1 -0
  8. package/build/codecs.js +138 -0
  9. package/build/codecs.js.map +1 -0
  10. package/build/errorSchema.d.ts +8 -0
  11. package/build/errorSchema.d.ts.map +1 -0
  12. package/build/errorSchema.js +12 -0
  13. package/build/errorSchema.js.map +1 -0
  14. package/build/errors.d.ts +18 -0
  15. package/build/errors.d.ts.map +1 -0
  16. package/build/errors.js +47 -0
  17. package/build/errors.js.map +1 -0
  18. package/build/frames.d.ts +1291 -0
  19. package/build/frames.d.ts.map +1 -0
  20. package/build/frames.js +222 -0
  21. package/build/frames.js.map +1 -0
  22. package/build/grants.d.ts +307 -0
  23. package/build/grants.d.ts.map +1 -0
  24. package/build/grants.js +228 -0
  25. package/build/grants.js.map +1 -0
  26. package/build/index.d.ts +24 -0
  27. package/build/index.d.ts.map +1 -0
  28. package/build/index.js +40 -0
  29. package/build/index.js.map +1 -0
  30. package/build/mp-build/_fixtures/joinTokenVectors.json +103 -0
  31. package/build/mp-build/claims.d.ts +299 -0
  32. package/build/mp-build/claims.d.ts.map +1 -0
  33. package/build/mp-build/claims.js +60 -0
  34. package/build/mp-build/claims.js.map +1 -0
  35. package/build/mp-build/errorSchema.d.ts +8 -0
  36. package/build/mp-build/errorSchema.d.ts.map +1 -0
  37. package/build/mp-build/errorSchema.js +12 -0
  38. package/build/mp-build/errorSchema.js.map +1 -0
  39. package/build/mp-build/errors.d.ts +18 -0
  40. package/build/mp-build/errors.d.ts.map +1 -0
  41. package/build/mp-build/errors.js +47 -0
  42. package/build/mp-build/errors.js.map +1 -0
  43. package/build/mp-build/frames.d.ts +786 -0
  44. package/build/mp-build/frames.d.ts.map +1 -0
  45. package/build/mp-build/frames.js +153 -0
  46. package/build/mp-build/frames.js.map +1 -0
  47. package/build/mp-build/grants.d.ts +245 -0
  48. package/build/mp-build/grants.d.ts.map +1 -0
  49. package/build/mp-build/grants.js +135 -0
  50. package/build/mp-build/grants.js.map +1 -0
  51. package/build/mp-build/index.d.ts +23 -0
  52. package/build/mp-build/index.d.ts.map +1 -0
  53. package/build/mp-build/index.js +39 -0
  54. package/build/mp-build/index.js.map +1 -0
  55. package/build/mp-build/token.d.ts +66 -0
  56. package/build/mp-build/token.d.ts.map +1 -0
  57. package/build/mp-build/token.js +208 -0
  58. package/build/mp-build/token.js.map +1 -0
  59. package/build/mp-build/version.d.ts +9 -0
  60. package/build/mp-build/version.d.ts.map +1 -0
  61. package/build/mp-build/version.js +12 -0
  62. package/build/mp-build/version.js.map +1 -0
  63. package/build/token.d.ts +66 -0
  64. package/build/token.d.ts.map +1 -0
  65. package/build/token.js +208 -0
  66. package/build/token.js.map +1 -0
  67. package/build/version.d.ts +9 -0
  68. package/build/version.d.ts.map +1 -0
  69. package/build/version.js +12 -0
  70. package/build/version.js.map +1 -0
  71. package/package.json +53 -0
  72. package/src/_fixtures/joinTokenVectors.json +103 -0
  73. package/src/_tests/codecs.spec.ts +66 -0
  74. package/src/_tests/publishGrants.spec.ts +109 -0
  75. package/src/_tests/token.spec.ts +272 -0
  76. package/src/claims.ts +116 -0
  77. package/src/codecs.ts +165 -0
  78. package/src/errorSchema.ts +9 -0
  79. package/src/errors.ts +44 -0
  80. package/src/frames.ts +234 -0
  81. package/src/grants.ts +248 -0
  82. package/src/index.ts +23 -0
  83. package/src/token.ts +237 -0
  84. package/src/version.ts +8 -0
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mediaGrantsSchema = exports.publishSourceSchema = exports.publishKindSchema = exports.transportKindSchema = exports.participantKindSchema = exports.subscribeRuleSchema = void 0;
4
+ exports.requiredPublishKind = requiredPublishKind;
5
+ exports.mayPublish = mayPublish;
6
+ exports.maySubscribe = maySubscribe;
7
+ exports.grantsAreValidForKind = grantsAreValidForKind;
8
+ const zod_1 = require("zod");
9
+ /**
10
+ * What a participant is allowed to do, and - the part that matters most - who
11
+ * they may receive.
12
+ *
13
+ * ## Why the schema is `.strict()`
14
+ *
15
+ * This is the single line that stops the information barrier failing open.
16
+ *
17
+ * Ten phase plans were written in parallel and two of them disagreed on a name:
18
+ * one minted `grants.subscribeOnly`, the other read `grants.subscribe`. The
19
+ * reader would have found nothing, applied its documented default ("an absent
20
+ * rule means everyone"), and two Mandanten in one room would have received each
21
+ * other with nothing anywhere erroring. That is a §203 incident whose start
22
+ * date is unrecoverable from logs.
23
+ *
24
+ * `.strict()` turns that class of mistake from silent permissiveness into a
25
+ * loud verification failure: a token carrying an unknown grant key is rejected
26
+ * with `bad_claims` rather than partially understood. Any future rename fails
27
+ * the same way, at mint time, in a test.
28
+ */
29
+ /**
30
+ * Who a participant may receive, or be received by.
31
+ *
32
+ * A boolean cannot express an information barrier. "A and B are in one room and
33
+ * must not receive each other, while C may receive both" is the actual
34
+ * requirement for a Kanzlei, and it is a per-identity rule rather than a
35
+ * per-room one.
36
+ */
37
+ exports.subscribeRuleSchema = zod_1.z.discriminatedUnion("mode", [
38
+ zod_1.z.object({ mode: zod_1.z.literal("all") }).strict(),
39
+ zod_1.z.object({ mode: zod_1.z.literal("none") }).strict(),
40
+ zod_1.z.object({ mode: zod_1.z.literal("allow"), identities: zod_1.z.array(zod_1.z.string()).max(500) }).strict(),
41
+ zod_1.z.object({ mode: zod_1.z.literal("deny"), identities: zod_1.z.array(zod_1.z.string()).max(500) }).strict(),
42
+ ]);
43
+ exports.participantKindSchema = zod_1.z.enum(["human", "agent", "egress", "sip", "ingress"]);
44
+ /**
45
+ * Transport authority is NOT media authority, which is why this is its own
46
+ * claim rather than something inferred from `kind`.
47
+ *
48
+ * A browser holding a perfectly valid join token must not be able to ask for a
49
+ * `PlainTransport` pointed at an address of its choosing. That is an RTP
50
+ * reflector and an unauthenticated publish path into whatever room the token
51
+ * names. Three phases independently needed to refuse it and none could, because
52
+ * nothing carried the fact.
53
+ */
54
+ exports.transportKindSchema = zod_1.z.enum(["webrtc", "plain", "pipe"]);
55
+ /**
56
+ * The two vocabularies, and the ONE place they are reconciled.
57
+ *
58
+ * A grant is written in kinds - "audio", "video", "screen" - because that is
59
+ * what a person minting a token thinks in: may this seat talk, may it be seen,
60
+ * may it present. A publisher labels its track with a SOURCE, because that is
61
+ * what it knows and what the other side renders: "microphone", "camera",
62
+ * "screen", "rtmp". The two lists overlap on one word and disagree on the rest.
63
+ *
64
+ * That disagreement shipped as a defect. The node compared the declared source
65
+ * against `publishKinds` directly, so a booking token minted with the full
66
+ * `["audio", "video", "screen"]` refused every microphone and every camera the
67
+ * browser SDK offered it - "microphone" is not in the list - and the only thing
68
+ * a coaching call could publish was a screen share. It refused the RTMP ingress
69
+ * for the same reason, whose source is "rtmp".
70
+ *
71
+ * So the translation lives here, next to the schema that creates the need for
72
+ * it, and both the node and the UI read it. An unrecognised source falls back to
73
+ * the TRACK's kind, which is a fact about the media rather than a claim: that is
74
+ * what lets a new source ("rtmp") work without a protocol change while a token
75
+ * granting audio only still cannot send video by inventing a label for it. The
76
+ * one thing a client can never do is send a screen share on an audio-or-video
77
+ * grant, because "screen" is only ever reachable by declaring it.
78
+ *
79
+ * ## A declaration is only honoured when the track could carry it
80
+ *
81
+ * The label is the client's claim; the track's kind is a fact mediasoup already
82
+ * knows. Translating the claim UNCONDITIONALLY made the claim outrank the fact,
83
+ * which is the bypass restated: `{ source: "microphone", kind: "video" }`
84
+ * resolved to "audio", so a seat minted `publishKinds: ["audio"]` published a
85
+ * camera and the node created a video producer with the real kind. The two
86
+ * audio-only grants in the product are a SIP gateway and an assistant runtime,
87
+ * and `publishKinds` is precisely the claim that has to survive the holder being
88
+ * compromised.
89
+ *
90
+ * So a source is translated only when it can actually carry the track in hand
91
+ * (a microphone carries audio, a camera carries video, a screen share carries
92
+ * either, because a shared window may bring system audio with it). A declaration
93
+ * the track contradicts is not a translation, it is a relabelling, and it is
94
+ * discarded in favour of the fact.
95
+ */
96
+ exports.publishKindSchema = zod_1.z.enum(["audio", "video", "screen"]);
97
+ /** The sources the browser SDK publishes with. Others are legal on the wire. */
98
+ exports.publishSourceSchema = zod_1.z.enum(["microphone", "camera", "screen"]);
99
+ const SOURCE_KINDS = {
100
+ microphone: "audio",
101
+ camera: "video",
102
+ screen: "screen",
103
+ };
104
+ /**
105
+ * What each source is physically able to carry.
106
+ *
107
+ * A screen share holds both, because sharing a window may bring its system
108
+ * audio; a microphone and a camera hold exactly one each. Anything outside this
109
+ * is a label that disagrees with the media, and the media wins.
110
+ */
111
+ const SOURCE_TRACK_KINDS = {
112
+ microphone: ["audio"],
113
+ camera: ["video"],
114
+ screen: ["audio", "video"],
115
+ };
116
+ /** Which grant a publish needs, from what it declares and what it carries. */
117
+ function requiredPublishKind(source, trackKind) {
118
+ const declared = source ? SOURCE_KINDS[source] : undefined;
119
+ if (!declared)
120
+ return trackKind;
121
+ // The declaration is honoured only where the track bears it out. A camera on
122
+ // an audio track, or a microphone on a video one, is a relabelling, and
123
+ // falling back to the track's own kind is what stops it buying a grant the
124
+ // token was never given.
125
+ if (!SOURCE_TRACK_KINDS[source].includes(trackKind))
126
+ return trackKind;
127
+ return declared;
128
+ }
129
+ /**
130
+ * May this token publish this? The node's decision, and the UI's hint, from one
131
+ * function so the button and the enforcement cannot disagree.
132
+ */
133
+ function mayPublish(grants, input) {
134
+ if (!grants?.canPublish)
135
+ return false;
136
+ if (!grants.publishKinds)
137
+ return true;
138
+ return grants.publishKinds.includes(requiredPublishKind(input.source, input.trackKind));
139
+ }
140
+ exports.mediaGrantsSchema = zod_1.z
141
+ .object({
142
+ canPublish: zod_1.z.boolean(),
143
+ canSubscribe: zod_1.z.boolean(),
144
+ canPublishData: zod_1.z.boolean(),
145
+ /** Who I may receive. Omitted means `{ mode: "all" }`. */
146
+ subscribe: exports.subscribeRuleSchema.optional(),
147
+ /**
148
+ * Who may receive ME. Enforced on the PRODUCER side from the owner's stored
149
+ * grants, so a barrier holds even if the other party's token is generous.
150
+ */
151
+ visibleTo: exports.subscribeRuleSchema.optional(),
152
+ /**
153
+ * Which kinds of media this token may send, in the vocabulary of
154
+ * `publishKindSchema`. A publisher labels its track with a SOURCE
155
+ * ("microphone", "camera", "screen", "rtmp", ...) and those are not the same
156
+ * words: `requiredPublishKind` below is the one translation between them.
157
+ */
158
+ publishKinds: zod_1.z.array(exports.publishKindSchema).optional(),
159
+ /**
160
+ * Bypasses the top-N active-speaker refusal. Mintable ONLY for a non-human
161
+ * kind: a recorder needs every track, a human asking for 1,000 does not.
162
+ */
163
+ subscribeExempt: zod_1.z.boolean().optional(),
164
+ /** Hard ceiling on concurrent consumers, for a non-human participant. */
165
+ maxSubscriptions: zod_1.z.number().int().positive().max(5000).optional(),
166
+ })
167
+ .strict();
168
+ /** An omitted rule means everyone. Stated once so both sides agree. */
169
+ const DEFAULT_RULE = { mode: "all" };
170
+ function ruleAllows(rule, identity) {
171
+ const r = rule ?? DEFAULT_RULE;
172
+ switch (r.mode) {
173
+ case "all":
174
+ return true;
175
+ case "none":
176
+ return false;
177
+ case "allow":
178
+ return r.identities.includes(identity);
179
+ case "deny":
180
+ return !r.identities.includes(identity);
181
+ }
182
+ }
183
+ /**
184
+ * May `consumer` receive `producer`? Two-sided, and deny wins.
185
+ *
186
+ * One-sided is not enough. If only the consumer's rule were consulted, a
187
+ * participant whose own token was minted generously could receive someone who
188
+ * had been explicitly walled off from them. So the producer's `visibleTo` is
189
+ * checked from the OWNER's stored grants, not from anything the consumer
190
+ * presents.
191
+ *
192
+ * **An unreadable owner is `false`.** If the producer's grants cannot be found,
193
+ * this refuses rather than falling through to the permissive default. A missing
194
+ * record is exactly the state a race or a partial failure produces, and in a
195
+ * room with an information barrier the safe reading of "I do not know" is "no".
196
+ * Note this differs deliberately from an ABSENT RULE on a record that WAS
197
+ * found, which means everyone.
198
+ */
199
+ function maySubscribe(input) {
200
+ if (!input.consumerGrants.canSubscribe)
201
+ return false;
202
+ // Nobody subscribes to themselves; asking is a client bug, not a denial.
203
+ if (input.consumerIdentity === input.producerIdentity)
204
+ return false;
205
+ if (!input.ownerGrants)
206
+ return false;
207
+ return (ruleAllows(input.consumerGrants.subscribe, input.producerIdentity) &&
208
+ ruleAllows(input.ownerGrants.visibleTo, input.consumerIdentity));
209
+ }
210
+ /**
211
+ * `subscribeExempt` and `maxSubscriptions` are meaningless for a human and
212
+ * dangerous if mintable for one: exemption is what lets a recorder take every
213
+ * track, and a human with it can pull a thousand streams through a node.
214
+ * Checked at MINT time so a bad token never exists.
215
+ */
216
+ function grantsAreValidForKind(grants, kind) {
217
+ if (kind === "human" && grants.subscribeExempt) {
218
+ return "subscribeExempt may not be minted for a human participant";
219
+ }
220
+ if (!grants.canSubscribe && (grants.subscribe || grants.subscribeExempt || grants.maxSubscriptions)) {
221
+ return "subscribe rules are meaningless when canSubscribe is false";
222
+ }
223
+ if (!grants.canPublish && grants.publishKinds?.length) {
224
+ return "publishKinds is meaningless when canPublish is false";
225
+ }
226
+ return true;
227
+ }
228
+ //# sourceMappingURL=grants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grants.js","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The contract between the backend, the media plane and every client.
3
+ *
4
+ * The root export is ISOMORPHIC: types, zod schemas, wire frames and the pure
5
+ * `maySubscribe` decision. It pulls in no `node:crypto`, no mediasoup, no DOM,
6
+ * so a browser SDK can import it.
7
+ *
8
+ * Signing and verifying live at `@tribe-nest/media-protocol/token`, which is
9
+ * Node-only. Keeping them apart is what stops importing a frame type from
10
+ * dragging crypto into a browser bundle.
11
+ *
12
+ * This package exists because `apps/backend` and `apps/media-server` may not
13
+ * import each other (there is a test), and six independently written phase
14
+ * plans each invented a different wire envelope for want of somewhere neutral
15
+ * to put one.
16
+ */
17
+ export * from "./version";
18
+ export * from "./errors";
19
+ export * from "./errorSchema";
20
+ export * from "./grants";
21
+ export * from "./claims";
22
+ export * from "./frames";
23
+ export * from "./codecs";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * The contract between the backend, the media plane and every client.
19
+ *
20
+ * The root export is ISOMORPHIC: types, zod schemas, wire frames and the pure
21
+ * `maySubscribe` decision. It pulls in no `node:crypto`, no mediasoup, no DOM,
22
+ * so a browser SDK can import it.
23
+ *
24
+ * Signing and verifying live at `@tribe-nest/media-protocol/token`, which is
25
+ * Node-only. Keeping them apart is what stops importing a frame type from
26
+ * dragging crypto into a browser bundle.
27
+ *
28
+ * This package exists because `apps/backend` and `apps/media-server` may not
29
+ * import each other (there is a test), and six independently written phase
30
+ * plans each invented a different wire envelope for want of somewhere neutral
31
+ * to put one.
32
+ */
33
+ __exportStar(require("./version"), exports);
34
+ __exportStar(require("./errors"), exports);
35
+ __exportStar(require("./errorSchema"), exports);
36
+ __exportStar(require("./grants"), exports);
37
+ __exportStar(require("./claims"), exports);
38
+ __exportStar(require("./frames"), exports);
39
+ __exportStar(require("./codecs"), exports);
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;GAeG;AACH,4CAA0B;AAC1B,2CAAyB;AACzB,gDAA8B;AAC9B,2CAAyB;AACzB,2CAAyB;AACzB,2CAAyB;AACzB,2CAAyB"}
@@ -0,0 +1,103 @@
1
+ {
2
+ "_comment": "GENERATED by scripts/generateVectors.ts - do not hand-edit. One fixture read by BOTH the backend minter spec and the media-server verifier spec, so the two cannot drift. The keys are test keys and are committed on purpose.",
3
+ "nowSeconds": 1760000000,
4
+ "issuer": "legal-de",
5
+ "kid": "test-v1",
6
+ "keys": {
7
+ "test-v1": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEADDA6KSONGhJqPWozH6py1Gu6M53ZpupI9cLMN/RZtws=\n-----END PUBLIC KEY-----\n"
8
+ },
9
+ "privateKeyForMinterTests": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIO8H96J+s7hghw0TR4cDCVjjaBwMciXpPte0hx7WSFf3\n-----END PRIVATE KEY-----\n",
10
+ "vectors": [
11
+ {
12
+ "name": "valid_human",
13
+ "why": "the ordinary case: a human joining over webrtc",
14
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDAwMzAwfQ.cHwZ_nFkDQoHVtB6-mA2Rg-KcuVTralgw1cS908OQh7_8JUOKAP9l6kFiKpmfHFMEz6aBt7yXeIRKnBvTfJlBw",
15
+ "expect": "ok"
16
+ },
17
+ {
18
+ "name": "valid_agent_exempt",
19
+ "why": "a non-human may be exempt from the active-set ceiling; a human may not",
20
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoidHJhbnNjcmliZXIiLCJraW5kIjoiYWdlbnQiLCJ0cmFuc3BvcnQiOiJwbGFpbiIsImdyYW50cyI6eyJjYW5QdWJsaXNoIjpmYWxzZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6ZmFsc2UsInN1YnNjcmliZUV4ZW1wdCI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDAwMzAwfQ.Mc3KKrczwe0j6YYpe-HXBiYn5C8CeOhYaE2HI64tANDzhucO0BxAgTg9fTdJAGKW8W9RymwV6j83HRWZr854AQ",
21
+ "expect": "ok"
22
+ },
23
+ {
24
+ "name": "valid_barrier",
25
+ "why": "an information barrier: alice may receive only carol, and only carol may receive alice",
26
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZSwic3Vic2NyaWJlIjp7Im1vZGUiOiJhbGxvdyIsImlkZW50aXRpZXMiOlsiY2Fyb2wiXX0sInZpc2libGVUbyI6eyJtb2RlIjoiYWxsb3ciLCJpZGVudGl0aWVzIjpbImNhcm9sIl19fSwianRpIjoianRpLTAwMDAwMDAxIiwiaWF0IjoxNzYwMDAwMDAwLCJleHAiOjE3NjAwMDAzMDB9.1jx23AACBL-tvtUFSUGQAjjBCuEs6pv7MHSeqAfy7HY-ki65Rf-YadxsPk5Nzg-OrOopu08J6m_Wdm5YA3TMDw",
27
+ "expect": "ok"
28
+ },
29
+ {
30
+ "name": "unknown_grant_key",
31
+ "why": "THE fail-open case. One phase minted `subscribeOnly` while another read `subscribe`; a lenient schema would have read 'no rule' as 'everyone' and put two Mandanten in one room.",
32
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZSwic3Vic2NyaWJlT25seSI6WyJjYXJvbCJdfSwianRpIjoianRpLTAwMDAwMDAxIiwiaWF0IjoxNzYwMDAwMDAwLCJleHAiOjE3NjAwMDAzMDB9.uda5k_gP-zizkivazZkx1QoSj-54DgnTzCMoTBZ-Pzfyn1jZdw49IvPbAD-88yOfn7ukVYCOko0DRHkxmfu8Aw",
33
+ "expect": "bad_claims"
34
+ },
35
+ {
36
+ "name": "human_with_subscribe_exempt",
37
+ "why": "exemption bypasses the active-set ceiling; a human holding it can pull every track in a 1,000-person room",
38
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZSwic3Vic2NyaWJlRXhlbXB0Ijp0cnVlfSwianRpIjoianRpLTAwMDAwMDAxIiwiaWF0IjoxNzYwMDAwMDAwLCJleHAiOjE3NjAwMDAzMDB9.o34OTK5D0qRD5enDABkljr_aFerqzoZYLR_uqgTKwO1DsszjDkIG8gUxvjucYZ9apvwWIq3tuwAPVRSAotqYCA",
39
+ "expect": "bad_claims"
40
+ },
41
+ {
42
+ "name": "expired",
43
+ "why": "a join ticket outlives its window",
44
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc1OTk5OTQwMCwiZXhwIjoxNzU5OTk5NzAwfQ.hTM23tP-4IP044XhjXNSmzpXMWzi1GC7LrV5vLN_H1SnnI0ZFgLmg8VTW34cPbZy6yIs8mXRZT8uNJ6xsCAQDQ",
45
+ "expect": "expired"
46
+ },
47
+ {
48
+ "name": "not_yet_valid",
49
+ "why": "nbf in the future",
50
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwibmJmIjoxNzYwMDAwNjAwLCJleHAiOjE3NjAwMDA5MDB9.xpFjHBt5XYyG6pP9_3j5C-h0FS5Y7byfo_M8qkkGn33iK9i4qO3L_SzFxYebTEoX-jmlTu8Y8MCPlhLzb3MFCw",
51
+ "expect": "not_yet_valid"
52
+ },
53
+ {
54
+ "name": "ttl_too_long",
55
+ "why": "enforced at VERIFY as well as mint, because a compromised minter would simply skip the mint check",
56
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDg2NDAwfQ.UCA0foITm9CL3z1ZslwGf4Dvmqe0xkOpYhN9l13-DKRPjje6pGez4jcvhKDGwPuy8f7lovvUfoRwCWG4UGYWCg",
57
+ "expect": "ttl_too_long"
58
+ },
59
+ {
60
+ "name": "wrong_issuer",
61
+ "why": "a token minted for the main instance must be worthless on the legal one",
62
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJtYWluIiwiYXVkIjoibWVkaWEiLCJyb29tIjoicm9vbS0xIiwiaWRlbnRpdHkiOiJhbGljZSIsImtpbmQiOiJodW1hbiIsInRyYW5zcG9ydCI6IndlYnJ0YyIsImdyYW50cyI6eyJjYW5QdWJsaXNoIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlfSwianRpIjoianRpLTAwMDAwMDAxIiwiaWF0IjoxNzYwMDAwMDAwLCJleHAiOjE3NjAwMDAzMDB9.Z33LRIVYrY7KlvUUsQCZ52x7HBxPKWBuJSKySb6d0Ncmenu8CpFSxC2JRm_s7NPbuMwBymgQjKUiFmTeUm_FCA",
63
+ "expect": "wrong_issuer"
64
+ },
65
+ {
66
+ "name": "wrong_audience_control_as_join",
67
+ "why": "a control token replayed as a join",
68
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6ImNvbnRyb2wiLCJqdGkiOiJqdGktY29udHJvbC0xIiwiaWF0IjoxNzYwMDAwMDAwLCJleHAiOjE3NjAwMDAzMDB9.q9LAV0vxFy95CqICQ9jl5dm7ll_iD8HSDADK0cQu85wJxVx1roQL-RZggjcrxhShH2uzfKufTW9211pF56caDA",
69
+ "expect": "wrong_audience"
70
+ },
71
+ {
72
+ "name": "unknown_kid",
73
+ "why": "signed by a key this node does not carry",
74
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InJvdGF0ZWQtYXdheSJ9.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDAwMzAwfQ.W_BZBx_p9W245Xw89H3Dy_MEYf5JfNb8KpSeLok4ZjYUc_EWx8wxvghDrWjaidXp6yxOK3Su_r_b_QHOis7tCA",
75
+ "expect": "unknown_kid"
76
+ },
77
+ {
78
+ "name": "bad_signature",
79
+ "why": "correct kid, wrong key - a forgery attempt rather than a rotation",
80
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDAwMzAwfQ.Chdv_Jl7v6HTE5u1TpP6f7j2mqFPocPKfBSdwgbL_lNIN708mHHA5oGeR8AOjPakn16WLTW6jODEUnbj_KTLAA",
81
+ "expect": "bad_signature"
82
+ },
83
+ {
84
+ "name": "alg_none",
85
+ "why": "classic JWT algorithm confusion. Only EdDSA is ever accepted.",
86
+ "token": "eyJhbGciOiJub25lIiwidHlwIjoiSldUIiwia2lkIjoidGVzdC12MSJ9.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMSIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDAwMzAwfQ.",
87
+ "expect": "malformed"
88
+ },
89
+ {
90
+ "name": "wrong_room",
91
+ "why": "a valid token for a different room",
92
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtdjEifQ.eyJ2IjoxLCJpc3MiOiJsZWdhbC1kZSIsImF1ZCI6Im1lZGlhIiwicm9vbSI6InJvb20tMiIsImlkZW50aXR5IjoiYWxpY2UiLCJraW5kIjoiaHVtYW4iLCJ0cmFuc3BvcnQiOiJ3ZWJydGMiLCJncmFudHMiOnsiY2FuUHVibGlzaCI6dHJ1ZSwiY2FuU3Vic2NyaWJlIjp0cnVlLCJjYW5QdWJsaXNoRGF0YSI6dHJ1ZX0sImp0aSI6Imp0aS0wMDAwMDAwMSIsImlhdCI6MTc2MDAwMDAwMCwiZXhwIjoxNzYwMDAwMzAwfQ.EWEv-Np5ECbj4TZXt3KTgcPUHEoDM2G4Wx-M0L25pTkE0onIRMm5vmbVBypULUtIWtKxOxsdDNBezcl8Nqy8Bg",
93
+ "expect": "wrong_room",
94
+ "room": "room-1"
95
+ },
96
+ {
97
+ "name": "malformed",
98
+ "why": "not three segments",
99
+ "token": "not.a.token.at.all",
100
+ "expect": "malformed"
101
+ }
102
+ ]
103
+ }