@xyo-network/event-kit-protocol 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/neutral/admissionCore.d.ts +9 -1
- package/dist/neutral/admissionCore.d.ts.map +1 -1
- package/dist/neutral/admitAndEnqueue.d.ts.map +1 -1
- package/dist/neutral/bestEffortAdmission.d.ts +113 -0
- package/dist/neutral/bestEffortAdmission.d.ts.map +1 -0
- package/dist/neutral/endpointChallenge.d.ts.map +1 -1
- package/dist/neutral/handshakeConstruction.d.ts +5 -1
- package/dist/neutral/handshakeConstruction.d.ts.map +1 -1
- package/dist/neutral/index.d.ts +1 -0
- package/dist/neutral/index.d.ts.map +1 -1
- package/dist/neutral/index.mjs +543 -366
- package/dist/neutral/index.mjs.map +4 -4
- package/dist/neutral/lifecycleSelection.d.ts.map +1 -1
- package/dist/neutral/publisherSubscription.d.ts.map +1 -1
- package/dist/neutral/types.d.ts +4 -0
- package/dist/neutral/types.d.ts.map +1 -1
- package/dist/neutral/verifiedAdmissionWake.d.ts +3 -1
- package/dist/neutral/verifiedAdmissionWake.d.ts.map +1 -1
- package/dist/neutral/verifyWakeRequest.d.ts +10 -1
- package/dist/neutral/verifyWakeRequest.d.ts.map +1 -1
- package/dist/neutral/wakeSubscriptionEncoding.d.ts.map +1 -1
- package/package.json +9 -9
package/dist/neutral/index.mjs
CHANGED
|
@@ -12,9 +12,8 @@ async function admitVerifiedWake(input) {
|
|
|
12
12
|
if (!isNonnegativeSafeInteger(input.expectedHead)) {
|
|
13
13
|
return reject("grant-view-invalid", ["expectedHead must be a nonnegative safe integer"]);
|
|
14
14
|
}
|
|
15
|
-
const authorization = await
|
|
16
|
-
|
|
17
|
-
return await commitAuthorizedWake(input.inbox, attempt, authorization.grant, wake);
|
|
15
|
+
const authorization = await authorizeVerifiedWake(input);
|
|
16
|
+
return authorization.ok ? await commitAuthorizedWake(input.inbox, attempt, authorization.grant, wake) : authorization.failure;
|
|
18
17
|
}
|
|
19
18
|
async function resolvePriorAttempt(inbox, attempt, wake) {
|
|
20
19
|
let resolution;
|
|
@@ -33,10 +32,7 @@ async function resolvePriorAttempt(inbox, attempt, wake) {
|
|
|
33
32
|
const receiptFailure = validateReceipt(resolution.receipt, attempt);
|
|
34
33
|
return completed(receiptFailure ?? success("duplicate", snapshotReceipt(resolution.receipt), wake));
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
return completed(reject("inbox-unavailable", ["inbox returned an unknown attempt resolution"]));
|
|
38
|
-
}
|
|
39
|
-
return Object.freeze({ pending: true });
|
|
35
|
+
return resolution.kind === "new" ? Object.freeze({ pending: true }) : completed(reject("inbox-unavailable", ["inbox returned an unknown attempt resolution"]));
|
|
40
36
|
}
|
|
41
37
|
async function commitAuthorizedWake(inbox, attempt, grant, wake) {
|
|
42
38
|
let committed;
|
|
@@ -59,8 +55,7 @@ async function commitAuthorizedWake(inbox, attempt, grant, wake) {
|
|
|
59
55
|
return reject("inbox-unavailable", ["inbox returned an unknown commit result"]);
|
|
60
56
|
}
|
|
61
57
|
const receiptFailure = validateReceipt(committed.receipt, attempt);
|
|
62
|
-
|
|
63
|
-
return success(committed.kind, snapshotReceipt(committed.receipt), wake);
|
|
58
|
+
return receiptFailure ?? success(committed.kind, snapshotReceipt(committed.receipt), wake);
|
|
64
59
|
}
|
|
65
60
|
function completed(result) {
|
|
66
61
|
return Object.freeze({ pending: false, result });
|
|
@@ -84,7 +79,7 @@ function createAttempt(verified) {
|
|
|
84
79
|
ok: true
|
|
85
80
|
};
|
|
86
81
|
}
|
|
87
|
-
async function
|
|
82
|
+
async function authorizeVerifiedWake(input) {
|
|
88
83
|
const { route } = input.verified;
|
|
89
84
|
let decision;
|
|
90
85
|
try {
|
|
@@ -132,10 +127,7 @@ function validateGrantView(decision, expectedHead, maxGrantLagBlocks) {
|
|
|
132
127
|
if (decision.lag !== void 0 && decision.lag !== lag) {
|
|
133
128
|
return reject("grant-view-invalid", ["grant diagnostic lag disagrees with the recomputed lag"]);
|
|
134
129
|
}
|
|
135
|
-
|
|
136
|
-
return reject("grant-view-stale", [`grantLag=${lag} max=${maxGrantLagBlocks}`]);
|
|
137
|
-
}
|
|
138
|
-
return void 0;
|
|
130
|
+
return lag > maxGrantLagBlocks ? reject("grant-view-stale", [`grantLag=${lag} max=${maxGrantLagBlocks}`]) : void 0;
|
|
139
131
|
}
|
|
140
132
|
function validateGrantEvidence(evidence, verified, atBlock) {
|
|
141
133
|
if (!isRecord(evidence)) return reject("grant-view-invalid", ["enabled grant decision omitted evidence"]);
|
|
@@ -170,10 +162,7 @@ function validateGrantEvidence(evidence, verified, atBlock) {
|
|
|
170
162
|
}
|
|
171
163
|
const evidenceHasQueue = Object.hasOwn(evidence, "queueId");
|
|
172
164
|
const routeHasQueue = Object.hasOwn(verified.route, "queueId");
|
|
173
|
-
|
|
174
|
-
return reject("grant-view-invalid", ["grant scope does not exactly match the configured route"]);
|
|
175
|
-
}
|
|
176
|
-
return void 0;
|
|
165
|
+
return evidence.deploymentId !== verified.route.deploymentId || evidenceHasQueue !== routeHasQueue || evidence.queueId !== verified.route.queueId ? reject("grant-view-invalid", ["grant scope does not exactly match the configured route"]) : void 0;
|
|
177
166
|
}
|
|
178
167
|
function validateReceipt(receipt, attempt) {
|
|
179
168
|
const requiredFields = [
|
|
@@ -186,10 +175,7 @@ function validateReceipt(receipt, attempt) {
|
|
|
186
175
|
if (!isRecord(receipt) || requiredFields.some((field) => !Object.hasOwn(receipt, field)) || typeof receipt.id !== "string" || receipt.id.length === 0) {
|
|
187
176
|
return reject("inbox-unavailable", ["inbox returned an invalid receipt"]);
|
|
188
177
|
}
|
|
189
|
-
|
|
190
|
-
return reject("inbox-unavailable", ["inbox receipt is not bound to the verified attempt"]);
|
|
191
|
-
}
|
|
192
|
-
return void 0;
|
|
178
|
+
return receipt.audience !== attempt.audience || receipt.publisher !== attempt.publisher || receipt.eventHash !== attempt.eventHash || receipt.wakeId !== attempt.wakeId ? reject("inbox-unavailable", ["inbox receipt is not bound to the verified attempt"]) : void 0;
|
|
193
179
|
}
|
|
194
180
|
function snapshotGrantEvidence(evidence) {
|
|
195
181
|
return Object.freeze({
|
|
@@ -280,6 +266,7 @@ async function parseAdmittedWake(value) {
|
|
|
280
266
|
// src/admitAndEnqueue.ts
|
|
281
267
|
async function admitAndEnqueue(input) {
|
|
282
268
|
const { verified } = input;
|
|
269
|
+
if (verified.route.deliveryMode !== "durable") throw new TypeError("Durable admission requires explicit durable delivery mode");
|
|
283
270
|
const wake = AdmittedWakeZod.parse({
|
|
284
271
|
audience: verified.audience,
|
|
285
272
|
deploymentId: verified.deploymentId,
|
|
@@ -295,6 +282,53 @@ async function admitAndEnqueue(input) {
|
|
|
295
282
|
return await admitVerifiedWake({ ...input, wake });
|
|
296
283
|
}
|
|
297
284
|
|
|
285
|
+
// src/bestEffortAdmission.ts
|
|
286
|
+
import {
|
|
287
|
+
HashZod as HashZod3,
|
|
288
|
+
NonnegativeJsonSafeIntegerZod as NonnegativeJsonSafeIntegerZod3,
|
|
289
|
+
NormalizedAddressZod as NormalizedAddressZod3,
|
|
290
|
+
WakeIdZod as WakeIdZod2,
|
|
291
|
+
WakeJwtAudienceZod as WakeJwtAudienceZod3,
|
|
292
|
+
WakeJwtIdZod as WakeJwtIdZod2
|
|
293
|
+
} from "@xyo-network/event-kit-schemas";
|
|
294
|
+
import { z as z3 } from "zod";
|
|
295
|
+
|
|
296
|
+
// src/verifyWakeRequest.ts
|
|
297
|
+
import {
|
|
298
|
+
ChainIdZod,
|
|
299
|
+
DeploymentIdZod as DeploymentIdZod2,
|
|
300
|
+
HashZod as HashZod2,
|
|
301
|
+
MAX_POSITIONS_PER_SUBSCRIPTION_WAKE,
|
|
302
|
+
NonnegativeJsonSafeIntegerZod as NonnegativeJsonSafeIntegerZod2,
|
|
303
|
+
NormalizedAddressZod as NormalizedAddressZod2,
|
|
304
|
+
PositiveJsonSafeIntegerZod,
|
|
305
|
+
SOURCE_WAKE_FILTER_PROFILE,
|
|
306
|
+
validWakeDeliveryTerms,
|
|
307
|
+
WakeDeliveryTermsFields,
|
|
308
|
+
WakeEnvelopeZod as WakeEnvelopeZod2,
|
|
309
|
+
WakeJwtAudienceZod as WakeJwtAudienceZod2,
|
|
310
|
+
WakeJwtHeaderZod,
|
|
311
|
+
WakeJwtPayloadZod,
|
|
312
|
+
WakeJwtSchema,
|
|
313
|
+
WakeSourceZod as WakeSourceZod2
|
|
314
|
+
} from "@xyo-network/event-kit-schemas";
|
|
315
|
+
import {
|
|
316
|
+
Account,
|
|
317
|
+
PayloadBuilder as PayloadBuilder3
|
|
318
|
+
} from "@xyo-network/sdk";
|
|
319
|
+
import { z as z2 } from "zod";
|
|
320
|
+
|
|
321
|
+
// src/sourceWakeIdentity.ts
|
|
322
|
+
import {
|
|
323
|
+
SourceWakeSubscriptionFilterZod,
|
|
324
|
+
WakeSourceSchema,
|
|
325
|
+
WakeSourceZod
|
|
326
|
+
} from "@xyo-network/event-kit-schemas";
|
|
327
|
+
import {
|
|
328
|
+
asSchema,
|
|
329
|
+
PayloadBuilder as PayloadBuilder2
|
|
330
|
+
} from "@xyo-network/sdk";
|
|
331
|
+
|
|
298
332
|
// src/canonicalJson.ts
|
|
299
333
|
var textEncoder = new TextEncoder();
|
|
300
334
|
function hasUnpairedSurrogate(value) {
|
|
@@ -344,6 +378,379 @@ function canonicalJsonBytes(value) {
|
|
|
344
378
|
return textEncoder.encode(canonicalize(value));
|
|
345
379
|
}
|
|
346
380
|
|
|
381
|
+
// src/sourceWakeIdentity.ts
|
|
382
|
+
async function wakeSourceHash(source) {
|
|
383
|
+
return await PayloadBuilder2.dataHash({ schema: WakeSourceSchema, ...WakeSourceZod.parse(source) });
|
|
384
|
+
}
|
|
385
|
+
function wakeSourcesEqual(left, right) {
|
|
386
|
+
return canonicalJson(WakeSourceZod.parse(left)) === canonicalJson(WakeSourceZod.parse(right));
|
|
387
|
+
}
|
|
388
|
+
async function sourceWakeFilterHash(filter) {
|
|
389
|
+
const parsed = SourceWakeSubscriptionFilterZod.parse(filter);
|
|
390
|
+
return await PayloadBuilder2.dataHash({ ...parsed, schema: asSchema(parsed.schema, true) });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/verifyWakeRequest.ts
|
|
394
|
+
var EVENT_KIT_MAX_JWT_LIFETIME_SECONDS = 300;
|
|
395
|
+
var EVENT_KIT_MAX_FUTURE_IAT_SECONDS = 60;
|
|
396
|
+
var WakeAdmissionRouteZod = z2.strictObject({
|
|
397
|
+
...WakeDeliveryTermsFields,
|
|
398
|
+
audience: WakeJwtAudienceZod2,
|
|
399
|
+
authorizationChainId: ChainIdZod,
|
|
400
|
+
controller: NormalizedAddressZod2,
|
|
401
|
+
deploymentId: DeploymentIdZod2,
|
|
402
|
+
queueId: z2.string().min(1).max(255).optional(),
|
|
403
|
+
source: WakeSourceZod2,
|
|
404
|
+
filterHash: HashZod2,
|
|
405
|
+
subscriptionHash: HashZod2,
|
|
406
|
+
fromPosition: NonnegativeJsonSafeIntegerZod2,
|
|
407
|
+
maxPositionsPerWake: PositiveJsonSafeIntegerZod.max(MAX_POSITIONS_PER_SUBSCRIPTION_WAKE),
|
|
408
|
+
maxGrantLagBlocks: NonnegativeJsonSafeIntegerZod2
|
|
409
|
+
}).refine((route) => validWakeDeliveryTerms(route) && (route.deliveryMode !== "best-effort" || route.maxPositionsPerWake === 1), "Invalid explicit delivery terms");
|
|
410
|
+
function verifyEventBinding(event, route, nowSeconds) {
|
|
411
|
+
if (event.authorizationChainId !== route.authorizationChainId) {
|
|
412
|
+
return { ok: false, reason: "event-authorization-chain-mismatch" };
|
|
413
|
+
}
|
|
414
|
+
if (!wakeSourcesEqual(event.source, route.source)) return { ok: false, reason: "event-source-mismatch" };
|
|
415
|
+
if (event.filterHash !== route.filterHash) return { ok: false, reason: "event-filter-mismatch" };
|
|
416
|
+
if (event.deploymentId !== route.deploymentId || event.queueId !== route.queueId) {
|
|
417
|
+
return { ok: false, reason: "event-target-mismatch" };
|
|
418
|
+
}
|
|
419
|
+
if (event.deliveryMode !== route.deliveryMode) return { ok: false, reason: "event-mode-mismatch" };
|
|
420
|
+
if (route.deliveryMode === "best-effort") {
|
|
421
|
+
if (event.issuedAt > nowSeconds + EVENT_KIT_MAX_FUTURE_IAT_SECONDS) return { ok: false, reason: "hint-future-issued-at" };
|
|
422
|
+
if (route.bestEffort === void 0 || nowSeconds > event.issuedAt + route.bestEffort.maxHintAgeSeconds) return { ok: false, reason: "hint-expired" };
|
|
423
|
+
}
|
|
424
|
+
if (event.fromPosition < route.fromPosition) return { ok: false, reason: "range-before-floor" };
|
|
425
|
+
if (event.toPosition - event.fromPosition >= route.maxPositionsPerWake) return { ok: false, reason: "range-too-large" };
|
|
426
|
+
}
|
|
427
|
+
async function verifyWakeRequest(input) {
|
|
428
|
+
const routeResult = WakeAdmissionRouteZod.safeParse(input.route);
|
|
429
|
+
if (!routeResult.success || !NonnegativeJsonSafeIntegerZod2.safeParse(input.nowSeconds).success) {
|
|
430
|
+
return { ok: false, reason: "configuration-invalid" };
|
|
431
|
+
}
|
|
432
|
+
const eventResult = WakeEnvelopeZod2.safeParse(input.body);
|
|
433
|
+
if (!eventResult.success) return malformedWake(input.body, eventResult.error.issues);
|
|
434
|
+
const route = routeResult.data;
|
|
435
|
+
if (route.filterHash !== await sourceWakeFilterHash({
|
|
436
|
+
schema: SOURCE_WAKE_FILTER_PROFILE,
|
|
437
|
+
deliveryMode: route.deliveryMode,
|
|
438
|
+
...route.bestEffort === void 0 ? {} : { bestEffort: route.bestEffort },
|
|
439
|
+
source: route.source,
|
|
440
|
+
fromPosition: route.fromPosition,
|
|
441
|
+
maxPositionsPerWake: route.maxPositionsPerWake
|
|
442
|
+
})) return { ok: false, reason: "configuration-invalid" };
|
|
443
|
+
const event = Object.freeze({ ...eventResult.data, source: Object.freeze(eventResult.data.source) });
|
|
444
|
+
const mismatch = verifyEventBinding(event, route, input.nowSeconds);
|
|
445
|
+
if (mismatch !== void 0) return mismatch;
|
|
446
|
+
const jwt = await verifyJwtProfile(input.jwt, route.audience, input.nowSeconds);
|
|
447
|
+
if (!jwt.ok) return jwt;
|
|
448
|
+
let eventHash;
|
|
449
|
+
try {
|
|
450
|
+
eventHash = await PayloadBuilder3.dataHash(event);
|
|
451
|
+
} catch {
|
|
452
|
+
return { ok: false, reason: "event-hash-failed" };
|
|
453
|
+
}
|
|
454
|
+
if (eventHash !== jwt.jwt.eventHash) return { ok: false, reason: "event-hash-mismatch" };
|
|
455
|
+
const sourceHash = await wakeSourceHash(route.source);
|
|
456
|
+
return Object.freeze({
|
|
457
|
+
ok: true,
|
|
458
|
+
verified: Object.freeze({
|
|
459
|
+
audience: route.audience,
|
|
460
|
+
deploymentId: event.deploymentId,
|
|
461
|
+
event,
|
|
462
|
+
eventHash,
|
|
463
|
+
expiresAt: jwt.jwt.expiresAt,
|
|
464
|
+
fromPosition: event.fromPosition,
|
|
465
|
+
issuedAt: event.issuedAt,
|
|
466
|
+
jwtId: jwt.jwt.jwtId,
|
|
467
|
+
publisher: jwt.jwt.publisher,
|
|
468
|
+
...event.queueId === void 0 ? {} : { queueId: event.queueId },
|
|
469
|
+
route: Object.freeze({
|
|
470
|
+
...route,
|
|
471
|
+
source: Object.freeze(route.source),
|
|
472
|
+
sourceHash,
|
|
473
|
+
...route.bestEffort === void 0 ? {} : { bestEffort: Object.freeze(route.bestEffort) }
|
|
474
|
+
}),
|
|
475
|
+
toPosition: event.toPosition,
|
|
476
|
+
tokenIssuedAt: jwt.jwt.issuedAt,
|
|
477
|
+
wakeId: event.wakeId
|
|
478
|
+
})
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
async function verifyJwtProfile(token, audience, nowSeconds) {
|
|
482
|
+
let verified;
|
|
483
|
+
try {
|
|
484
|
+
verified = await Account.verifyJwt(token, { audience, now: nowSeconds });
|
|
485
|
+
} catch (error) {
|
|
486
|
+
return reject2("jwt-malformed", [errorMessage2(error)]);
|
|
487
|
+
}
|
|
488
|
+
if (!verified.ok) {
|
|
489
|
+
return Object.hasOwn(verified.payload ?? {}, "nbf") ? reject2("jwt-nbf-not-allowed") : reject2(classifySdkJwtFailure(verified.reasons), verified.reasons);
|
|
490
|
+
}
|
|
491
|
+
const wireResult = parseStrictJwtWire(verified.header, verified.payload, audience);
|
|
492
|
+
if (!wireResult.ok) return wireResult;
|
|
493
|
+
const { header: strictHeader, payload: strictPayload } = wireResult;
|
|
494
|
+
if (strictHeader.kid !== strictPayload.iss) return reject2("jwt-identity-invalid");
|
|
495
|
+
if (strictPayload.exp <= nowSeconds) return reject2("jwt-expired");
|
|
496
|
+
if (strictPayload.exp - strictPayload.iat > EVENT_KIT_MAX_JWT_LIFETIME_SECONDS) return reject2("jwt-lifetime-invalid");
|
|
497
|
+
if (strictPayload.iat - nowSeconds > EVENT_KIT_MAX_FUTURE_IAT_SECONDS) return reject2("jwt-future-iat");
|
|
498
|
+
const jwt = Object.freeze({
|
|
499
|
+
eventHash: strictPayload.eventHash,
|
|
500
|
+
expiresAt: strictPayload.exp,
|
|
501
|
+
issuedAt: strictPayload.iat,
|
|
502
|
+
jwtId: strictPayload.jti,
|
|
503
|
+
publisher: strictPayload.iss
|
|
504
|
+
});
|
|
505
|
+
return Object.freeze({ jwt, ok: true });
|
|
506
|
+
}
|
|
507
|
+
function parseStrictJwtWire(header, payload, audience) {
|
|
508
|
+
const headerResult = WakeJwtHeaderZod.safeParse(header);
|
|
509
|
+
if (!headerResult.success) {
|
|
510
|
+
return NormalizedAddressZod2.safeParse(header.kid).success ? reject2("jwt-header-invalid", zodDetails(headerResult.error.issues)) : reject2("jwt-identity-invalid");
|
|
511
|
+
}
|
|
512
|
+
if (Object.hasOwn(payload, "nbf")) return reject2("jwt-nbf-not-allowed");
|
|
513
|
+
if (!NormalizedAddressZod2.safeParse(payload.iss).success) return reject2("jwt-identity-invalid");
|
|
514
|
+
if (payload.schema !== WakeJwtSchema) return reject2("jwt-schema-mismatch");
|
|
515
|
+
if (payload.aud !== audience) return reject2("jwt-audience-mismatch");
|
|
516
|
+
if (!isNonnegativeSafeInteger2(payload.iat) || !isNonnegativeSafeInteger2(payload.exp) || payload.exp <= payload.iat) {
|
|
517
|
+
return reject2("jwt-lifetime-invalid");
|
|
518
|
+
}
|
|
519
|
+
const payloadResult = WakeJwtPayloadZod.safeParse(payload);
|
|
520
|
+
if (!payloadResult.success) return reject2("jwt-claims-invalid", zodDetails(payloadResult.error.issues));
|
|
521
|
+
return Object.freeze({
|
|
522
|
+
header: headerResult.data,
|
|
523
|
+
ok: true,
|
|
524
|
+
payload: payloadResult.data
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function classifySdkJwtFailure(reasons) {
|
|
528
|
+
if (reasons.some((reason) => reason.startsWith("audience mismatch:"))) return "jwt-audience-mismatch";
|
|
529
|
+
if (reasons.some((reason) => reason.startsWith("expired:"))) return "jwt-expired";
|
|
530
|
+
if (reasons.some((reason) => reason.startsWith("unsupported alg:") || reason.startsWith("unsupported typ:"))) {
|
|
531
|
+
return "jwt-header-invalid";
|
|
532
|
+
}
|
|
533
|
+
return reasons.some((reason) => reason.startsWith("invalid public key:") || reason.startsWith("public key does not derive to kid:") || reason === "header.kid does not match payload.iss") ? "jwt-identity-invalid" : "jwt-invalid";
|
|
534
|
+
}
|
|
535
|
+
function malformedWake(body, issues) {
|
|
536
|
+
const details = zodDetails(issues);
|
|
537
|
+
if (hasInvertedSafeRange(body)) return reject2("range-invalid", details);
|
|
538
|
+
return reject2(isRecord2(body) && Object.hasOwn(body, "wakeId") && issues.some((issue) => issue.path[0] === "wakeId") ? "wake-id-invalid" : "event-malformed", details);
|
|
539
|
+
}
|
|
540
|
+
function hasInvertedSafeRange(value) {
|
|
541
|
+
if (!isRecord2(value)) return false;
|
|
542
|
+
const { fromPosition, toPosition } = value;
|
|
543
|
+
return typeof fromPosition === "number" && typeof toPosition === "number" && isNonnegativeSafeInteger2(fromPosition) && isNonnegativeSafeInteger2(toPosition) && fromPosition > toPosition;
|
|
544
|
+
}
|
|
545
|
+
function isNonnegativeSafeInteger2(value) {
|
|
546
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
547
|
+
}
|
|
548
|
+
function isRecord2(value) {
|
|
549
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
550
|
+
}
|
|
551
|
+
function zodDetails(issues) {
|
|
552
|
+
return issues.map((issue) => {
|
|
553
|
+
const path = issue.path.map(String).join(".") || "<root>";
|
|
554
|
+
return `${path}: ${issue.message}`;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function reject2(reason, details) {
|
|
558
|
+
if (details === void 0) return Object.freeze({ ok: false, reason });
|
|
559
|
+
return Object.freeze({
|
|
560
|
+
details: Object.freeze([...details]),
|
|
561
|
+
ok: false,
|
|
562
|
+
reason
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
function errorMessage2(error) {
|
|
566
|
+
return error instanceof Error ? error.message : String(error);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/bestEffortAdmission.ts
|
|
570
|
+
function hintRetention(input) {
|
|
571
|
+
const { verified, nowSeconds } = input;
|
|
572
|
+
const policy = verified.route.bestEffort;
|
|
573
|
+
if (verified.route.deliveryMode !== "best-effort" || verified.event.deliveryMode !== "best-effort" || policy === void 0 || verified.fromPosition !== verified.toPosition || !Number.isSafeInteger(nowSeconds) || nowSeconds < 0 || !Number.isSafeInteger(input.expectedHead) || input.expectedHead < 0)
|
|
574
|
+
return { ok: false, reason: "configuration-invalid" };
|
|
575
|
+
if (verified.issuedAt > nowSeconds + EVENT_KIT_MAX_FUTURE_IAT_SECONDS) return { ok: false, reason: "hint-future-issued-at" };
|
|
576
|
+
const bodyDeadline = verified.issuedAt + policy.maxHintAgeSeconds;
|
|
577
|
+
const retainWakeUntil = bodyDeadline + EVENT_KIT_REPLAY_RETENTION_SKEW_SECONDS;
|
|
578
|
+
const retainJwtUntil = verified.expiresAt + EVENT_KIT_REPLAY_RETENTION_SKEW_SECONDS;
|
|
579
|
+
if (!Number.isSafeInteger(retainWakeUntil) || !Number.isSafeInteger(retainJwtUntil)) return { ok: false, reason: "configuration-invalid" };
|
|
580
|
+
return nowSeconds > bodyDeadline ? { ok: false, reason: "hint-expired" } : {
|
|
581
|
+
ok: true,
|
|
582
|
+
retainJwtUntil,
|
|
583
|
+
retainWakeUntil
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
async function admitBestEffortHint(input) {
|
|
587
|
+
const retention = hintRetention(input);
|
|
588
|
+
if (!retention.ok) return retention;
|
|
589
|
+
const { retainJwtUntil, retainWakeUntil } = retention;
|
|
590
|
+
const { verified, nowSeconds } = input;
|
|
591
|
+
const authorization = await authorizeVerifiedWake(input);
|
|
592
|
+
if (!authorization.ok) return authorization.failure;
|
|
593
|
+
const wake = AdmittedWakeZod.parse({
|
|
594
|
+
audience: verified.audience,
|
|
595
|
+
deploymentId: verified.deploymentId,
|
|
596
|
+
event: verified.event,
|
|
597
|
+
eventHash: verified.eventHash,
|
|
598
|
+
fromPosition: verified.fromPosition,
|
|
599
|
+
toPosition: verified.toPosition,
|
|
600
|
+
jti: verified.jwtId,
|
|
601
|
+
publisher: verified.publisher,
|
|
602
|
+
wakeId: verified.wakeId,
|
|
603
|
+
...verified.queueId === void 0 ? {} : { queueId: verified.queueId }
|
|
604
|
+
});
|
|
605
|
+
const attempt = {
|
|
606
|
+
audience: verified.audience,
|
|
607
|
+
eventHash: verified.eventHash,
|
|
608
|
+
jwtId: verified.jwtId,
|
|
609
|
+
publisher: verified.publisher,
|
|
610
|
+
retainJwtUntil,
|
|
611
|
+
retainWakeUntil,
|
|
612
|
+
wakeId: verified.wakeId
|
|
613
|
+
};
|
|
614
|
+
try {
|
|
615
|
+
const committed = await input.inbox.commitAuthorized({
|
|
616
|
+
attempt,
|
|
617
|
+
grant: authorization.grant,
|
|
618
|
+
wake,
|
|
619
|
+
nowSeconds
|
|
620
|
+
});
|
|
621
|
+
if (committed.kind !== "accepted" && committed.kind !== "duplicate") return { ok: false, reason: committed.kind };
|
|
622
|
+
const acknowledgment = committed.acknowledgment;
|
|
623
|
+
return acknowledgmentMatches(acknowledgment, attempt, nowSeconds) ? {
|
|
624
|
+
ok: true,
|
|
625
|
+
disposition: committed.kind,
|
|
626
|
+
acknowledgment,
|
|
627
|
+
wake
|
|
628
|
+
} : { ok: false, reason: "inbox-unavailable" };
|
|
629
|
+
} catch {
|
|
630
|
+
return { ok: false, reason: "inbox-unavailable" };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function acknowledgmentMatches(value, attempt, now) {
|
|
634
|
+
return BestEffortAdmissionAcknowledgmentZod.safeParse(value).success && value.eventHash === attempt.eventHash && value.wakeId === attempt.wakeId && value.acceptedAt <= now && value.expiresAt === attempt.retainWakeUntil;
|
|
635
|
+
}
|
|
636
|
+
var BestEffortAdmissionAcknowledgmentZod = z3.strictObject({
|
|
637
|
+
admissionId: z3.string().min(1).max(4096),
|
|
638
|
+
eventHash: HashZod3,
|
|
639
|
+
wakeId: WakeIdZod2,
|
|
640
|
+
acceptedAt: NonnegativeJsonSafeIntegerZod3,
|
|
641
|
+
expiresAt: NonnegativeJsonSafeIntegerZod3
|
|
642
|
+
}).refine((value) => value.expiresAt > value.acceptedAt, "Acknowledgment expiry must follow admission");
|
|
643
|
+
var JwtSecurityRecordZod = z3.strictObject({
|
|
644
|
+
audience: WakeJwtAudienceZod3,
|
|
645
|
+
publisher: NormalizedAddressZod3,
|
|
646
|
+
jwtId: WakeJwtIdZod2,
|
|
647
|
+
wakeId: WakeIdZod2,
|
|
648
|
+
eventHash: HashZod3,
|
|
649
|
+
expiresAt: NonnegativeJsonSafeIntegerZod3
|
|
650
|
+
});
|
|
651
|
+
var WakeSecurityRecordZod = z3.strictObject({
|
|
652
|
+
audience: WakeJwtAudienceZod3,
|
|
653
|
+
publisher: NormalizedAddressZod3,
|
|
654
|
+
wakeId: WakeIdZod2,
|
|
655
|
+
eventHash: HashZod3,
|
|
656
|
+
acknowledgment: BestEffortAdmissionAcknowledgmentZod
|
|
657
|
+
}).refine(
|
|
658
|
+
(value) => value.wakeId === value.acknowledgment.wakeId && value.eventHash === value.acknowledgment.eventHash,
|
|
659
|
+
"Security identity must match acknowledgment"
|
|
660
|
+
);
|
|
661
|
+
var BestEffortSecurityStateZod = z3.strictObject({
|
|
662
|
+
highestNowSeconds: NonnegativeJsonSafeIntegerZod3,
|
|
663
|
+
jwtRecords: z3.array(JwtSecurityRecordZod).max(65536),
|
|
664
|
+
wakeRecords: z3.array(WakeSecurityRecordZod).max(65536)
|
|
665
|
+
}).superRefine((value, context) => {
|
|
666
|
+
const jwtKeys = value.jwtRecords.map((record) => JSON.stringify([record.audience, record.publisher, record.jwtId]));
|
|
667
|
+
if (value.wakeRecords.some((record) => record.acknowledgment.acceptedAt > value.highestNowSeconds))
|
|
668
|
+
context.addIssue({ code: "custom", message: "Acknowledgment cannot precede stored admission clock" });
|
|
669
|
+
const wakeKeys = value.wakeRecords.map((record) => JSON.stringify([record.audience, record.publisher, record.wakeId]));
|
|
670
|
+
const jwtIdentities = new Set(jwtKeys);
|
|
671
|
+
const wakeIdentities = new Set(wakeKeys);
|
|
672
|
+
if (jwtIdentities.size !== jwtKeys.length || wakeIdentities.size !== wakeKeys.length)
|
|
673
|
+
context.addIssue({ code: "custom", message: "Persisted security identities must be unique" });
|
|
674
|
+
});
|
|
675
|
+
function parseBestEffortSecurityState(value) {
|
|
676
|
+
return BestEffortSecurityStateZod.parse(value);
|
|
677
|
+
}
|
|
678
|
+
var BestEffortAdmissionAttemptZod = z3.strictObject({
|
|
679
|
+
audience: WakeJwtAudienceZod3,
|
|
680
|
+
eventHash: HashZod3,
|
|
681
|
+
jwtId: WakeJwtIdZod2,
|
|
682
|
+
publisher: NormalizedAddressZod3,
|
|
683
|
+
retainJwtUntil: NonnegativeJsonSafeIntegerZod3,
|
|
684
|
+
retainWakeUntil: NonnegativeJsonSafeIntegerZod3,
|
|
685
|
+
wakeId: WakeIdZod2
|
|
686
|
+
});
|
|
687
|
+
function createBestEffortSecurityState() {
|
|
688
|
+
return {
|
|
689
|
+
highestNowSeconds: 0,
|
|
690
|
+
jwtRecords: [],
|
|
691
|
+
wakeRecords: []
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function securityConflict(attempt, priorJwt, priorWake, jwtCount, wakeCount, maxRecords) {
|
|
695
|
+
if (priorJwt !== void 0 && (priorJwt.eventHash !== attempt.eventHash || priorJwt.wakeId !== attempt.wakeId))
|
|
696
|
+
return "jwt-id-collision";
|
|
697
|
+
if (priorWake !== void 0 && priorWake.eventHash !== attempt.eventHash)
|
|
698
|
+
return "wake-id-collision";
|
|
699
|
+
if (priorJwt === void 0 && jwtCount >= maxRecords || priorWake === void 0 && wakeCount >= maxRecords)
|
|
700
|
+
return "capacity-exceeded";
|
|
701
|
+
}
|
|
702
|
+
function validateTransaction(input, maxRecords) {
|
|
703
|
+
const attempt = BestEffortAdmissionAttemptZod.parse(input.attempt);
|
|
704
|
+
const wake = AdmittedWakeZod.parse(input.wake);
|
|
705
|
+
const nowSeconds = NonnegativeJsonSafeIntegerZod3.parse(input.nowSeconds);
|
|
706
|
+
if (attempt.audience !== wake.audience || attempt.publisher !== wake.publisher || attempt.jwtId !== wake.jti || attempt.wakeId !== wake.wakeId || attempt.eventHash !== wake.eventHash || wake.event.deliveryMode !== "best-effort" || attempt.retainJwtUntil <= nowSeconds || attempt.retainWakeUntil <= nowSeconds)
|
|
707
|
+
throw new TypeError("Best Effort transaction attempt does not match admitted hint");
|
|
708
|
+
if (!Number.isSafeInteger(maxRecords) || maxRecords < 1 || maxRecords > 65536) throw new TypeError("Invalid finite security capacity");
|
|
709
|
+
return { attempt, nowSeconds };
|
|
710
|
+
}
|
|
711
|
+
function commitBestEffortSecurity(state, input, maxRecords) {
|
|
712
|
+
state = parseBestEffortSecurityState(state);
|
|
713
|
+
const { attempt, nowSeconds } = validateTransaction(input, maxRecords);
|
|
714
|
+
if (nowSeconds < state.highestNowSeconds) return { state, result: { kind: "clock-regressed" } };
|
|
715
|
+
const jwtRecords = state.jwtRecords.filter((record) => record.expiresAt > nowSeconds).map((record) => ({ ...record }));
|
|
716
|
+
const wakeRecords = state.wakeRecords.filter((record) => record.acknowledgment.expiresAt > nowSeconds);
|
|
717
|
+
const priorJwt = jwtRecords.find((record) => record.audience === attempt.audience && record.publisher === attempt.publisher && record.jwtId === attempt.jwtId);
|
|
718
|
+
const priorWake = wakeRecords.find((record) => record.audience === attempt.audience && record.publisher === attempt.publisher && record.wakeId === attempt.wakeId);
|
|
719
|
+
const conflict = securityConflict(attempt, priorJwt, priorWake, jwtRecords.length, wakeRecords.length, maxRecords);
|
|
720
|
+
if (conflict !== void 0) return { state, result: { kind: conflict } };
|
|
721
|
+
const acknowledgment = priorWake?.acknowledgment ?? {
|
|
722
|
+
admissionId: JSON.stringify([attempt.audience, attempt.publisher, attempt.wakeId, attempt.eventHash]),
|
|
723
|
+
eventHash: attempt.eventHash,
|
|
724
|
+
wakeId: attempt.wakeId,
|
|
725
|
+
acceptedAt: nowSeconds,
|
|
726
|
+
expiresAt: attempt.retainWakeUntil
|
|
727
|
+
};
|
|
728
|
+
if (priorJwt !== void 0) priorJwt.expiresAt = Math.max(priorJwt.expiresAt, attempt.retainJwtUntil);
|
|
729
|
+
if (priorJwt === void 0) jwtRecords.push({
|
|
730
|
+
audience: attempt.audience,
|
|
731
|
+
publisher: attempt.publisher,
|
|
732
|
+
jwtId: attempt.jwtId,
|
|
733
|
+
wakeId: attempt.wakeId,
|
|
734
|
+
eventHash: attempt.eventHash,
|
|
735
|
+
expiresAt: attempt.retainJwtUntil
|
|
736
|
+
});
|
|
737
|
+
if (priorWake === void 0) wakeRecords.push({
|
|
738
|
+
audience: attempt.audience,
|
|
739
|
+
publisher: attempt.publisher,
|
|
740
|
+
wakeId: attempt.wakeId,
|
|
741
|
+
eventHash: attempt.eventHash,
|
|
742
|
+
acknowledgment
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
state: {
|
|
746
|
+
highestNowSeconds: nowSeconds,
|
|
747
|
+
jwtRecords,
|
|
748
|
+
wakeRecords
|
|
749
|
+
},
|
|
750
|
+
result: { kind: priorWake === void 0 ? "accepted" : "duplicate", acknowledgment }
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
347
754
|
// src/endpointChallenge.ts
|
|
348
755
|
import {
|
|
349
756
|
Base64Url32BytesZod,
|
|
@@ -357,7 +764,7 @@ import {
|
|
|
357
764
|
generateHandshakeId,
|
|
358
765
|
MAX_ENDPOINT_CHALLENGE_LIFETIME_SECONDS
|
|
359
766
|
} from "@xyo-network/event-kit-schemas";
|
|
360
|
-
import { PayloadBuilder as
|
|
767
|
+
import { PayloadBuilder as PayloadBuilder4 } from "@xyo-network/sdk";
|
|
361
768
|
function hashBytes(hash) {
|
|
362
769
|
return Uint8Array.from({ length: 32 }, (_, index) => Number.parseInt(hash.slice(index * 2, index * 2 + 2), 16));
|
|
363
770
|
}
|
|
@@ -384,7 +791,7 @@ function equalProof(left, right) {
|
|
|
384
791
|
}
|
|
385
792
|
async function endpointChallengeHash(challenge) {
|
|
386
793
|
const parsed = EndpointChallengeZod.parse(challenge);
|
|
387
|
-
return await
|
|
794
|
+
return await PayloadBuilder4.dataHash(parsed);
|
|
388
795
|
}
|
|
389
796
|
function createEndpointChallenge(input) {
|
|
390
797
|
const lifetimeSeconds = input.lifetimeSeconds ?? MAX_ENDPOINT_CHALLENGE_LIFETIME_SECONDS;
|
|
@@ -397,7 +804,7 @@ function createEndpointChallenge(input) {
|
|
|
397
804
|
if (input.issuedAt > Number.MAX_SAFE_INTEGER - lifetimeSeconds) {
|
|
398
805
|
throw new RangeError("Challenge expiry exceeds the JSON safe-integer range");
|
|
399
806
|
}
|
|
400
|
-
const builder = new
|
|
807
|
+
const builder = new PayloadBuilder4({ schema: EndpointChallengeSchema });
|
|
401
808
|
return EndpointChallengeZod.parse(builder.fields({
|
|
402
809
|
challengeId: generateHandshakeId(),
|
|
403
810
|
chainId: input.chainId,
|
|
@@ -416,7 +823,7 @@ function createEndpointChallenge(input) {
|
|
|
416
823
|
}
|
|
417
824
|
async function createEndpointChallengeResponse(challenge, secret) {
|
|
418
825
|
const challengeHash = await endpointChallengeHash(challenge);
|
|
419
|
-
const builder = new
|
|
826
|
+
const builder = new PayloadBuilder4({ schema: EndpointChallengeResponseSchema });
|
|
420
827
|
return EndpointChallengeResponseZod.parse(builder.fields({
|
|
421
828
|
challenge: challengeHash,
|
|
422
829
|
proof: await hmacProof(secret, challengeHash)
|
|
@@ -427,8 +834,7 @@ async function verifyEndpointChallengeProof(input) {
|
|
|
427
834
|
const challenge = EndpointChallengeZod.parse(input.challenge);
|
|
428
835
|
const response = EndpointChallengeResponseZod.parse(input.response);
|
|
429
836
|
const challengeHash = await endpointChallengeHash(challenge);
|
|
430
|
-
|
|
431
|
-
return equalProof(response.proof, await hmacProof(input.secret, challengeHash));
|
|
837
|
+
return response.challenge === challengeHash && equalProof(response.proof, await hmacProof(input.secret, challengeHash));
|
|
432
838
|
} catch {
|
|
433
839
|
return false;
|
|
434
840
|
}
|
|
@@ -436,8 +842,7 @@ async function verifyEndpointChallengeProof(input) {
|
|
|
436
842
|
async function verifyEndpointChallengeResponse(input) {
|
|
437
843
|
try {
|
|
438
844
|
const challenge = EndpointChallengeZod.parse(input.challenge);
|
|
439
|
-
|
|
440
|
-
return await verifyEndpointChallengeProof(input);
|
|
845
|
+
return Number.isSafeInteger(input.nowSeconds) && input.nowSeconds >= challenge.issuedAt && input.nowSeconds < challenge.expiresAt && await verifyEndpointChallengeProof(input);
|
|
441
846
|
} catch {
|
|
442
847
|
return false;
|
|
443
848
|
}
|
|
@@ -453,7 +858,7 @@ import {
|
|
|
453
858
|
EndpointChallengeZod as EndpointChallengeZod2,
|
|
454
859
|
generateGrantId,
|
|
455
860
|
generateHandshakeId as generateHandshakeId2,
|
|
456
|
-
HashZod as
|
|
861
|
+
HashZod as HashZod5,
|
|
457
862
|
MAX_SUBSCRIPTION_PLAINTEXT_BYTES as MAX_SUBSCRIPTION_PLAINTEXT_BYTES2,
|
|
458
863
|
PublisherEncryptionKeyRevocationSchema,
|
|
459
864
|
PublisherEncryptionKeyRevocationZod,
|
|
@@ -464,7 +869,7 @@ import {
|
|
|
464
869
|
PublisherServiceSchema,
|
|
465
870
|
PublisherServiceZod,
|
|
466
871
|
SOURCE_WAKE_EVENT_KIND,
|
|
467
|
-
SOURCE_WAKE_FILTER_PROFILE,
|
|
872
|
+
SOURCE_WAKE_FILTER_PROFILE as SOURCE_WAKE_FILTER_PROFILE2,
|
|
468
873
|
WAKE_SUBSCRIPTION_HPKE_PROFILE as WAKE_SUBSCRIPTION_HPKE_PROFILE3,
|
|
469
874
|
WAKE_WALLET_JWT_DELIVERY_PROFILE,
|
|
470
875
|
WakeGrantObjectSchema,
|
|
@@ -481,7 +886,7 @@ import {
|
|
|
481
886
|
WakeSubscriptionRevocationSchema,
|
|
482
887
|
WakeSubscriptionRevocationZod
|
|
483
888
|
} from "@xyo-network/event-kit-schemas";
|
|
484
|
-
import { PayloadBuilder as
|
|
889
|
+
import { PayloadBuilder as PayloadBuilder5 } from "@xyo-network/sdk";
|
|
485
890
|
|
|
486
891
|
// src/wakeSubscriptionHpke.ts
|
|
487
892
|
import {
|
|
@@ -496,8 +901,8 @@ import {
|
|
|
496
901
|
canonicalBase64UrlToBytes as canonicalBase64UrlToBytes2,
|
|
497
902
|
EncryptedWakeSubscriptionZod as EncryptedWakeSubscriptionZod2,
|
|
498
903
|
HandshakeIdZod,
|
|
499
|
-
HashZod as
|
|
500
|
-
NormalizedAddressZod as
|
|
904
|
+
HashZod as HashZod4,
|
|
905
|
+
NormalizedAddressZod as NormalizedAddressZod5,
|
|
501
906
|
WAKE_SUBSCRIPTION_HPKE_PROFILE as WAKE_SUBSCRIPTION_HPKE_PROFILE2,
|
|
502
907
|
WakeSubscriptionCleartextZod as WakeSubscriptionCleartextZod2
|
|
503
908
|
} from "@xyo-network/event-kit-schemas";
|
|
@@ -506,7 +911,7 @@ import {
|
|
|
506
911
|
import {
|
|
507
912
|
EncryptedWakeSubscriptionZod,
|
|
508
913
|
MAX_SUBSCRIPTION_PLAINTEXT_BYTES,
|
|
509
|
-
NormalizedAddressZod as
|
|
914
|
+
NormalizedAddressZod as NormalizedAddressZod4,
|
|
510
915
|
WAKE_SUBSCRIPTION_HPKE_PROFILE,
|
|
511
916
|
WakeSubscriptionCleartextZod,
|
|
512
917
|
WakeSubscriptionPlaintextZod
|
|
@@ -523,7 +928,7 @@ function cleartextFromEncrypted(subscription) {
|
|
|
523
928
|
return cleartext;
|
|
524
929
|
}
|
|
525
930
|
function wakeSubscriptionAadBytes(subscription, subscriber) {
|
|
526
|
-
const parsedSubscriber =
|
|
931
|
+
const parsedSubscriber = NormalizedAddressZod4.parse(subscriber);
|
|
527
932
|
const parsedSubscription = "encryption" in subscription ? cleartextFromEncrypted(EncryptedWakeSubscriptionZod.parse(subscription)) : WakeSubscriptionCleartextZod.parse(subscription);
|
|
528
933
|
return canonicalJsonBytes({
|
|
529
934
|
schema: parsedSubscription.schema,
|
|
@@ -538,6 +943,7 @@ function wakeSubscriptionAadBytes(subscription, subscriber) {
|
|
|
538
943
|
revision: parsedSubscription.revision,
|
|
539
944
|
supersedes: parsedSubscription.supersedes ?? null,
|
|
540
945
|
eventKind: parsedSubscription.eventKind,
|
|
946
|
+
deliveryMode: parsedSubscription.deliveryMode,
|
|
541
947
|
expiresAtBlock: parsedSubscription.expiresAtBlock,
|
|
542
948
|
profile: WAKE_SUBSCRIPTION_HPKE_PROFILE
|
|
543
949
|
});
|
|
@@ -587,14 +993,14 @@ var WakeSubscriptionDecryptionError = class extends Error {
|
|
|
587
993
|
};
|
|
588
994
|
function parsePublicKey(value) {
|
|
589
995
|
return {
|
|
590
|
-
encryptionKey:
|
|
996
|
+
encryptionKey: HashZod4.parse(value.encryptionKey),
|
|
591
997
|
keyId: HandshakeIdZod.parse(value.keyId),
|
|
592
998
|
publicKey: Base64Url32BytesZod2.parse(value.publicKey)
|
|
593
999
|
};
|
|
594
1000
|
}
|
|
595
1001
|
function parsePrivateKey(value) {
|
|
596
1002
|
return {
|
|
597
|
-
encryptionKey:
|
|
1003
|
+
encryptionKey: HashZod4.parse(value.encryptionKey),
|
|
598
1004
|
keyId: HandshakeIdZod.parse(value.keyId),
|
|
599
1005
|
privateKey: Base64Url32BytesZod2.parse(value.privateKey)
|
|
600
1006
|
};
|
|
@@ -618,7 +1024,7 @@ async function generateWakeSubscriptionEncryptionKeyPair() {
|
|
|
618
1024
|
}
|
|
619
1025
|
async function encryptWakeSubscription(input) {
|
|
620
1026
|
const subscription = WakeSubscriptionCleartextZod2.parse(input.subscription);
|
|
621
|
-
const subscriber =
|
|
1027
|
+
const subscriber = NormalizedAddressZod5.parse(input.subscriber);
|
|
622
1028
|
const publisherKey = parsePublicKey(input.publisherKey);
|
|
623
1029
|
if (subscription.encryptionKey !== publisherKey.encryptionKey || subscription.keyId !== publisherKey.keyId) {
|
|
624
1030
|
throw new TypeError("Subscription key reference does not match the supplied publisher key");
|
|
@@ -641,7 +1047,7 @@ async function encryptWakeSubscription(input) {
|
|
|
641
1047
|
async function decryptWakeSubscription(input) {
|
|
642
1048
|
try {
|
|
643
1049
|
const subscription = EncryptedWakeSubscriptionZod2.parse(input.subscription);
|
|
644
|
-
const subscriber =
|
|
1050
|
+
const subscriber = NormalizedAddressZod5.parse(input.subscriber);
|
|
645
1051
|
const publisherKey = parsePrivateKey(input.publisherKey);
|
|
646
1052
|
if (subscription.encryptionKey !== publisherKey.encryptionKey || subscription.keyId !== publisherKey.keyId) throw new TypeError("Key reference mismatch");
|
|
647
1053
|
const recipientKey = await suite.kem.deserializePrivateKey(decoded32(publisherKey.privateKey));
|
|
@@ -684,13 +1090,13 @@ function parseHandshakePublicObject(value) {
|
|
|
684
1090
|
throw new TypeError("Expected a strict Event Kit handshake public object");
|
|
685
1091
|
}
|
|
686
1092
|
async function handshakeObjectHash(object) {
|
|
687
|
-
return
|
|
1093
|
+
return HashZod5.parse(await PayloadBuilder5.dataHash(parseHandshakePublicObject(object)));
|
|
688
1094
|
}
|
|
689
1095
|
async function withHandshakeObjectHash(body) {
|
|
690
1096
|
return Object.freeze({ body, objectHash: await handshakeObjectHash(body) });
|
|
691
1097
|
}
|
|
692
1098
|
function createPublisherEncryptionKey(input) {
|
|
693
|
-
const builder = new
|
|
1099
|
+
const builder = new PayloadBuilder5({ schema: PublisherEncryptionKeySchema });
|
|
694
1100
|
return PublisherEncryptionKeyZod.parse(builder.fields({
|
|
695
1101
|
keyId: input.keyId ?? generateHandshakeId2(),
|
|
696
1102
|
profile: WAKE_SUBSCRIPTION_HPKE_PROFILE3,
|
|
@@ -722,7 +1128,7 @@ async function generatePublisherEncryptionKey(input) {
|
|
|
722
1128
|
});
|
|
723
1129
|
}
|
|
724
1130
|
function createPublisherService(input) {
|
|
725
|
-
const builder = new
|
|
1131
|
+
const builder = new PayloadBuilder5({ schema: PublisherServiceSchema });
|
|
726
1132
|
return PublisherServiceZod.parse(builder.fields({
|
|
727
1133
|
authorizationChainId: input.authorizationChainId,
|
|
728
1134
|
serviceId: input.serviceId ?? generateHandshakeId2(),
|
|
@@ -732,9 +1138,11 @@ function createPublisherService(input) {
|
|
|
732
1138
|
...input.expiresAtBlock === void 0 ? {} : { expiresAtBlock: input.expiresAtBlock },
|
|
733
1139
|
events: input.sources.map((source) => ({
|
|
734
1140
|
kind: SOURCE_WAKE_EVENT_KIND,
|
|
735
|
-
filterProfile:
|
|
1141
|
+
filterProfile: SOURCE_WAKE_FILTER_PROFILE2,
|
|
736
1142
|
source
|
|
737
1143
|
})),
|
|
1144
|
+
deliveryMode: input.deliveryMode,
|
|
1145
|
+
...input.bestEffort === void 0 ? {} : { bestEffort: input.bestEffort },
|
|
738
1146
|
deliveryProfile: WAKE_WALLET_JWT_DELIVERY_PROFILE,
|
|
739
1147
|
challengeProfile: ENDPOINT_CHALLENGE_HMAC_SHA256_PROFILE,
|
|
740
1148
|
encryptionProfile: WAKE_SUBSCRIPTION_HPKE_PROFILE3,
|
|
@@ -749,7 +1157,7 @@ function createPublisherService(input) {
|
|
|
749
1157
|
}).build());
|
|
750
1158
|
}
|
|
751
1159
|
function createWakeGrant(input) {
|
|
752
|
-
const builder = new
|
|
1160
|
+
const builder = new PayloadBuilder5({ schema: WakeGrantObjectSchema });
|
|
753
1161
|
return WakeGrantObjectZod.parse(builder.fields({
|
|
754
1162
|
grantId: input.grantId ?? generateGrantId(),
|
|
755
1163
|
deploymentId: input.deploymentId,
|
|
@@ -764,14 +1172,16 @@ function createWakeSubscriptionPlaintext(input) {
|
|
|
764
1172
|
globalThis.crypto.getRandomValues(secretBytes);
|
|
765
1173
|
}
|
|
766
1174
|
const challengeSecret = input.challengeSecret ?? bytesToCanonicalBase64Url3(secretBytes);
|
|
767
|
-
const builder = new
|
|
1175
|
+
const builder = new PayloadBuilder5({ schema: WakeSubscriptionPlaintextSchema });
|
|
768
1176
|
return WakeSubscriptionPlaintextZod2.parse(builder.fields({
|
|
769
1177
|
endpoint: input.endpoint,
|
|
770
1178
|
audience: input.audience,
|
|
771
1179
|
deploymentId: input.deploymentId,
|
|
772
1180
|
queueId: input.queueId ?? null,
|
|
773
1181
|
filter: {
|
|
774
|
-
schema:
|
|
1182
|
+
schema: SOURCE_WAKE_FILTER_PROFILE2,
|
|
1183
|
+
deliveryMode: input.deliveryMode,
|
|
1184
|
+
...input.bestEffort === void 0 ? {} : { bestEffort: input.bestEffort },
|
|
775
1185
|
source: input.source,
|
|
776
1186
|
fromPosition: input.fromPosition,
|
|
777
1187
|
maxPositionsPerWake: input.maxPositionsPerWake
|
|
@@ -783,7 +1193,7 @@ function createWakeSubscriptionPlaintext(input) {
|
|
|
783
1193
|
}).build());
|
|
784
1194
|
}
|
|
785
1195
|
async function createEncryptedWakeSubscription(input) {
|
|
786
|
-
const builder = new
|
|
1196
|
+
const builder = new PayloadBuilder5({ schema: EncryptedWakeSubscriptionSchema });
|
|
787
1197
|
const cleartext = WakeSubscriptionCleartextZod3.parse(builder.fields({
|
|
788
1198
|
subscriptionId: input.subscriptionId ?? generateHandshakeId2(),
|
|
789
1199
|
revision: input.revision ?? 1,
|
|
@@ -795,6 +1205,7 @@ async function createEncryptedWakeSubscription(input) {
|
|
|
795
1205
|
keyId: input.publisherKey.keyId,
|
|
796
1206
|
grant: input.grant,
|
|
797
1207
|
eventKind: SOURCE_WAKE_EVENT_KIND,
|
|
1208
|
+
deliveryMode: input.plaintext.filter.deliveryMode,
|
|
798
1209
|
expiresAtBlock: input.expiresAtBlock
|
|
799
1210
|
}).build());
|
|
800
1211
|
return await encryptWakeSubscription({
|
|
@@ -805,7 +1216,7 @@ async function createEncryptedWakeSubscription(input) {
|
|
|
805
1216
|
});
|
|
806
1217
|
}
|
|
807
1218
|
function createWakeSubscriptionAcceptance(input) {
|
|
808
|
-
const builder = new
|
|
1219
|
+
const builder = new PayloadBuilder5({ schema: WakeSubscriptionAcceptanceSchema });
|
|
809
1220
|
return WakeSubscriptionAcceptanceZod.parse(builder.fields({
|
|
810
1221
|
challenge: input.challenge,
|
|
811
1222
|
encryptionKey: input.encryptionKey,
|
|
@@ -816,47 +1227,45 @@ function createWakeSubscriptionAcceptance(input) {
|
|
|
816
1227
|
}).build());
|
|
817
1228
|
}
|
|
818
1229
|
function createPublisherServiceRevocation(service) {
|
|
819
|
-
const builder = new
|
|
1230
|
+
const builder = new PayloadBuilder5({ schema: PublisherServiceRevocationSchema });
|
|
820
1231
|
return PublisherServiceRevocationZod.parse(builder.fields({ service }).build());
|
|
821
1232
|
}
|
|
822
1233
|
function createPublisherEncryptionKeyRevocation(key) {
|
|
823
|
-
const builder = new
|
|
1234
|
+
const builder = new PayloadBuilder5({ schema: PublisherEncryptionKeyRevocationSchema });
|
|
824
1235
|
return PublisherEncryptionKeyRevocationZod.parse(builder.fields({ key }).build());
|
|
825
1236
|
}
|
|
826
1237
|
function createWakeGrantRevocation(grant) {
|
|
827
|
-
const builder = new
|
|
1238
|
+
const builder = new PayloadBuilder5({ schema: WakeGrantRevocationSchema });
|
|
828
1239
|
return WakeGrantRevocationZod.parse(builder.fields({ grant }).build());
|
|
829
1240
|
}
|
|
830
1241
|
function createWakeSubscriptionRevocation(subscription) {
|
|
831
|
-
const builder = new
|
|
1242
|
+
const builder = new PayloadBuilder5({ schema: WakeSubscriptionRevocationSchema });
|
|
832
1243
|
return WakeSubscriptionRevocationZod.parse(builder.fields({ subscription }).build());
|
|
833
1244
|
}
|
|
834
1245
|
function createWakeSubscriptionAcceptanceRevocation(acceptance) {
|
|
835
|
-
const builder = new
|
|
1246
|
+
const builder = new PayloadBuilder5({ schema: WakeSubscriptionAcceptanceRevocationSchema });
|
|
836
1247
|
return WakeSubscriptionAcceptanceRevocationZod.parse(builder.fields({ acceptance }).build());
|
|
837
1248
|
}
|
|
838
1249
|
|
|
839
1250
|
// src/lifecycleSelection.ts
|
|
840
|
-
function
|
|
1251
|
+
function isNonnegativeSafeInteger3(value) {
|
|
841
1252
|
return Number.isSafeInteger(value) && value >= 0;
|
|
842
1253
|
}
|
|
843
1254
|
function isVisible(claimedAtBlock, finalizedHead) {
|
|
844
|
-
return
|
|
1255
|
+
return isNonnegativeSafeInteger3(claimedAtBlock) && claimedAtBlock <= finalizedHead;
|
|
845
1256
|
}
|
|
846
1257
|
function isRevoked(revokedAtBlock, finalizedHead) {
|
|
847
1258
|
return revokedAtBlock !== void 0 && revokedAtBlock <= finalizedHead;
|
|
848
1259
|
}
|
|
849
1260
|
function isUnrevokedEvidence(claimedAtBlock, revokedAtBlock, finalizedHead) {
|
|
850
|
-
return isVisible(claimedAtBlock, finalizedHead) && (revokedAtBlock === void 0 ||
|
|
1261
|
+
return isVisible(claimedAtBlock, finalizedHead) && (revokedAtBlock === void 0 || isNonnegativeSafeInteger3(revokedAtBlock) && revokedAtBlock >= claimedAtBlock) && !isRevoked(revokedAtBlock, finalizedHead);
|
|
851
1262
|
}
|
|
852
1263
|
function isInsideExclusiveWindow(finalizedHead, validFromBlock, expiresAtBlock) {
|
|
853
|
-
|
|
854
|
-
return expiresAtBlock === void 0 || isNonnegativeSafeInteger2(expiresAtBlock) && expiresAtBlock > validFromBlock && finalizedHead < expiresAtBlock;
|
|
1264
|
+
return isNonnegativeSafeInteger3(validFromBlock) && finalizedHead >= validFromBlock && (expiresAtBlock === void 0 || isNonnegativeSafeInteger3(expiresAtBlock) && expiresAtBlock > validFromBlock && finalizedHead < expiresAtBlock);
|
|
855
1265
|
}
|
|
856
1266
|
function isRevisionShapeValid(revision) {
|
|
857
|
-
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1) return false;
|
|
858
|
-
if (!
|
|
859
|
-
if (revision.revokedAtBlock !== void 0 && (!isNonnegativeSafeInteger2(revision.revokedAtBlock) || revision.revokedAtBlock < revision.claimedAtBlock)) return false;
|
|
1267
|
+
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1 || !isNonnegativeSafeInteger3(revision.claimedAtBlock)) return false;
|
|
1268
|
+
if (revision.revokedAtBlock !== void 0 && (!isNonnegativeSafeInteger3(revision.revokedAtBlock) || revision.revokedAtBlock < revision.claimedAtBlock)) return false;
|
|
860
1269
|
return revision.revision === 1 ? revision.supersedes === void 0 : revision.supersedes !== void 0;
|
|
861
1270
|
}
|
|
862
1271
|
function resolveLinearChain(revisions, finalizedHead) {
|
|
@@ -929,8 +1338,7 @@ function selectPublisherService(input) {
|
|
|
929
1338
|
}
|
|
930
1339
|
}
|
|
931
1340
|
if (selectable.length === 0) return { ok: false, reason: "service-not-found" };
|
|
932
|
-
|
|
933
|
-
return { ok: true, service: selectable[0] };
|
|
1341
|
+
return selectable.length > 1 ? { ok: false, reason: "ambiguous-service" } : { ok: true, service: selectable[0] };
|
|
934
1342
|
}
|
|
935
1343
|
function resolvePreferredEncryptionKey(input) {
|
|
936
1344
|
const preferredHash = input.service.preferredEncryptionKey;
|
|
@@ -943,10 +1351,7 @@ function resolvePreferredEncryptionKey(input) {
|
|
|
943
1351
|
const key = exact[0];
|
|
944
1352
|
const sameStableIdentity = input.keys.filter((candidate) => candidate.source === key.source && candidate.keyId === key.keyId && candidate.objectHash !== key.objectHash && isVisible(candidate.claimedAtBlock, input.finalizedHead));
|
|
945
1353
|
if (sameStableIdentity.length > 0) return { ok: false, reason: "ambiguous-key" };
|
|
946
|
-
|
|
947
|
-
return { ok: false, reason: "preferred-key-inactive" };
|
|
948
|
-
}
|
|
949
|
-
return { ok: true, key };
|
|
1354
|
+
return !isUnrevokedEvidence(key.claimedAtBlock, key.revokedAtBlock, input.finalizedHead) || !isInsideExclusiveWindow(input.finalizedHead, key.validFromBlock, key.expiresAtBlock) ? { ok: false, reason: "preferred-key-inactive" } : { ok: true, key };
|
|
950
1355
|
}
|
|
951
1356
|
function acceptanceFor(subscription, input) {
|
|
952
1357
|
return input.acceptances.filter((acceptance) => acceptance.source === input.publisherSource && acceptance.subscription === subscription.objectHash && isVisible(acceptance.acceptanceBlock, input.finalizedHead));
|
|
@@ -955,7 +1360,7 @@ function acceptedSubscription(subscription, input) {
|
|
|
955
1360
|
const acceptances = acceptanceFor(subscription, input);
|
|
956
1361
|
if (acceptances.length > 1) return "ambiguous";
|
|
957
1362
|
const acceptance = acceptances[0];
|
|
958
|
-
if (acceptance === void 0 || !
|
|
1363
|
+
if (acceptance === void 0 || !isNonnegativeSafeInteger3(acceptance.acceptanceBlock)) return void 0;
|
|
959
1364
|
if (!Number.isSafeInteger(subscription.expiresAtBlock) || subscription.expiresAtBlock < 1) return void 0;
|
|
960
1365
|
if (acceptance.acceptanceBlock <= subscription.claimedAtBlock || acceptance.acceptanceBlock >= Number.MAX_SAFE_INTEGER) return void 0;
|
|
961
1366
|
const activeFromBlock = acceptance.acceptanceBlock + 1;
|
|
@@ -991,10 +1396,7 @@ function selectSubscriptionActivation(input) {
|
|
|
991
1396
|
if (selected === void 0) return { ok: false, reason: "subscription-pending" };
|
|
992
1397
|
const latest = resolution.chain.at(-1);
|
|
993
1398
|
if (!isActiveAcceptedSubscription(selected, input.finalizedHead)) {
|
|
994
|
-
|
|
995
|
-
return { ok: false, reason: "subscription-pending" };
|
|
996
|
-
}
|
|
997
|
-
return { ok: false, reason: "subscription-inactive" };
|
|
1399
|
+
return { ok: false, reason: latest?.objectHash === selected.subscription.objectHash ? "subscription-inactive" : "subscription-pending" };
|
|
998
1400
|
}
|
|
999
1401
|
return {
|
|
1000
1402
|
acceptance: selected.acceptance,
|
|
@@ -1225,43 +1627,20 @@ function createMemoryWakeQueue() {
|
|
|
1225
1627
|
|
|
1226
1628
|
// src/publisherSubscription.ts
|
|
1227
1629
|
import {
|
|
1228
|
-
ChainIdZod,
|
|
1630
|
+
ChainIdZod as ChainIdZod2,
|
|
1229
1631
|
EncryptedWakeSubscriptionZod as EncryptedWakeSubscriptionZod4,
|
|
1230
1632
|
EndpointChallengeZod as EndpointChallengeZod3,
|
|
1231
|
-
HashZod as
|
|
1633
|
+
HashZod as HashZod6,
|
|
1232
1634
|
hasMatchingHandshakeProfile,
|
|
1233
|
-
NormalizedAddressZod as
|
|
1635
|
+
NormalizedAddressZod as NormalizedAddressZod6,
|
|
1234
1636
|
PublisherEncryptionKeyZod as PublisherEncryptionKeyZod2,
|
|
1235
1637
|
PublisherServiceZod as PublisherServiceZod2,
|
|
1236
1638
|
WakeGrantObjectZod as WakeGrantObjectZod2
|
|
1237
1639
|
} from "@xyo-network/event-kit-schemas";
|
|
1238
|
-
|
|
1239
|
-
// src/sourceWakeIdentity.ts
|
|
1240
|
-
import {
|
|
1241
|
-
SourceWakeSubscriptionFilterZod,
|
|
1242
|
-
WakeSourceSchema,
|
|
1243
|
-
WakeSourceZod
|
|
1244
|
-
} from "@xyo-network/event-kit-schemas";
|
|
1245
|
-
import {
|
|
1246
|
-
asSchema,
|
|
1247
|
-
PayloadBuilder as PayloadBuilder4
|
|
1248
|
-
} from "@xyo-network/sdk";
|
|
1249
|
-
async function wakeSourceHash(source) {
|
|
1250
|
-
return await PayloadBuilder4.dataHash({ schema: WakeSourceSchema, ...WakeSourceZod.parse(source) });
|
|
1251
|
-
}
|
|
1252
|
-
function wakeSourcesEqual(left, right) {
|
|
1253
|
-
return canonicalJson(WakeSourceZod.parse(left)) === canonicalJson(WakeSourceZod.parse(right));
|
|
1254
|
-
}
|
|
1255
|
-
async function sourceWakeFilterHash(filter) {
|
|
1256
|
-
const parsed = SourceWakeSubscriptionFilterZod.parse(filter);
|
|
1257
|
-
return await PayloadBuilder4.dataHash({ ...parsed, schema: asSchema(parsed.schema, true) });
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// src/publisherSubscription.ts
|
|
1261
1640
|
function rejectPublisherSubscription(reason) {
|
|
1262
1641
|
return Object.freeze({ ok: false, reason });
|
|
1263
1642
|
}
|
|
1264
|
-
function
|
|
1643
|
+
function isNonnegativeSafeInteger4(value) {
|
|
1265
1644
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1266
1645
|
}
|
|
1267
1646
|
function isActiveAt(value, block) {
|
|
@@ -1278,7 +1657,7 @@ function hasExactQueueScope(grant, plaintext) {
|
|
|
1278
1657
|
return plaintext.deploymentId === grant.deploymentId && plaintext.queueId === grantQueue;
|
|
1279
1658
|
}
|
|
1280
1659
|
function hasExactSubscriptionBinding(candidate, subscription) {
|
|
1281
|
-
return subscription.chainId === candidate.chainId && subscription.publisher === candidate.publisher && subscription.service === candidate.service.objectHash && subscription.encryptionKey === candidate.key.objectHash && subscription.keyId === candidate.key.body.keyId && subscription.grant === candidate.grant.objectHash;
|
|
1660
|
+
return subscription.deliveryMode === candidate.service.body.deliveryMode && subscription.chainId === candidate.chainId && subscription.publisher === candidate.publisher && subscription.service === candidate.service.objectHash && subscription.encryptionKey === candidate.key.objectHash && subscription.keyId === candidate.key.body.keyId && subscription.grant === candidate.grant.objectHash;
|
|
1282
1661
|
}
|
|
1283
1662
|
function hasExactKeyBinding(service, key, subscription) {
|
|
1284
1663
|
return service.encryptionKeys.includes(key.objectHash) && service.preferredEncryptionKey === key.objectHash && key.body.keyId === subscription.keyId && key.objectHash === subscription.encryptionKey;
|
|
@@ -1289,22 +1668,22 @@ function expiryWithinServicePolicy(subscription, service, tupleBlock) {
|
|
|
1289
1668
|
function parsePublisherSubscriptionCandidate(candidate) {
|
|
1290
1669
|
try {
|
|
1291
1670
|
return {
|
|
1292
|
-
chainId:
|
|
1671
|
+
chainId: ChainIdZod2.parse(candidate.chainId),
|
|
1293
1672
|
grant: WakeGrantObjectZod2.parse(candidate.grant.body),
|
|
1294
1673
|
key: PublisherEncryptionKeyZod2.parse(candidate.key.body),
|
|
1295
|
-
publisher:
|
|
1674
|
+
publisher: NormalizedAddressZod6.parse(candidate.publisher),
|
|
1296
1675
|
service: PublisherServiceZod2.parse(candidate.service.body),
|
|
1297
|
-
subscriber:
|
|
1676
|
+
subscriber: NormalizedAddressZod6.parse(candidate.subscriber),
|
|
1298
1677
|
subscription: EncryptedWakeSubscriptionZod4.parse(candidate.subscription.body),
|
|
1299
|
-
tupleObjectHashes: candidate.tupleObjectHashes.map((hash) =>
|
|
1300
|
-
tupleSubject:
|
|
1678
|
+
tupleObjectHashes: candidate.tupleObjectHashes.map((hash) => HashZod6.parse(hash)),
|
|
1679
|
+
tupleSubject: NormalizedAddressZod6.parse(candidate.tupleSubject)
|
|
1301
1680
|
};
|
|
1302
1681
|
} catch {
|
|
1303
1682
|
return void 0;
|
|
1304
1683
|
}
|
|
1305
1684
|
}
|
|
1306
1685
|
function tupleBindingIsValid(candidate, parsed) {
|
|
1307
|
-
return
|
|
1686
|
+
return isNonnegativeSafeInteger4(candidate.tupleBlock) && isNonnegativeSafeInteger4(candidate.finalizedHead) && candidate.finalizedHead >= candidate.tupleBlock && parsed.tupleSubject === parsed.publisher && occursExactlyOnce(parsed.tupleObjectHashes, candidate.service.objectHash) && occursExactlyOnce(parsed.tupleObjectHashes, candidate.grant.objectHash) && occursExactlyOnce(parsed.tupleObjectHashes, candidate.subscription.objectHash);
|
|
1308
1687
|
}
|
|
1309
1688
|
async function bodyHashesAreValid(candidate, parsed) {
|
|
1310
1689
|
const canonicalHashes = await Promise.all([
|
|
@@ -1323,8 +1702,7 @@ function publicBindingFailure(candidate, parsed) {
|
|
|
1323
1702
|
if (!hasExactKeyBinding(parsed.service, hashedKey, parsed.subscription)) return "key-binding-invalid";
|
|
1324
1703
|
if (!isActiveAt(parsed.service, candidate.tupleBlock) || !isActiveAt(parsed.service, candidate.finalizedHead)) return "service-inactive";
|
|
1325
1704
|
if (!isActiveAt(parsed.key, candidate.tupleBlock) || !isActiveAt(parsed.key, candidate.finalizedHead)) return "key-inactive";
|
|
1326
|
-
|
|
1327
|
-
return void 0;
|
|
1705
|
+
return !expiryWithinServicePolicy(parsed.subscription, parsed.service, candidate.tupleBlock) || parsed.subscription.expiresAtBlock - candidate.finalizedHead <= 2 ? "subscription-expiry-invalid" : void 0;
|
|
1328
1706
|
}
|
|
1329
1707
|
async function decryptCandidate(candidate, parsed) {
|
|
1330
1708
|
let privateKey;
|
|
@@ -1388,19 +1766,14 @@ async function validateAndDecryptPublisherSubscription(candidate) {
|
|
|
1388
1766
|
if (!hasExactQueueScope(parsed.grant, decrypted.plaintext)) {
|
|
1389
1767
|
return rejectPublisherSubscription("grant-scope-invalid");
|
|
1390
1768
|
}
|
|
1391
|
-
|
|
1392
|
-
return rejectPublisherSubscription("service-limit-invalid");
|
|
1393
|
-
}
|
|
1394
|
-
return validatedPublisherSubscription(candidate, parsed, decrypted.plaintext);
|
|
1769
|
+
return filterWithinServicePolicy(parsed.service, decrypted.plaintext) ? validatedPublisherSubscription(candidate, parsed, decrypted.plaintext) : rejectPublisherSubscription("service-limit-invalid");
|
|
1395
1770
|
}
|
|
1396
1771
|
function endpointChallengeMatchesPublisherContext(challenge, context) {
|
|
1397
1772
|
const parsed = EndpointChallengeZod3.safeParse(challenge);
|
|
1398
|
-
|
|
1399
|
-
return parsed.data.chainId === context.chainId && parsed.data.publisher === context.publisher && parsed.data.subscriber === context.subscriber && parsed.data.service === context.service && parsed.data.encryptionKey === context.encryptionKey && parsed.data.keyId === context.keyId && parsed.data.subscription === context.subscription && parsed.data.grant === context.grant && parsed.data.eventKind === context.eventKind;
|
|
1773
|
+
return parsed.success && parsed.data.chainId === context.chainId && parsed.data.publisher === context.publisher && parsed.data.subscriber === context.subscriber && parsed.data.service === context.service && parsed.data.encryptionKey === context.encryptionKey && parsed.data.keyId === context.keyId && parsed.data.subscription === context.subscription && parsed.data.grant === context.grant && parsed.data.eventKind === context.eventKind;
|
|
1400
1774
|
}
|
|
1401
1775
|
async function challengeRecordMatches(record, challenge, challengeHash, responseHash) {
|
|
1402
|
-
|
|
1403
|
-
return responseHash === void 0 || record.responseHash === responseHash;
|
|
1776
|
+
return record.challengeHash === challengeHash && await endpointChallengeHash(record.challenge) === challengeHash && await endpointChallengeHash(challenge) === challengeHash && (responseHash === void 0 || record.responseHash === responseHash);
|
|
1404
1777
|
}
|
|
1405
1778
|
async function issuePublisherEndpointChallenge(input) {
|
|
1406
1779
|
const context = input.subscription.context;
|
|
@@ -1428,10 +1801,10 @@ async function issuePublisherEndpointChallenge(input) {
|
|
|
1428
1801
|
}
|
|
1429
1802
|
}
|
|
1430
1803
|
async function beginPublisherEndpointChallengeAttempt(input) {
|
|
1431
|
-
if (!
|
|
1804
|
+
if (!isNonnegativeSafeInteger4(input.nowSeconds)) return { kind: "store-invalid" };
|
|
1432
1805
|
try {
|
|
1433
1806
|
const result = await input.store.beginAttempt({
|
|
1434
|
-
challengeHash:
|
|
1807
|
+
challengeHash: HashZod6.parse(input.challengeHash),
|
|
1435
1808
|
nowSeconds: input.nowSeconds
|
|
1436
1809
|
});
|
|
1437
1810
|
if (!["already-attempted", "expired", "missing", "started", "verified"].includes(result.kind)) {
|
|
@@ -1440,11 +1813,10 @@ async function beginPublisherEndpointChallengeAttempt(input) {
|
|
|
1440
1813
|
if (result.kind !== "missing" && !await challengeRecordMatches(
|
|
1441
1814
|
result.record,
|
|
1442
1815
|
result.record.challenge,
|
|
1443
|
-
|
|
1816
|
+
HashZod6.parse(input.challengeHash)
|
|
1444
1817
|
)) return { kind: "store-invalid" };
|
|
1445
1818
|
if ((result.kind === "started" || result.kind === "already-attempted") && result.record.state !== "attempted") return { kind: "store-invalid" };
|
|
1446
|
-
|
|
1447
|
-
return result;
|
|
1819
|
+
return result.kind === "verified" && result.record.state !== "verified" ? { kind: "store-invalid" } : result;
|
|
1448
1820
|
} catch {
|
|
1449
1821
|
return { kind: "store-unavailable" };
|
|
1450
1822
|
}
|
|
@@ -1461,16 +1833,13 @@ async function validateChallengeCompletion(input, result, challengeHash, respons
|
|
|
1461
1833
|
if (result.kind === "expired") return completeFailure("challenge-expired");
|
|
1462
1834
|
if (result.kind === "not-attempted") return completeFailure("challenge-not-attempted");
|
|
1463
1835
|
if (result.kind === "collision") return completeFailure("challenge-response-collision");
|
|
1464
|
-
|
|
1465
|
-
return completeFailure("challenge-store-invalid");
|
|
1466
|
-
}
|
|
1467
|
-
return result;
|
|
1836
|
+
return result.kind !== "duplicate" && result.kind !== "verified" || !await challengeRecordMatches(result.record, input.challenge, challengeHash, responseHash) ? completeFailure("challenge-store-invalid") : result;
|
|
1468
1837
|
}
|
|
1469
1838
|
async function completePublisherEndpointChallenge(input) {
|
|
1470
1839
|
if (!endpointChallengeMatchesPublisherContext(input.challenge, input.subscription.context)) {
|
|
1471
1840
|
return completeFailure("challenge-context-invalid");
|
|
1472
1841
|
}
|
|
1473
|
-
if (!
|
|
1842
|
+
if (!isNonnegativeSafeInteger4(input.nowSeconds)) return completeFailure("challenge-expired");
|
|
1474
1843
|
const insideDecisionWindow = input.challenge.issuedAt <= input.nowSeconds && input.nowSeconds < input.challenge.expiresAt;
|
|
1475
1844
|
const proofInput = {
|
|
1476
1845
|
challenge: input.challenge,
|
|
@@ -1520,33 +1889,33 @@ async function completePublisherEndpointChallenge(input) {
|
|
|
1520
1889
|
}
|
|
1521
1890
|
function filterWithinServicePolicy(service, plaintext) {
|
|
1522
1891
|
const filter = plaintext.filter;
|
|
1523
|
-
return filter.maxPositionsPerWake <= service.limits.maxPositionsPerWake && service.events.some((event) => wakeSourcesEqual(event.source, filter.source));
|
|
1892
|
+
return service.deliveryMode === filter.deliveryMode && JSON.stringify(service.bestEffort) === JSON.stringify(filter.bestEffort) && filter.maxPositionsPerWake <= service.limits.maxPositionsPerWake && service.events.some((event) => wakeSourcesEqual(event.source, filter.source));
|
|
1524
1893
|
}
|
|
1525
1894
|
|
|
1526
1895
|
// src/sourceWakeActivation.ts
|
|
1527
1896
|
import {
|
|
1528
|
-
ChainIdZod as
|
|
1529
|
-
DeploymentIdZod as
|
|
1530
|
-
HashZod as
|
|
1531
|
-
NonnegativeJsonSafeIntegerZod as
|
|
1532
|
-
NormalizedAddressZod as
|
|
1533
|
-
PositiveJsonSafeIntegerZod
|
|
1897
|
+
ChainIdZod as ChainIdZod3,
|
|
1898
|
+
DeploymentIdZod as DeploymentIdZod3,
|
|
1899
|
+
HashZod as HashZod7,
|
|
1900
|
+
NonnegativeJsonSafeIntegerZod as NonnegativeJsonSafeIntegerZod4,
|
|
1901
|
+
NormalizedAddressZod as NormalizedAddressZod7,
|
|
1902
|
+
PositiveJsonSafeIntegerZod as PositiveJsonSafeIntegerZod2
|
|
1534
1903
|
} from "@xyo-network/event-kit-schemas";
|
|
1535
|
-
import { z as
|
|
1536
|
-
var SourceWakeSubscriptionActivationZod =
|
|
1537
|
-
atBlock:
|
|
1538
|
-
activeFromBlock:
|
|
1539
|
-
expiresAtBlock:
|
|
1540
|
-
authorizationChainId:
|
|
1541
|
-
controller:
|
|
1542
|
-
publisher:
|
|
1543
|
-
deploymentId:
|
|
1544
|
-
queueId:
|
|
1545
|
-
sourceHash:
|
|
1546
|
-
filterHash:
|
|
1547
|
-
subscriptionHash:
|
|
1548
|
-
serviceHash:
|
|
1549
|
-
grantHash:
|
|
1904
|
+
import { z as z4 } from "zod";
|
|
1905
|
+
var SourceWakeSubscriptionActivationZod = z4.strictObject({
|
|
1906
|
+
atBlock: NonnegativeJsonSafeIntegerZod4,
|
|
1907
|
+
activeFromBlock: NonnegativeJsonSafeIntegerZod4,
|
|
1908
|
+
expiresAtBlock: PositiveJsonSafeIntegerZod2,
|
|
1909
|
+
authorizationChainId: ChainIdZod3,
|
|
1910
|
+
controller: NormalizedAddressZod7,
|
|
1911
|
+
publisher: NormalizedAddressZod7,
|
|
1912
|
+
deploymentId: DeploymentIdZod3,
|
|
1913
|
+
queueId: z4.string().min(1).max(255).optional(),
|
|
1914
|
+
sourceHash: HashZod7,
|
|
1915
|
+
filterHash: HashZod7,
|
|
1916
|
+
subscriptionHash: HashZod7,
|
|
1917
|
+
serviceHash: HashZod7,
|
|
1918
|
+
grantHash: HashZod7
|
|
1550
1919
|
}).refine(
|
|
1551
1920
|
(value) => value.activeFromBlock <= value.atBlock && value.atBlock < value.expiresAtBlock,
|
|
1552
1921
|
"Subscription must be active at the exact finalized authorization snapshot"
|
|
@@ -1554,17 +1923,17 @@ var SourceWakeSubscriptionActivationZod = z2.strictObject({
|
|
|
1554
1923
|
|
|
1555
1924
|
// src/statementGraphWakeGrantAuthorizer.ts
|
|
1556
1925
|
import {
|
|
1557
|
-
ChainIdZod as
|
|
1558
|
-
WakeSourceZod as
|
|
1926
|
+
ChainIdZod as ChainIdZod4,
|
|
1927
|
+
WakeSourceZod as WakeSourceZod3
|
|
1559
1928
|
} from "@xyo-network/event-kit-schemas";
|
|
1560
1929
|
|
|
1561
1930
|
// src/grantEvidence.ts
|
|
1562
1931
|
import {
|
|
1563
1932
|
asWakeGrantObject,
|
|
1564
1933
|
asWakeGrantRevocation,
|
|
1565
|
-
DeploymentIdZod as
|
|
1566
|
-
HashZod as
|
|
1567
|
-
NormalizedAddressZod as
|
|
1934
|
+
DeploymentIdZod as DeploymentIdZod4,
|
|
1935
|
+
HashZod as HashZod8,
|
|
1936
|
+
NormalizedAddressZod as NormalizedAddressZod8,
|
|
1568
1937
|
WakeGrantObjectSchema as WakeGrantObjectSchema3,
|
|
1569
1938
|
WakeGrantRevocationSchema as WakeGrantRevocationSchema3
|
|
1570
1939
|
} from "@xyo-network/event-kit-schemas";
|
|
@@ -1620,9 +1989,7 @@ async function readRevocations(viewer, controller, subject, atBlock) {
|
|
|
1620
1989
|
for (const claim of claims) {
|
|
1621
1990
|
if (!isLiveClaim(claim, controller, subject, WakeGrantRevocationSchema3, atBlock)) continue;
|
|
1622
1991
|
const object = await viewer.object(claim.objectHash);
|
|
1623
|
-
if (object?.assurance !== "resolved") continue;
|
|
1624
|
-
if (object.declaredSchema !== WakeGrantRevocationSchema3 || object.objectHash !== claim.objectHash) continue;
|
|
1625
|
-
if (!isRecord2(object.params) || Object.hasOwn(object.params, "schema")) continue;
|
|
1992
|
+
if (object?.assurance !== "resolved" || object.declaredSchema !== WakeGrantRevocationSchema3 || object.objectHash !== claim.objectHash || !isRecord3(object.params) || Object.hasOwn(object.params, "schema")) continue;
|
|
1626
1993
|
const revocation = asWakeGrantRevocation({ ...object.params, schema: object.declaredSchema });
|
|
1627
1994
|
if (revocation !== void 0) revoked.add(revocation.grant);
|
|
1628
1995
|
}
|
|
@@ -1654,61 +2021,55 @@ async function resolveEvidence(viewer, claim, request, atBlock, revokedGrantHash
|
|
|
1654
2021
|
});
|
|
1655
2022
|
}
|
|
1656
2023
|
function isLiveClaim(claim, controller, subject, declaredObjectSchema, atBlock) {
|
|
1657
|
-
|
|
1658
|
-
if (claim.subject !== subject) return false;
|
|
1659
|
-
if (claim.declaredObjectSchema !== declaredObjectSchema) return false;
|
|
1660
|
-
if (!isNonnegativeSafeInteger4(claim.firstBlock) || claim.firstBlock > atBlock) return false;
|
|
1661
|
-
if (!isNonnegativeSafeInteger4(claim.lastBlock) || claim.lastBlock < claim.firstBlock) return false;
|
|
1662
|
-
if (!HashZod6.safeParse(claim.firstEventHash).success) return false;
|
|
1663
|
-
return HashZod6.safeParse(claim.objectHash).success;
|
|
2024
|
+
return claim.source === controller && claim.subject === subject && claim.declaredObjectSchema === declaredObjectSchema && isNonnegativeSafeInteger5(claim.firstBlock) && claim.firstBlock <= atBlock && isNonnegativeSafeInteger5(claim.lastBlock) && claim.lastBlock >= claim.firstBlock && HashZod8.safeParse(claim.firstEventHash).success && HashZod8.safeParse(claim.objectHash).success;
|
|
1664
2025
|
}
|
|
1665
2026
|
function parseExactGrantObject(object, expectedObjectHash) {
|
|
1666
2027
|
if (object?.assurance !== "resolved") return void 0;
|
|
1667
2028
|
if (object.declaredSchema !== WakeGrantObjectSchema3 || object.objectHash !== expectedObjectHash) return void 0;
|
|
1668
|
-
if (!
|
|
2029
|
+
if (!isRecord3(object.params) || Object.hasOwn(object.params, "schema")) return void 0;
|
|
1669
2030
|
return asWakeGrantObject({
|
|
1670
2031
|
...object.params,
|
|
1671
2032
|
schema: object.declaredSchema
|
|
1672
2033
|
});
|
|
1673
2034
|
}
|
|
1674
2035
|
function assertValidRequest(request) {
|
|
1675
|
-
if (request.exactGrantHash !== void 0 && !
|
|
2036
|
+
if (request.exactGrantHash !== void 0 && !HashZod8.safeParse(request.exactGrantHash).success) {
|
|
1676
2037
|
throw new TypeError("Exact grant hash must be canonical lowercase hexadecimal");
|
|
1677
2038
|
}
|
|
1678
|
-
if (!
|
|
2039
|
+
if (!NormalizedAddressZod8.safeParse(request.controller).success) {
|
|
1679
2040
|
throw new TypeError("Grant controller must be a normalized address");
|
|
1680
2041
|
}
|
|
1681
|
-
if (!
|
|
2042
|
+
if (!NormalizedAddressZod8.safeParse(request.publisher).success) {
|
|
1682
2043
|
throw new TypeError("Grant publisher must be a normalized address");
|
|
1683
2044
|
}
|
|
1684
|
-
if (!
|
|
2045
|
+
if (!DeploymentIdZod4.safeParse(request.deploymentId).success) {
|
|
1685
2046
|
throw new TypeError("Grant deploymentId must contain 1 to 255 characters");
|
|
1686
2047
|
}
|
|
1687
2048
|
if (request.queueId !== void 0 && (request.queueId.length === 0 || request.queueId.length > 255)) {
|
|
1688
2049
|
throw new TypeError("Grant queueId must contain 1 to 255 characters when configured");
|
|
1689
2050
|
}
|
|
1690
|
-
if (!
|
|
2051
|
+
if (!isNonnegativeSafeInteger5(request.expectedHead)) {
|
|
1691
2052
|
throw new RangeError("Grant expectedHead must be a nonnegative safe integer");
|
|
1692
2053
|
}
|
|
1693
2054
|
}
|
|
1694
2055
|
async function readHead(viewer) {
|
|
1695
2056
|
const head = await viewer.head();
|
|
1696
|
-
if (!
|
|
2057
|
+
if (!isNonnegativeSafeInteger5(head)) {
|
|
1697
2058
|
throw new RangeError("Statement Graph head must be a nonnegative safe integer");
|
|
1698
2059
|
}
|
|
1699
2060
|
return head;
|
|
1700
2061
|
}
|
|
1701
|
-
function
|
|
2062
|
+
function isNonnegativeSafeInteger5(value) {
|
|
1702
2063
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1703
2064
|
}
|
|
1704
|
-
function
|
|
2065
|
+
function isRecord3(value) {
|
|
1705
2066
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1706
2067
|
}
|
|
1707
2068
|
|
|
1708
2069
|
// src/statementGraphWakeGrantAuthorizer.ts
|
|
1709
2070
|
function createStatementGraphWakeGrantAuthorizer(options) {
|
|
1710
|
-
const authorizationChainId =
|
|
1711
|
-
const source =
|
|
2071
|
+
const authorizationChainId = ChainIdZod4.parse(options.authorizationChainId);
|
|
2072
|
+
const source = WakeSourceZod3.parse(options.source);
|
|
1712
2073
|
const grants = createDeploymentGrantAuthorizer({ viewer: options.viewer });
|
|
1713
2074
|
return Object.freeze({
|
|
1714
2075
|
async authorizePublisher(request) {
|
|
@@ -1740,196 +2101,6 @@ function createStatementGraphWakeGrantAuthorizer(options) {
|
|
|
1740
2101
|
});
|
|
1741
2102
|
}
|
|
1742
2103
|
|
|
1743
|
-
// src/verifyWakeRequest.ts
|
|
1744
|
-
import {
|
|
1745
|
-
ChainIdZod as ChainIdZod4,
|
|
1746
|
-
DeploymentIdZod as DeploymentIdZod4,
|
|
1747
|
-
HashZod as HashZod7,
|
|
1748
|
-
MAX_POSITIONS_PER_SUBSCRIPTION_WAKE,
|
|
1749
|
-
NonnegativeJsonSafeIntegerZod as NonnegativeJsonSafeIntegerZod3,
|
|
1750
|
-
NormalizedAddressZod as NormalizedAddressZod7,
|
|
1751
|
-
PositiveJsonSafeIntegerZod as PositiveJsonSafeIntegerZod2,
|
|
1752
|
-
SOURCE_WAKE_FILTER_PROFILE as SOURCE_WAKE_FILTER_PROFILE2,
|
|
1753
|
-
WakeEnvelopeZod as WakeEnvelopeZod2,
|
|
1754
|
-
WakeJwtAudienceZod as WakeJwtAudienceZod2,
|
|
1755
|
-
WakeJwtHeaderZod,
|
|
1756
|
-
WakeJwtPayloadZod,
|
|
1757
|
-
WakeJwtSchema,
|
|
1758
|
-
WakeSourceZod as WakeSourceZod3
|
|
1759
|
-
} from "@xyo-network/event-kit-schemas";
|
|
1760
|
-
import {
|
|
1761
|
-
Account,
|
|
1762
|
-
PayloadBuilder as PayloadBuilder5
|
|
1763
|
-
} from "@xyo-network/sdk";
|
|
1764
|
-
import { z as z3 } from "zod";
|
|
1765
|
-
var EVENT_KIT_MAX_JWT_LIFETIME_SECONDS = 300;
|
|
1766
|
-
var EVENT_KIT_MAX_FUTURE_IAT_SECONDS = 60;
|
|
1767
|
-
var WakeAdmissionRouteZod = z3.strictObject({
|
|
1768
|
-
audience: WakeJwtAudienceZod2,
|
|
1769
|
-
authorizationChainId: ChainIdZod4,
|
|
1770
|
-
controller: NormalizedAddressZod7,
|
|
1771
|
-
deploymentId: DeploymentIdZod4,
|
|
1772
|
-
queueId: z3.string().min(1).max(255).optional(),
|
|
1773
|
-
source: WakeSourceZod3,
|
|
1774
|
-
filterHash: HashZod7,
|
|
1775
|
-
subscriptionHash: HashZod7,
|
|
1776
|
-
fromPosition: NonnegativeJsonSafeIntegerZod3,
|
|
1777
|
-
maxPositionsPerWake: PositiveJsonSafeIntegerZod2.max(MAX_POSITIONS_PER_SUBSCRIPTION_WAKE),
|
|
1778
|
-
maxGrantLagBlocks: NonnegativeJsonSafeIntegerZod3
|
|
1779
|
-
});
|
|
1780
|
-
async function verifyWakeRequest(input) {
|
|
1781
|
-
const routeResult = WakeAdmissionRouteZod.safeParse(input.route);
|
|
1782
|
-
if (!routeResult.success || !NonnegativeJsonSafeIntegerZod3.safeParse(input.nowSeconds).success) {
|
|
1783
|
-
return { ok: false, reason: "configuration-invalid" };
|
|
1784
|
-
}
|
|
1785
|
-
const eventResult = WakeEnvelopeZod2.safeParse(input.body);
|
|
1786
|
-
if (!eventResult.success) return malformedWake(input.body, eventResult.error.issues);
|
|
1787
|
-
const route = routeResult.data;
|
|
1788
|
-
if (route.filterHash !== await sourceWakeFilterHash({
|
|
1789
|
-
schema: SOURCE_WAKE_FILTER_PROFILE2,
|
|
1790
|
-
source: route.source,
|
|
1791
|
-
fromPosition: route.fromPosition,
|
|
1792
|
-
maxPositionsPerWake: route.maxPositionsPerWake
|
|
1793
|
-
})) return { ok: false, reason: "configuration-invalid" };
|
|
1794
|
-
const event = Object.freeze({ ...eventResult.data, source: Object.freeze(eventResult.data.source) });
|
|
1795
|
-
if (event.authorizationChainId !== route.authorizationChainId) {
|
|
1796
|
-
return { ok: false, reason: "event-authorization-chain-mismatch" };
|
|
1797
|
-
}
|
|
1798
|
-
if (!wakeSourcesEqual(event.source, route.source)) return { ok: false, reason: "event-source-mismatch" };
|
|
1799
|
-
if (event.filterHash !== route.filterHash) return { ok: false, reason: "event-filter-mismatch" };
|
|
1800
|
-
if (event.deploymentId !== route.deploymentId || event.queueId !== route.queueId) {
|
|
1801
|
-
return { ok: false, reason: "event-target-mismatch" };
|
|
1802
|
-
}
|
|
1803
|
-
if (event.fromPosition < route.fromPosition) return { ok: false, reason: "range-before-floor" };
|
|
1804
|
-
if (event.toPosition - event.fromPosition >= route.maxPositionsPerWake) return { ok: false, reason: "range-too-large" };
|
|
1805
|
-
const jwt = await verifyJwtProfile(input.jwt, route.audience, input.nowSeconds);
|
|
1806
|
-
if (!jwt.ok) return jwt;
|
|
1807
|
-
let eventHash;
|
|
1808
|
-
try {
|
|
1809
|
-
eventHash = await PayloadBuilder5.dataHash(event);
|
|
1810
|
-
} catch {
|
|
1811
|
-
return { ok: false, reason: "event-hash-failed" };
|
|
1812
|
-
}
|
|
1813
|
-
if (eventHash !== jwt.jwt.eventHash) return { ok: false, reason: "event-hash-mismatch" };
|
|
1814
|
-
const sourceHash = await wakeSourceHash(route.source);
|
|
1815
|
-
return Object.freeze({
|
|
1816
|
-
ok: true,
|
|
1817
|
-
verified: Object.freeze({
|
|
1818
|
-
audience: route.audience,
|
|
1819
|
-
deploymentId: event.deploymentId,
|
|
1820
|
-
event,
|
|
1821
|
-
eventHash,
|
|
1822
|
-
expiresAt: jwt.jwt.expiresAt,
|
|
1823
|
-
fromPosition: event.fromPosition,
|
|
1824
|
-
issuedAt: event.issuedAt,
|
|
1825
|
-
jwtId: jwt.jwt.jwtId,
|
|
1826
|
-
publisher: jwt.jwt.publisher,
|
|
1827
|
-
...event.queueId === void 0 ? {} : { queueId: event.queueId },
|
|
1828
|
-
route: Object.freeze({
|
|
1829
|
-
...route,
|
|
1830
|
-
source: Object.freeze(route.source),
|
|
1831
|
-
sourceHash
|
|
1832
|
-
}),
|
|
1833
|
-
toPosition: event.toPosition,
|
|
1834
|
-
tokenIssuedAt: jwt.jwt.issuedAt,
|
|
1835
|
-
wakeId: event.wakeId
|
|
1836
|
-
})
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
async function verifyJwtProfile(token, audience, nowSeconds) {
|
|
1840
|
-
let verified;
|
|
1841
|
-
try {
|
|
1842
|
-
verified = await Account.verifyJwt(token, { audience, now: nowSeconds });
|
|
1843
|
-
} catch (error) {
|
|
1844
|
-
return reject2("jwt-malformed", [errorMessage2(error)]);
|
|
1845
|
-
}
|
|
1846
|
-
if (!verified.ok) {
|
|
1847
|
-
if (Object.hasOwn(verified.payload ?? {}, "nbf")) return reject2("jwt-nbf-not-allowed");
|
|
1848
|
-
return reject2(classifySdkJwtFailure(verified.reasons), verified.reasons);
|
|
1849
|
-
}
|
|
1850
|
-
const wireResult = parseStrictJwtWire(verified.header, verified.payload, audience);
|
|
1851
|
-
if (!wireResult.ok) return wireResult;
|
|
1852
|
-
const { header: strictHeader, payload: strictPayload } = wireResult;
|
|
1853
|
-
if (strictHeader.kid !== strictPayload.iss) return reject2("jwt-identity-invalid");
|
|
1854
|
-
if (strictPayload.exp <= nowSeconds) return reject2("jwt-expired");
|
|
1855
|
-
if (strictPayload.exp - strictPayload.iat > EVENT_KIT_MAX_JWT_LIFETIME_SECONDS) return reject2("jwt-lifetime-invalid");
|
|
1856
|
-
if (strictPayload.iat - nowSeconds > EVENT_KIT_MAX_FUTURE_IAT_SECONDS) return reject2("jwt-future-iat");
|
|
1857
|
-
const jwt = Object.freeze({
|
|
1858
|
-
eventHash: strictPayload.eventHash,
|
|
1859
|
-
expiresAt: strictPayload.exp,
|
|
1860
|
-
issuedAt: strictPayload.iat,
|
|
1861
|
-
jwtId: strictPayload.jti,
|
|
1862
|
-
publisher: strictPayload.iss
|
|
1863
|
-
});
|
|
1864
|
-
return Object.freeze({ jwt, ok: true });
|
|
1865
|
-
}
|
|
1866
|
-
function parseStrictJwtWire(header, payload, audience) {
|
|
1867
|
-
const headerResult = WakeJwtHeaderZod.safeParse(header);
|
|
1868
|
-
if (!headerResult.success) {
|
|
1869
|
-
if (!NormalizedAddressZod7.safeParse(header.kid).success) return reject2("jwt-identity-invalid");
|
|
1870
|
-
return reject2("jwt-header-invalid", zodDetails(headerResult.error.issues));
|
|
1871
|
-
}
|
|
1872
|
-
if (Object.hasOwn(payload, "nbf")) return reject2("jwt-nbf-not-allowed");
|
|
1873
|
-
if (!NormalizedAddressZod7.safeParse(payload.iss).success) return reject2("jwt-identity-invalid");
|
|
1874
|
-
if (payload.schema !== WakeJwtSchema) return reject2("jwt-schema-mismatch");
|
|
1875
|
-
if (payload.aud !== audience) return reject2("jwt-audience-mismatch");
|
|
1876
|
-
if (!isNonnegativeSafeInteger5(payload.iat) || !isNonnegativeSafeInteger5(payload.exp) || payload.exp <= payload.iat) {
|
|
1877
|
-
return reject2("jwt-lifetime-invalid");
|
|
1878
|
-
}
|
|
1879
|
-
const payloadResult = WakeJwtPayloadZod.safeParse(payload);
|
|
1880
|
-
if (!payloadResult.success) return reject2("jwt-claims-invalid", zodDetails(payloadResult.error.issues));
|
|
1881
|
-
return Object.freeze({
|
|
1882
|
-
header: headerResult.data,
|
|
1883
|
-
ok: true,
|
|
1884
|
-
payload: payloadResult.data
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
function classifySdkJwtFailure(reasons) {
|
|
1888
|
-
if (reasons.some((reason) => reason.startsWith("audience mismatch:"))) return "jwt-audience-mismatch";
|
|
1889
|
-
if (reasons.some((reason) => reason.startsWith("expired:"))) return "jwt-expired";
|
|
1890
|
-
if (reasons.some((reason) => reason.startsWith("unsupported alg:") || reason.startsWith("unsupported typ:"))) {
|
|
1891
|
-
return "jwt-header-invalid";
|
|
1892
|
-
}
|
|
1893
|
-
if (reasons.some((reason) => reason.startsWith("invalid public key:") || reason.startsWith("public key does not derive to kid:") || reason === "header.kid does not match payload.iss")) {
|
|
1894
|
-
return "jwt-identity-invalid";
|
|
1895
|
-
}
|
|
1896
|
-
return "jwt-invalid";
|
|
1897
|
-
}
|
|
1898
|
-
function malformedWake(body, issues) {
|
|
1899
|
-
const details = zodDetails(issues);
|
|
1900
|
-
if (hasInvertedSafeRange(body)) return reject2("range-invalid", details);
|
|
1901
|
-
if (isRecord3(body) && Object.hasOwn(body, "wakeId") && issues.some((issue) => issue.path[0] === "wakeId")) return reject2("wake-id-invalid", details);
|
|
1902
|
-
return reject2("event-malformed", details);
|
|
1903
|
-
}
|
|
1904
|
-
function hasInvertedSafeRange(value) {
|
|
1905
|
-
if (!isRecord3(value)) return false;
|
|
1906
|
-
const { fromPosition, toPosition } = value;
|
|
1907
|
-
return typeof fromPosition === "number" && typeof toPosition === "number" && isNonnegativeSafeInteger5(fromPosition) && isNonnegativeSafeInteger5(toPosition) && fromPosition > toPosition;
|
|
1908
|
-
}
|
|
1909
|
-
function isNonnegativeSafeInteger5(value) {
|
|
1910
|
-
return Number.isSafeInteger(value) && value >= 0;
|
|
1911
|
-
}
|
|
1912
|
-
function isRecord3(value) {
|
|
1913
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1914
|
-
}
|
|
1915
|
-
function zodDetails(issues) {
|
|
1916
|
-
return issues.map((issue) => {
|
|
1917
|
-
const path = issue.path.map(String).join(".") || "<root>";
|
|
1918
|
-
return `${path}: ${issue.message}`;
|
|
1919
|
-
});
|
|
1920
|
-
}
|
|
1921
|
-
function reject2(reason, details) {
|
|
1922
|
-
if (details === void 0) return Object.freeze({ ok: false, reason });
|
|
1923
|
-
return Object.freeze({
|
|
1924
|
-
details: Object.freeze([...details]),
|
|
1925
|
-
ok: false,
|
|
1926
|
-
reason
|
|
1927
|
-
});
|
|
1928
|
-
}
|
|
1929
|
-
function errorMessage2(error) {
|
|
1930
|
-
return error instanceof Error ? error.message : String(error);
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
2104
|
// src/wakeQueue.ts
|
|
1934
2105
|
async function drainQueue(queue, handler) {
|
|
1935
2106
|
let handled = 0;
|
|
@@ -1948,6 +2119,8 @@ async function drainQueue(queue, handler) {
|
|
|
1948
2119
|
}
|
|
1949
2120
|
export {
|
|
1950
2121
|
AdmittedWakeZod,
|
|
2122
|
+
BestEffortAdmissionAcknowledgmentZod,
|
|
2123
|
+
BestEffortSecurityStateZod,
|
|
1951
2124
|
EVENT_KIT_MAX_FUTURE_IAT_SECONDS,
|
|
1952
2125
|
EVENT_KIT_MAX_JWT_LIFETIME_SECONDS,
|
|
1953
2126
|
EVENT_KIT_REPLAY_RETENTION_SKEW_SECONDS,
|
|
@@ -1955,10 +2128,13 @@ export {
|
|
|
1955
2128
|
WakeAdmissionRouteZod,
|
|
1956
2129
|
WakeSubscriptionDecryptionError,
|
|
1957
2130
|
admitAndEnqueue,
|
|
2131
|
+
admitBestEffortHint,
|
|
1958
2132
|
beginPublisherEndpointChallengeAttempt,
|
|
1959
2133
|
canonicalJson,
|
|
1960
2134
|
canonicalJsonBytes,
|
|
2135
|
+
commitBestEffortSecurity,
|
|
1961
2136
|
completePublisherEndpointChallenge,
|
|
2137
|
+
createBestEffortSecurityState,
|
|
1962
2138
|
createEncryptedWakeSubscription,
|
|
1963
2139
|
createEndpointChallenge,
|
|
1964
2140
|
createEndpointChallengeResponse,
|
|
@@ -1986,6 +2162,7 @@ export {
|
|
|
1986
2162
|
handshakeObjectHash,
|
|
1987
2163
|
issuePublisherEndpointChallenge,
|
|
1988
2164
|
parseAdmittedWake,
|
|
2165
|
+
parseBestEffortSecurityState,
|
|
1989
2166
|
resolvePreferredEncryptionKey,
|
|
1990
2167
|
selectPublisherService,
|
|
1991
2168
|
selectSubscriptionActivation,
|