@metalabel/dfos-web-relay 0.9.0 → 0.11.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/README.md +12 -13
- package/dist/index.d.ts +59 -23
- package/dist/index.js +322 -174
- package/dist/serve.js +28 -1
- package/openapi.yaml +84 -50
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
createNewEd25519Keypair,
|
|
9
9
|
generateId,
|
|
10
|
+
importEd25519Keypair,
|
|
10
11
|
signPayloadEd25519
|
|
11
12
|
} from "@metalabel/dfos-protocol/crypto";
|
|
12
13
|
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
import {
|
|
15
16
|
decodeMultikey,
|
|
16
17
|
verifyArtifact,
|
|
17
|
-
verifyBeacon,
|
|
18
18
|
verifyContentChain,
|
|
19
19
|
verifyContentExtensionFromTrustedState,
|
|
20
20
|
verifyCountersignature,
|
|
@@ -26,6 +26,16 @@ import {
|
|
|
26
26
|
verifyDFOSCredential
|
|
27
27
|
} from "@metalabel/dfos-protocol/credentials";
|
|
28
28
|
import { dagCborCanonicalEncode, decodeJwsUnsafe } from "@metalabel/dfos-protocol/crypto";
|
|
29
|
+
var FORK_POINT_STATE_ERROR_PREFIX = "failed to compute state at fork point: ";
|
|
30
|
+
var DEPENDENCY_FAILURE_SUBSTRINGS = ["unknown identity:", "unknown key "];
|
|
31
|
+
var isKeyResolutionFailure = (message) => DEPENDENCY_FAILURE_SUBSTRINGS.some((s) => message.includes(s));
|
|
32
|
+
var isCredentialDependencyFailure = (message) => message.includes("issuer identity not found:") || message.includes(" not found on identity ");
|
|
33
|
+
var computeOpCID = async (jwsToken) => {
|
|
34
|
+
const decoded = decodeJwsUnsafe(jwsToken);
|
|
35
|
+
if (!decoded) return "";
|
|
36
|
+
const encoded = await dagCborCanonicalEncode(decoded.payload);
|
|
37
|
+
return encoded.cid.toString();
|
|
38
|
+
};
|
|
29
39
|
var MAX_FUTURE_TIMESTAMP_MS = 24 * 60 * 60 * 1e3;
|
|
30
40
|
var isFutureTimestamp = (createdAt) => {
|
|
31
41
|
const ts = new Date(createdAt).getTime();
|
|
@@ -60,17 +70,6 @@ var classify = (jwsToken) => {
|
|
|
60
70
|
const opDID = typeof payload["did"] === "string" ? payload["did"] : null;
|
|
61
71
|
return { ...base, kind: "content-op", referencedDID: null, signerDID: opDID, priority: 2 };
|
|
62
72
|
}
|
|
63
|
-
if (typ === "did:dfos:beacon") {
|
|
64
|
-
const beaconDID = typeof payload["did"] === "string" ? payload["did"] : null;
|
|
65
|
-
return {
|
|
66
|
-
...base,
|
|
67
|
-
kind: "beacon",
|
|
68
|
-
referencedDID: beaconDID,
|
|
69
|
-
signerDID: null,
|
|
70
|
-
priority: 1,
|
|
71
|
-
previousCID: null
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
73
|
if (typ === "did:dfos:countersign") {
|
|
75
74
|
const witnessDID = typeof payload["did"] === "string" ? payload["did"] : null;
|
|
76
75
|
return {
|
|
@@ -91,7 +90,7 @@ var classify = (jwsToken) => {
|
|
|
91
90
|
referencedDID: artifactDID,
|
|
92
91
|
signerDID: null,
|
|
93
92
|
priority: 1,
|
|
94
|
-
//
|
|
93
|
+
// needs identity keys resolved first
|
|
95
94
|
previousCID: null
|
|
96
95
|
};
|
|
97
96
|
}
|
|
@@ -103,7 +102,7 @@ var classify = (jwsToken) => {
|
|
|
103
102
|
referencedDID: revocationDID,
|
|
104
103
|
signerDID: null,
|
|
105
104
|
priority: 1,
|
|
106
|
-
//
|
|
105
|
+
// needs identity keys to verify
|
|
107
106
|
previousCID: null
|
|
108
107
|
};
|
|
109
108
|
}
|
|
@@ -231,7 +230,13 @@ var ingestIdentityOp = async (jwsToken, store, logEnabled) => {
|
|
|
231
230
|
const opType = payload["type"];
|
|
232
231
|
const isGenesis = opType === "create";
|
|
233
232
|
if (isGenesis) {
|
|
234
|
-
|
|
233
|
+
let identity;
|
|
234
|
+
try {
|
|
235
|
+
identity = await verifyIdentityChain({ didPrefix: "did:dfos", log: [jwsToken] });
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const message = err instanceof Error ? err.message : "verification failed";
|
|
238
|
+
return { cid, status: "rejected", error: message };
|
|
239
|
+
}
|
|
235
240
|
const createdAt = payload["createdAt"];
|
|
236
241
|
const chain2 = {
|
|
237
242
|
did: identity.did,
|
|
@@ -252,15 +257,27 @@ var ingestIdentityOp = async (jwsToken, store, logEnabled) => {
|
|
|
252
257
|
if (hashIdx < 0) return { cid, status: "rejected", error: "non-genesis kid must be a DID URL" };
|
|
253
258
|
const did = kid.substring(0, hashIdx);
|
|
254
259
|
const chain = await store.getIdentityChain(did);
|
|
255
|
-
if (!chain)
|
|
260
|
+
if (!chain)
|
|
261
|
+
return {
|
|
262
|
+
cid,
|
|
263
|
+
status: "rejected",
|
|
264
|
+
error: `unknown identity: ${did}`,
|
|
265
|
+
dependencyMissing: true
|
|
266
|
+
};
|
|
256
267
|
const previousCID = typeof payload["previousOperationCID"] === "string" ? payload["previousOperationCID"] : null;
|
|
257
268
|
if (previousCID === chain.headCID) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
let extResult2;
|
|
270
|
+
try {
|
|
271
|
+
extResult2 = await verifyIdentityExtensionFromTrustedState({
|
|
272
|
+
currentState: chain.state,
|
|
273
|
+
headCID: chain.headCID,
|
|
274
|
+
lastCreatedAt: chain.lastCreatedAt,
|
|
275
|
+
newOp: jwsToken
|
|
276
|
+
});
|
|
277
|
+
} catch (err) {
|
|
278
|
+
const message = err instanceof Error ? err.message : "verification failed";
|
|
279
|
+
return { cid, status: "rejected", error: message };
|
|
280
|
+
}
|
|
264
281
|
const updated2 = {
|
|
265
282
|
did: chain.did,
|
|
266
283
|
log: [...chain.log, jwsToken],
|
|
@@ -276,18 +293,34 @@ var ingestIdentityOp = async (jwsToken, store, logEnabled) => {
|
|
|
276
293
|
return { cid, status: "new", kind: "identity-op", chainId: did };
|
|
277
294
|
}
|
|
278
295
|
if (!previousCID || !chainLogContainsCID(chain.log, previousCID)) {
|
|
279
|
-
return {
|
|
296
|
+
return {
|
|
297
|
+
cid,
|
|
298
|
+
status: "rejected",
|
|
299
|
+
error: "unknown previous operation in identity chain",
|
|
300
|
+
dependencyMissing: true
|
|
301
|
+
};
|
|
280
302
|
}
|
|
281
303
|
const forkState = await store.getIdentityStateAtCID(did, previousCID);
|
|
282
304
|
if (!forkState) {
|
|
283
|
-
return {
|
|
305
|
+
return {
|
|
306
|
+
cid,
|
|
307
|
+
status: "rejected",
|
|
308
|
+
error: `${FORK_POINT_STATE_ERROR_PREFIX}${previousCID}`,
|
|
309
|
+
dependencyMissing: true
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
let extResult;
|
|
313
|
+
try {
|
|
314
|
+
extResult = await verifyIdentityExtensionFromTrustedState({
|
|
315
|
+
currentState: forkState.state,
|
|
316
|
+
headCID: previousCID,
|
|
317
|
+
lastCreatedAt: forkState.lastCreatedAt,
|
|
318
|
+
newOp: jwsToken
|
|
319
|
+
});
|
|
320
|
+
} catch (err) {
|
|
321
|
+
const message = err instanceof Error ? err.message : "verification failed";
|
|
322
|
+
return { cid, status: "rejected", error: message };
|
|
284
323
|
}
|
|
285
|
-
const extResult = await verifyIdentityExtensionFromTrustedState({
|
|
286
|
-
currentState: forkState.state,
|
|
287
|
-
headCID: previousCID,
|
|
288
|
-
lastCreatedAt: forkState.lastCreatedAt,
|
|
289
|
-
newOp: jwsToken
|
|
290
|
-
});
|
|
291
324
|
const updatedLog = [...chain.log, jwsToken];
|
|
292
325
|
const head = selectDeterministicHead(updatedLog);
|
|
293
326
|
let headState = chain.state;
|
|
@@ -345,15 +378,28 @@ var ingestContentOp = async (jwsToken, store, logEnabled) => {
|
|
|
345
378
|
}
|
|
346
379
|
const resolveKey = createKeyResolver(store);
|
|
347
380
|
const resolveIdentity = createHistoricalIdentityResolver(store);
|
|
381
|
+
const isRevoked = (issuerDID, credentialCID) => store.isCredentialRevoked(issuerDID, credentialCID);
|
|
348
382
|
const opType = payload["type"];
|
|
349
383
|
const isGenesis = opType === "create";
|
|
350
384
|
if (isGenesis) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
385
|
+
let content;
|
|
386
|
+
try {
|
|
387
|
+
content = await verifyContentChain({
|
|
388
|
+
log: [jwsToken],
|
|
389
|
+
resolveKey,
|
|
390
|
+
enforceAuthorization: true,
|
|
391
|
+
resolveIdentity,
|
|
392
|
+
isRevoked
|
|
393
|
+
});
|
|
394
|
+
} catch (err) {
|
|
395
|
+
const message = err instanceof Error ? err.message : "verification failed";
|
|
396
|
+
return {
|
|
397
|
+
cid,
|
|
398
|
+
status: "rejected",
|
|
399
|
+
error: message,
|
|
400
|
+
dependencyMissing: isKeyResolutionFailure(message)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
357
403
|
const createdAt = payload["createdAt"];
|
|
358
404
|
const chain2 = {
|
|
359
405
|
contentId: content.contentId,
|
|
@@ -375,26 +421,48 @@ var ingestContentOp = async (jwsToken, store, logEnabled) => {
|
|
|
375
421
|
}
|
|
376
422
|
const prevOp = await store.getOperation(previousCID);
|
|
377
423
|
if (!prevOp)
|
|
378
|
-
return {
|
|
424
|
+
return {
|
|
425
|
+
cid,
|
|
426
|
+
status: "rejected",
|
|
427
|
+
error: `unknown previous operation: ${previousCID}`,
|
|
428
|
+
dependencyMissing: true
|
|
429
|
+
};
|
|
379
430
|
if (prevOp.chainType !== "content") {
|
|
380
431
|
return { cid, status: "rejected", error: "previousOperationCID is not a content operation" };
|
|
381
432
|
}
|
|
382
433
|
const chain = await store.getContentChain(prevOp.chainId);
|
|
383
434
|
if (!chain)
|
|
384
|
-
return {
|
|
435
|
+
return {
|
|
436
|
+
cid,
|
|
437
|
+
status: "rejected",
|
|
438
|
+
error: `content chain not found: ${prevOp.chainId}`,
|
|
439
|
+
dependencyMissing: true
|
|
440
|
+
};
|
|
385
441
|
const creatorIdentity = await store.getIdentityChain(chain.state.creatorDID);
|
|
386
442
|
if (creatorIdentity?.state.isDeleted) {
|
|
387
443
|
return { cid, status: "rejected", error: "content creator identity is deleted" };
|
|
388
444
|
}
|
|
389
445
|
if (chain.state.headCID === previousCID) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
446
|
+
let extResult2;
|
|
447
|
+
try {
|
|
448
|
+
extResult2 = await verifyContentExtensionFromTrustedState({
|
|
449
|
+
currentState: chain.state,
|
|
450
|
+
lastCreatedAt: chain.lastCreatedAt,
|
|
451
|
+
newOp: jwsToken,
|
|
452
|
+
resolveKey,
|
|
453
|
+
enforceAuthorization: true,
|
|
454
|
+
resolveIdentity,
|
|
455
|
+
isRevoked
|
|
456
|
+
});
|
|
457
|
+
} catch (err) {
|
|
458
|
+
const message = err instanceof Error ? err.message : "verification failed";
|
|
459
|
+
return {
|
|
460
|
+
cid,
|
|
461
|
+
status: "rejected",
|
|
462
|
+
error: message,
|
|
463
|
+
dependencyMissing: isKeyResolutionFailure(message)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
398
466
|
const updated2 = {
|
|
399
467
|
contentId: chain.contentId,
|
|
400
468
|
genesisCID: chain.genesisCID,
|
|
@@ -410,20 +478,42 @@ var ingestContentOp = async (jwsToken, store, logEnabled) => {
|
|
|
410
478
|
return { cid, status: "new", kind: "content-op", chainId: chain.contentId };
|
|
411
479
|
}
|
|
412
480
|
if (!chainLogContainsCID(chain.log, previousCID)) {
|
|
413
|
-
return {
|
|
481
|
+
return {
|
|
482
|
+
cid,
|
|
483
|
+
status: "rejected",
|
|
484
|
+
error: "unknown previous operation in content chain",
|
|
485
|
+
dependencyMissing: true
|
|
486
|
+
};
|
|
414
487
|
}
|
|
415
488
|
const forkState = await store.getContentStateAtCID(chain.contentId, previousCID);
|
|
416
489
|
if (!forkState) {
|
|
417
|
-
return {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
490
|
+
return {
|
|
491
|
+
cid,
|
|
492
|
+
status: "rejected",
|
|
493
|
+
error: `${FORK_POINT_STATE_ERROR_PREFIX}${previousCID}`,
|
|
494
|
+
dependencyMissing: true
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
let extResult;
|
|
498
|
+
try {
|
|
499
|
+
extResult = await verifyContentExtensionFromTrustedState({
|
|
500
|
+
currentState: forkState.state,
|
|
501
|
+
lastCreatedAt: forkState.lastCreatedAt,
|
|
502
|
+
newOp: jwsToken,
|
|
503
|
+
resolveKey,
|
|
504
|
+
enforceAuthorization: true,
|
|
505
|
+
resolveIdentity,
|
|
506
|
+
isRevoked
|
|
507
|
+
});
|
|
508
|
+
} catch (err) {
|
|
509
|
+
const message = err instanceof Error ? err.message : "verification failed";
|
|
510
|
+
return {
|
|
511
|
+
cid,
|
|
512
|
+
status: "rejected",
|
|
513
|
+
error: message,
|
|
514
|
+
dependencyMissing: isKeyResolutionFailure(message)
|
|
515
|
+
};
|
|
516
|
+
}
|
|
427
517
|
const updatedLog = [...chain.log, jwsToken];
|
|
428
518
|
const head = selectDeterministicHead(updatedLog);
|
|
429
519
|
let headState = chain.state;
|
|
@@ -446,36 +536,6 @@ var ingestContentOp = async (jwsToken, store, logEnabled) => {
|
|
|
446
536
|
}
|
|
447
537
|
return { cid, status: "new", kind: "content-op", chainId: chain.contentId };
|
|
448
538
|
};
|
|
449
|
-
var ingestBeacon = async (jwsToken, store, logEnabled) => {
|
|
450
|
-
const resolveKey = createKeyResolver(store);
|
|
451
|
-
let verified;
|
|
452
|
-
try {
|
|
453
|
-
verified = await verifyBeacon({ jwsToken, resolveKey });
|
|
454
|
-
} catch (err) {
|
|
455
|
-
const message = err instanceof Error ? err.message : "verification failed";
|
|
456
|
-
return { cid: "", status: "rejected", error: message };
|
|
457
|
-
}
|
|
458
|
-
const did = verified.did;
|
|
459
|
-
const cid = verified.beaconCID;
|
|
460
|
-
const identity = await store.getIdentityChain(did);
|
|
461
|
-
if (identity?.state.isDeleted) {
|
|
462
|
-
return { cid, status: "rejected", error: "identity is deleted" };
|
|
463
|
-
}
|
|
464
|
-
const existing = await store.getBeacon(did);
|
|
465
|
-
if (existing) {
|
|
466
|
-
const existingTime = new Date(existing.state.createdAt).getTime();
|
|
467
|
-
const newTime = new Date(verified.createdAt).getTime();
|
|
468
|
-
if (newTime <= existingTime) {
|
|
469
|
-
return { cid, status: "duplicate", kind: "beacon", chainId: did };
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
await store.putBeacon({ did, jwsToken, beaconCID: cid, state: verified });
|
|
473
|
-
await store.putOperation({ cid, jwsToken, chainType: "beacon", chainId: did });
|
|
474
|
-
if (logEnabled) {
|
|
475
|
-
await store.appendToLog({ cid, jwsToken, kind: "beacon", chainId: did });
|
|
476
|
-
}
|
|
477
|
-
return { cid, status: "new", kind: "beacon", chainId: did };
|
|
478
|
-
};
|
|
479
539
|
var ingestCountersign = async (jwsToken, store, logEnabled) => {
|
|
480
540
|
const resolveKey = createKeyResolver(store);
|
|
481
541
|
let verified;
|
|
@@ -483,7 +543,12 @@ var ingestCountersign = async (jwsToken, store, logEnabled) => {
|
|
|
483
543
|
verified = await verifyCountersignature({ jwsToken, resolveKey });
|
|
484
544
|
} catch (err) {
|
|
485
545
|
const message = err instanceof Error ? err.message : "verification failed";
|
|
486
|
-
return {
|
|
546
|
+
return {
|
|
547
|
+
cid: await computeOpCID(jwsToken),
|
|
548
|
+
status: "rejected",
|
|
549
|
+
error: message,
|
|
550
|
+
dependencyMissing: isKeyResolutionFailure(message)
|
|
551
|
+
};
|
|
487
552
|
}
|
|
488
553
|
const cid = verified.countersignCID;
|
|
489
554
|
const { witnessDID, targetCID } = verified;
|
|
@@ -500,7 +565,12 @@ var ingestCountersign = async (jwsToken, store, logEnabled) => {
|
|
|
500
565
|
}
|
|
501
566
|
const targetOp = await store.getOperation(targetCID);
|
|
502
567
|
if (!targetOp) {
|
|
503
|
-
return {
|
|
568
|
+
return {
|
|
569
|
+
cid,
|
|
570
|
+
status: "rejected",
|
|
571
|
+
error: `unknown target operation: ${targetCID}`,
|
|
572
|
+
dependencyMissing: true
|
|
573
|
+
};
|
|
504
574
|
}
|
|
505
575
|
let targetAuthorDID = null;
|
|
506
576
|
if (targetOp.chainType === "identity") {
|
|
@@ -542,7 +612,12 @@ var ingestArtifact = async (jwsToken, store, logEnabled) => {
|
|
|
542
612
|
verified = await verifyArtifact({ jwsToken, resolveKey });
|
|
543
613
|
} catch (err) {
|
|
544
614
|
const message = err instanceof Error ? err.message : "verification failed";
|
|
545
|
-
return {
|
|
615
|
+
return {
|
|
616
|
+
cid: await computeOpCID(jwsToken),
|
|
617
|
+
status: "rejected",
|
|
618
|
+
error: message,
|
|
619
|
+
dependencyMissing: isKeyResolutionFailure(message)
|
|
620
|
+
};
|
|
546
621
|
}
|
|
547
622
|
const cid = verified.artifactCID;
|
|
548
623
|
const did = verified.payload.did;
|
|
@@ -574,7 +649,12 @@ var ingestRevocation = async (jwsToken, store, logEnabled) => {
|
|
|
574
649
|
verified = await verifyRevocation({ jwsToken, resolveKey });
|
|
575
650
|
} catch (err) {
|
|
576
651
|
const message = err instanceof Error ? err.message : "verification failed";
|
|
577
|
-
return {
|
|
652
|
+
return {
|
|
653
|
+
cid: await computeOpCID(jwsToken),
|
|
654
|
+
status: "rejected",
|
|
655
|
+
error: message,
|
|
656
|
+
dependencyMissing: isKeyResolutionFailure(message)
|
|
657
|
+
};
|
|
578
658
|
}
|
|
579
659
|
const cid = verified.revocationCID;
|
|
580
660
|
const did = verified.did;
|
|
@@ -613,11 +693,16 @@ var ingestPublicCredential = async (jwsToken, store, logEnabled) => {
|
|
|
613
693
|
verified = await verifyDFOSCredential(jwsToken, { resolveIdentity });
|
|
614
694
|
} catch (err) {
|
|
615
695
|
const message = err instanceof Error ? err.message : "verification failed";
|
|
616
|
-
return {
|
|
696
|
+
return {
|
|
697
|
+
cid: await computeOpCID(jwsToken),
|
|
698
|
+
status: "rejected",
|
|
699
|
+
error: message,
|
|
700
|
+
dependencyMissing: isCredentialDependencyFailure(message)
|
|
701
|
+
};
|
|
617
702
|
}
|
|
618
703
|
const cid = verified.credentialCID;
|
|
619
704
|
if (verified.aud !== "*") {
|
|
620
|
-
return { cid
|
|
705
|
+
return { cid, status: "rejected", error: "not a public credential" };
|
|
621
706
|
}
|
|
622
707
|
const existing = await store.getOperation(cid);
|
|
623
708
|
if (existing) {
|
|
@@ -726,9 +811,10 @@ var selectDeterministicHead = (log) => {
|
|
|
726
811
|
if (previousCID) hasChild.add(previousCID);
|
|
727
812
|
}
|
|
728
813
|
const tips = ops.filter((op) => !hasChild.has(op.cid));
|
|
814
|
+
const cmp = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
729
815
|
tips.sort((a, b) => {
|
|
730
|
-
if (a.createdAt !== b.createdAt) return b.createdAt
|
|
731
|
-
return b.cid
|
|
816
|
+
if (a.createdAt !== b.createdAt) return cmp(b.createdAt, a.createdAt);
|
|
817
|
+
return cmp(b.cid, a.cid);
|
|
732
818
|
});
|
|
733
819
|
return tips[0] ?? { cid: "", createdAt: "" };
|
|
734
820
|
};
|
|
@@ -747,9 +833,6 @@ var ingestOperations = async (tokens, store, options) => {
|
|
|
747
833
|
case "content-op":
|
|
748
834
|
result = await ingestContentOp(op.jwsToken, store, logEnabled);
|
|
749
835
|
break;
|
|
750
|
-
case "beacon":
|
|
751
|
-
result = await ingestBeacon(op.jwsToken, store, logEnabled);
|
|
752
|
-
break;
|
|
753
836
|
case "countersign":
|
|
754
837
|
result = await ingestCountersign(op.jwsToken, store, logEnabled);
|
|
755
838
|
break;
|
|
@@ -763,14 +846,18 @@ var ingestOperations = async (tokens, store, options) => {
|
|
|
763
846
|
result = await ingestPublicCredential(op.jwsToken, store, logEnabled);
|
|
764
847
|
break;
|
|
765
848
|
default:
|
|
766
|
-
result = {
|
|
849
|
+
result = {
|
|
850
|
+
cid: await computeOpCID(op.jwsToken),
|
|
851
|
+
status: "rejected",
|
|
852
|
+
error: "unrecognized operation type"
|
|
853
|
+
};
|
|
767
854
|
}
|
|
768
855
|
indexedResults.push({ index: op.originalIndex, result });
|
|
769
856
|
} catch (err) {
|
|
770
857
|
const message = err instanceof Error ? err.message : "unexpected error";
|
|
771
858
|
indexedResults.push({
|
|
772
859
|
index: op.originalIndex,
|
|
773
|
-
result: { cid:
|
|
860
|
+
result: { cid: await computeOpCID(op.jwsToken), status: "rejected", error: message }
|
|
774
861
|
});
|
|
775
862
|
}
|
|
776
863
|
}
|
|
@@ -781,8 +868,28 @@ var ingestOperations = async (tokens, store, options) => {
|
|
|
781
868
|
var bootstrapRelayIdentity = async (store) => {
|
|
782
869
|
const keypair = createNewEd25519Keypair();
|
|
783
870
|
const keyId = generateId("key");
|
|
784
|
-
|
|
785
|
-
|
|
871
|
+
return bootstrapWithKeyMaterial(store, {
|
|
872
|
+
privateKey: keypair.privateKey,
|
|
873
|
+
publicKey: keypair.publicKey,
|
|
874
|
+
keyId
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
var bootstrapRelayIdentityFromKey = async (store, params) => {
|
|
878
|
+
const { publicKey } = importEd25519Keypair(params.privateKey);
|
|
879
|
+
return bootstrapWithKeyMaterial(store, {
|
|
880
|
+
privateKey: params.privateKey,
|
|
881
|
+
publicKey,
|
|
882
|
+
keyId: params.keyId,
|
|
883
|
+
...params.name !== void 0 ? { name: params.name } : {},
|
|
884
|
+
...params.createdAt !== void 0 ? { createdAt: params.createdAt } : {}
|
|
885
|
+
});
|
|
886
|
+
};
|
|
887
|
+
var bootstrapWithKeyMaterial = async (store, params) => {
|
|
888
|
+
const { privateKey, publicKey, keyId } = params;
|
|
889
|
+
const name = params.name ?? "DFOS Relay";
|
|
890
|
+
const createdAt = params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
891
|
+
const multibase = encodeEd25519Multikey(publicKey);
|
|
892
|
+
const signer = async (msg) => signPayloadEd25519(msg, privateKey);
|
|
786
893
|
const key = { id: keyId, type: "Multikey", publicKeyMultibase: multibase };
|
|
787
894
|
const identityOp = {
|
|
788
895
|
version: 1,
|
|
@@ -790,7 +897,7 @@ var bootstrapRelayIdentity = async (store) => {
|
|
|
790
897
|
authKeys: [key],
|
|
791
898
|
assertKeys: [key],
|
|
792
899
|
controllerKeys: [key],
|
|
793
|
-
createdAt
|
|
900
|
+
createdAt
|
|
794
901
|
};
|
|
795
902
|
const { jwsToken: identityJws } = await signIdentityOperation({
|
|
796
903
|
operation: identityOp,
|
|
@@ -808,9 +915,9 @@ var bootstrapRelayIdentity = async (store) => {
|
|
|
808
915
|
did,
|
|
809
916
|
content: {
|
|
810
917
|
$schema: "https://schemas.dfos.com/profile/v1",
|
|
811
|
-
name
|
|
918
|
+
name
|
|
812
919
|
},
|
|
813
|
-
createdAt
|
|
920
|
+
createdAt
|
|
814
921
|
};
|
|
815
922
|
const kid = `${did}#${keyId}`;
|
|
816
923
|
const { jwsToken: profileArtifactJws } = await signArtifact({
|
|
@@ -865,11 +972,16 @@ var createHttpPeerClient = () => {
|
|
|
865
972
|
},
|
|
866
973
|
async submitOperations(peerUrl, operations) {
|
|
867
974
|
try {
|
|
868
|
-
await fetch(new URL("/operations", peerUrl).toString(), {
|
|
975
|
+
const res = await fetch(new URL("/operations", peerUrl).toString(), {
|
|
869
976
|
method: "POST",
|
|
870
977
|
headers: { "Content-Type": "application/json" },
|
|
871
978
|
body: JSON.stringify({ operations })
|
|
872
979
|
});
|
|
980
|
+
if (!res.ok) {
|
|
981
|
+
console.warn(
|
|
982
|
+
`gossip submitOperations to ${peerUrl} returned ${res.status} (${operations.length} ops dropped)`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
873
985
|
} catch {
|
|
874
986
|
}
|
|
875
987
|
}
|
|
@@ -878,7 +990,7 @@ var createHttpPeerClient = () => {
|
|
|
878
990
|
|
|
879
991
|
// src/relay.ts
|
|
880
992
|
import { createRequire } from "module";
|
|
881
|
-
import { dagCborCanonicalEncode as
|
|
993
|
+
import { dagCborCanonicalEncode as dagCborCanonicalEncode2, decodeJwsUnsafe as decodeJwsUnsafe3 } from "@metalabel/dfos-protocol/crypto";
|
|
882
994
|
import { Hono } from "hono";
|
|
883
995
|
import { z } from "zod";
|
|
884
996
|
|
|
@@ -890,7 +1002,8 @@ import {
|
|
|
890
1002
|
verifyDFOSCredential as verifyDFOSCredential2
|
|
891
1003
|
} from "@metalabel/dfos-protocol/credentials";
|
|
892
1004
|
import { decodeJwsUnsafe as decodeJwsUnsafe2 } from "@metalabel/dfos-protocol/crypto";
|
|
893
|
-
var
|
|
1005
|
+
var DEFAULT_MAX_AUTH_TOKEN_TTL_SECONDS = 86400;
|
|
1006
|
+
var authenticateRequest = async (authHeader, relayDID, store, maxAuthTokenTTLSeconds = DEFAULT_MAX_AUTH_TOKEN_TTL_SECONDS) => {
|
|
894
1007
|
if (!authHeader) return null;
|
|
895
1008
|
if (!authHeader.startsWith("Bearer ")) return null;
|
|
896
1009
|
const token = authHeader.substring(7);
|
|
@@ -907,7 +1020,11 @@ var authenticateRequest = async (authHeader, relayDID, store) => {
|
|
|
907
1020
|
return null;
|
|
908
1021
|
}
|
|
909
1022
|
try {
|
|
910
|
-
|
|
1023
|
+
const verified = verifyAuthToken({ token, publicKey, audience: relayDID });
|
|
1024
|
+
if (maxAuthTokenTTLSeconds > 0 && verified.exp - verified.iat > maxAuthTokenTTLSeconds) {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
return verified;
|
|
911
1028
|
} catch {
|
|
912
1029
|
return null;
|
|
913
1030
|
}
|
|
@@ -986,22 +1103,7 @@ var verifyContentAccess = async (options) => {
|
|
|
986
1103
|
};
|
|
987
1104
|
|
|
988
1105
|
// src/sequencer.ts
|
|
989
|
-
|
|
990
|
-
var isDependencyFailure = (error) => {
|
|
991
|
-
const patterns = [
|
|
992
|
-
"unknown previous operation",
|
|
993
|
-
"unknown identity:",
|
|
994
|
-
"content chain not found:",
|
|
995
|
-
"failed to compute state at fork point:"
|
|
996
|
-
];
|
|
997
|
-
return patterns.some((p) => error.includes(p));
|
|
998
|
-
};
|
|
999
|
-
var computeOpCID = async (jwsToken) => {
|
|
1000
|
-
const decoded = decodeJwsUnsafe3(jwsToken);
|
|
1001
|
-
if (!decoded) return void 0;
|
|
1002
|
-
const encoded = await dagCborCanonicalEncode2(decoded.payload);
|
|
1003
|
-
return encoded.cid.toString();
|
|
1004
|
-
};
|
|
1106
|
+
var isDependencyFailure = (res) => res.dependencyMissing === true;
|
|
1005
1107
|
var sequenceOps = async (store) => {
|
|
1006
1108
|
const newOps = [];
|
|
1007
1109
|
const result = { sequenced: 0, rejected: 0, pending: 0 };
|
|
@@ -1022,7 +1124,7 @@ var sequenceOps = async (store) => {
|
|
|
1022
1124
|
} else if (res.status === "duplicate") {
|
|
1023
1125
|
sequencedCIDs.push(res.cid);
|
|
1024
1126
|
progress = true;
|
|
1025
|
-
} else if (res.status === "rejected" && !isDependencyFailure(res
|
|
1127
|
+
} else if (res.status === "rejected" && !isDependencyFailure(res)) {
|
|
1026
1128
|
await store.markOpRejected(res.cid, res.error ?? "unknown");
|
|
1027
1129
|
result.rejected++;
|
|
1028
1130
|
progress = true;
|
|
@@ -1041,13 +1143,37 @@ var sequenceOps = async (store) => {
|
|
|
1041
1143
|
// src/relay.ts
|
|
1042
1144
|
var require2 = createRequire(import.meta.url);
|
|
1043
1145
|
var { version: RELAY_VERSION } = require2("../package.json");
|
|
1146
|
+
var MAX_OPERATIONS_PER_BATCH = 100;
|
|
1044
1147
|
var IngestBody = z.object({
|
|
1045
|
-
operations: z.array(z.string()).min(1).max(
|
|
1148
|
+
operations: z.array(z.string()).min(1).max(MAX_OPERATIONS_PER_BATCH)
|
|
1046
1149
|
});
|
|
1150
|
+
var MAX_GOSSIP_BATCH = MAX_OPERATIONS_PER_BATCH;
|
|
1151
|
+
var chunkOps = (items, size) => {
|
|
1152
|
+
const chunks = [];
|
|
1153
|
+
for (let start = 0; start < items.length; start += size) {
|
|
1154
|
+
chunks.push(items.slice(start, start + size));
|
|
1155
|
+
}
|
|
1156
|
+
return chunks;
|
|
1157
|
+
};
|
|
1158
|
+
var MAX_BODY_BYTES = 16 << 20;
|
|
1159
|
+
var exceedsBodyCap = (contentLength) => {
|
|
1160
|
+
if (!contentLength) return false;
|
|
1161
|
+
const n = Number(contentLength);
|
|
1162
|
+
return Number.isFinite(n) && n > MAX_BODY_BYTES;
|
|
1163
|
+
};
|
|
1164
|
+
var parseLimit = (raw, defaultLimit, maxLimit) => {
|
|
1165
|
+
if (raw === void 0 || raw === "") return defaultLimit;
|
|
1166
|
+
if (!/^-?\d+$/.test(raw)) return defaultLimit;
|
|
1167
|
+
const n = Number(raw);
|
|
1168
|
+
if (!Number.isSafeInteger(n) || n < 1) return defaultLimit;
|
|
1169
|
+
if (n > maxLimit) return maxLimit;
|
|
1170
|
+
return n;
|
|
1171
|
+
};
|
|
1047
1172
|
var createRelay = async (options) => {
|
|
1048
1173
|
const { store } = options;
|
|
1049
1174
|
const contentEnabled = options.content !== false;
|
|
1050
1175
|
const logEnabled = options.log !== false;
|
|
1176
|
+
const maxAuthTokenTTLSeconds = options.maxAuthTokenTTLSeconds ?? DEFAULT_MAX_AUTH_TOKEN_TTL_SECONDS;
|
|
1051
1177
|
const peers = options.peers ?? [];
|
|
1052
1178
|
const peerClient = options.peerClient;
|
|
1053
1179
|
const gossipPeers = peers.filter((p) => p.gossip !== false);
|
|
@@ -1059,8 +1185,10 @@ var createRelay = async (options) => {
|
|
|
1059
1185
|
const gossip = (ops) => {
|
|
1060
1186
|
if (ops.length === 0 || gossipPeers.length === 0 || !peerClient) return;
|
|
1061
1187
|
for (const peer of gossipPeers) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1188
|
+
for (const chunk of chunkOps(ops, MAX_GOSSIP_BATCH)) {
|
|
1189
|
+
peerClient.submitOperations(peer.url, chunk).catch(() => {
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1064
1192
|
}
|
|
1065
1193
|
};
|
|
1066
1194
|
const ingestWithGossip = async (tokens) => {
|
|
@@ -1086,6 +1214,20 @@ var createRelay = async (options) => {
|
|
|
1086
1214
|
return results;
|
|
1087
1215
|
};
|
|
1088
1216
|
const app = new Hono();
|
|
1217
|
+
app.use("*", async (c, next) => {
|
|
1218
|
+
if (c.req.method === "OPTIONS") {
|
|
1219
|
+
return c.body(null, 204, {
|
|
1220
|
+
"Access-Control-Allow-Origin": "*",
|
|
1221
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS",
|
|
1222
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
await next();
|
|
1226
|
+
c.res.headers.set("Access-Control-Allow-Origin", "*");
|
|
1227
|
+
c.res.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
|
|
1228
|
+
c.res.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
1229
|
+
return;
|
|
1230
|
+
});
|
|
1089
1231
|
app.get("/.well-known/dfos-relay", (c) => {
|
|
1090
1232
|
return c.json({
|
|
1091
1233
|
did: relayDID,
|
|
@@ -1101,6 +1243,9 @@ var createRelay = async (options) => {
|
|
|
1101
1243
|
});
|
|
1102
1244
|
});
|
|
1103
1245
|
app.post("/operations", async (c) => {
|
|
1246
|
+
if (exceedsBodyCap(c.req.header("content-length"))) {
|
|
1247
|
+
return c.json({ error: "request body too large" }, 413);
|
|
1248
|
+
}
|
|
1104
1249
|
let body;
|
|
1105
1250
|
try {
|
|
1106
1251
|
body = await c.req.json();
|
|
@@ -1130,9 +1275,9 @@ var createRelay = async (options) => {
|
|
|
1130
1275
|
const chain = await store.getIdentityChain(did);
|
|
1131
1276
|
if (!chain) return c.json({ error: "not found" }, 404);
|
|
1132
1277
|
const after = c.req.query("after");
|
|
1133
|
-
const limit =
|
|
1278
|
+
const limit = parseLimit(c.req.query("limit"), 100, 1e3);
|
|
1134
1279
|
const entries = chain.log.map((jws) => {
|
|
1135
|
-
const decoded =
|
|
1280
|
+
const decoded = decodeJwsUnsafe3(jws);
|
|
1136
1281
|
return { cid: decoded?.header.cid || "", jwsToken: jws };
|
|
1137
1282
|
});
|
|
1138
1283
|
let startIdx = 0;
|
|
@@ -1176,9 +1321,9 @@ var createRelay = async (options) => {
|
|
|
1176
1321
|
const chain = await store.getContentChain(contentId);
|
|
1177
1322
|
if (!chain) return c.json({ error: "not found" }, 404);
|
|
1178
1323
|
const after = c.req.query("after");
|
|
1179
|
-
const limit =
|
|
1324
|
+
const limit = parseLimit(c.req.query("limit"), 100, 1e3);
|
|
1180
1325
|
const entries = chain.log.map((jws) => {
|
|
1181
|
-
const decoded =
|
|
1326
|
+
const decoded = decodeJwsUnsafe3(jws);
|
|
1182
1327
|
return { cid: decoded?.header.cid || "", jwsToken: jws };
|
|
1183
1328
|
});
|
|
1184
1329
|
let startIdx = 0;
|
|
@@ -1197,7 +1342,12 @@ var createRelay = async (options) => {
|
|
|
1197
1342
|
if (!chain) return c.json({ error: "not found" }, 404);
|
|
1198
1343
|
const publicAccess = await hasPublicStandingAuth(contentId, "read", store);
|
|
1199
1344
|
if (!publicAccess) {
|
|
1200
|
-
const auth = await authenticateRequest(
|
|
1345
|
+
const auth = await authenticateRequest(
|
|
1346
|
+
c.req.header("authorization"),
|
|
1347
|
+
relayDID,
|
|
1348
|
+
store,
|
|
1349
|
+
maxAuthTokenTTLSeconds
|
|
1350
|
+
);
|
|
1201
1351
|
if (!auth) return c.json({ error: "authentication required" }, 401);
|
|
1202
1352
|
const accessError = await verifyReadAccess(
|
|
1203
1353
|
auth,
|
|
@@ -1209,7 +1359,7 @@ var createRelay = async (options) => {
|
|
|
1209
1359
|
if (accessError) return accessError;
|
|
1210
1360
|
}
|
|
1211
1361
|
const after = c.req.query("after");
|
|
1212
|
-
const limit =
|
|
1362
|
+
const limit = parseLimit(c.req.query("limit"), 100, 1e3);
|
|
1213
1363
|
const result = await store.getDocuments(contentId, {
|
|
1214
1364
|
...after ? { after } : {},
|
|
1215
1365
|
limit
|
|
@@ -1266,22 +1416,10 @@ var createRelay = async (options) => {
|
|
|
1266
1416
|
const countersigs = await store.getCountersignatures(cid);
|
|
1267
1417
|
return c.json({ operationCID: cid, countersignatures: countersigs });
|
|
1268
1418
|
});
|
|
1269
|
-
app.get("/beacons/:did{.+}", async (c) => {
|
|
1270
|
-
const did = c.req.param("did");
|
|
1271
|
-
const beacon = await store.getBeacon(did);
|
|
1272
|
-
if (!beacon) return c.json({ error: "not found" }, 404);
|
|
1273
|
-
return c.json({
|
|
1274
|
-
did: beacon.did,
|
|
1275
|
-
jwsToken: beacon.jwsToken,
|
|
1276
|
-
beaconCID: beacon.beaconCID,
|
|
1277
|
-
manifestContentId: beacon.state.manifestContentId,
|
|
1278
|
-
createdAt: beacon.state.createdAt
|
|
1279
|
-
});
|
|
1280
|
-
});
|
|
1281
1419
|
app.get("/log", async (c) => {
|
|
1282
1420
|
if (!logEnabled) return c.json({ error: "global log not available" }, 501);
|
|
1283
1421
|
const afterParam = c.req.query("after");
|
|
1284
|
-
const limit =
|
|
1422
|
+
const limit = parseLimit(c.req.query("limit"), 100, 1e3);
|
|
1285
1423
|
const result = await store.readLog(afterParam ? { after: afterParam, limit } : { limit });
|
|
1286
1424
|
return c.json(result);
|
|
1287
1425
|
});
|
|
@@ -1289,14 +1427,22 @@ var createRelay = async (options) => {
|
|
|
1289
1427
|
if (!contentEnabled) return c.json({ error: "content plane not available" }, 501);
|
|
1290
1428
|
const contentId = c.req.param("contentId");
|
|
1291
1429
|
const operationCID = c.req.param("operationCID");
|
|
1292
|
-
|
|
1430
|
+
if (exceedsBodyCap(c.req.header("content-length"))) {
|
|
1431
|
+
return c.json({ error: "request body too large" }, 413);
|
|
1432
|
+
}
|
|
1433
|
+
const auth = await authenticateRequest(
|
|
1434
|
+
c.req.header("authorization"),
|
|
1435
|
+
relayDID,
|
|
1436
|
+
store,
|
|
1437
|
+
maxAuthTokenTTLSeconds
|
|
1438
|
+
);
|
|
1293
1439
|
if (!auth) return c.json({ error: "authentication required" }, 401);
|
|
1294
1440
|
const chain = await store.getContentChain(contentId);
|
|
1295
1441
|
if (!chain) return c.json({ error: "content chain not found" }, 404);
|
|
1296
1442
|
let documentCID = null;
|
|
1297
1443
|
let operationSignerDID = null;
|
|
1298
1444
|
for (const token of chain.log) {
|
|
1299
|
-
const decoded =
|
|
1445
|
+
const decoded = decodeJwsUnsafe3(token);
|
|
1300
1446
|
if (!decoded) continue;
|
|
1301
1447
|
if (decoded.header.cid !== operationCID) continue;
|
|
1302
1448
|
const payload = decoded.payload;
|
|
@@ -1311,9 +1457,12 @@ var createRelay = async (options) => {
|
|
|
1311
1457
|
return c.json({ error: "not authorized \u2014 must be chain creator or operation signer" }, 403);
|
|
1312
1458
|
}
|
|
1313
1459
|
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
1460
|
+
if (bytes.byteLength > MAX_BODY_BYTES) {
|
|
1461
|
+
return c.json({ error: "request body too large" }, 413);
|
|
1462
|
+
}
|
|
1314
1463
|
try {
|
|
1315
1464
|
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
1316
|
-
const encoded = await
|
|
1465
|
+
const encoded = await dagCborCanonicalEncode2(parsed);
|
|
1317
1466
|
if (encoded.cid.toString() !== documentCID) {
|
|
1318
1467
|
return c.json({ error: "blob bytes do not match documentCID" }, 400);
|
|
1319
1468
|
}
|
|
@@ -1331,7 +1480,8 @@ var createRelay = async (options) => {
|
|
|
1331
1480
|
authHeader: c.req.header("authorization"),
|
|
1332
1481
|
credHeader: c.req.header("x-credential"),
|
|
1333
1482
|
relayDID,
|
|
1334
|
-
store
|
|
1483
|
+
store,
|
|
1484
|
+
maxAuthTokenTTLSeconds
|
|
1335
1485
|
});
|
|
1336
1486
|
});
|
|
1337
1487
|
app.get("/content/:contentId/blob/:ref", async (c) => {
|
|
@@ -1342,20 +1492,24 @@ var createRelay = async (options) => {
|
|
|
1342
1492
|
authHeader: c.req.header("authorization"),
|
|
1343
1493
|
credHeader: c.req.header("x-credential"),
|
|
1344
1494
|
relayDID,
|
|
1345
|
-
store
|
|
1495
|
+
store,
|
|
1496
|
+
maxAuthTokenTTLSeconds
|
|
1346
1497
|
});
|
|
1347
1498
|
});
|
|
1499
|
+
const maxOpsPerSyncCycle = 5e3;
|
|
1348
1500
|
const syncFromPeers = async () => {
|
|
1349
1501
|
if (!peerClient) return;
|
|
1350
1502
|
for (const peer of syncPeers) {
|
|
1351
1503
|
let cursor = await store.getPeerCursor(peer.url);
|
|
1352
|
-
|
|
1504
|
+
let fetched = 0;
|
|
1505
|
+
while (fetched < maxOpsPerSyncCycle) {
|
|
1353
1506
|
const page = await peerClient.getOperationLog(peer.url, {
|
|
1354
1507
|
...cursor ? { after: cursor } : {},
|
|
1355
1508
|
limit: 1e3
|
|
1356
1509
|
});
|
|
1357
1510
|
if (!page || page.entries.length === 0) break;
|
|
1358
1511
|
await ingestWithGossip(page.entries.map((e) => e.jwsToken));
|
|
1512
|
+
fetched += page.entries.length;
|
|
1359
1513
|
cursor = page.cursor ?? page.entries[page.entries.length - 1].cid;
|
|
1360
1514
|
await store.setPeerCursor(peer.url, cursor);
|
|
1361
1515
|
if (!page.cursor) break;
|
|
@@ -1369,12 +1523,12 @@ var jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
|
1369
1523
|
headers: { "content-type": "application/json" }
|
|
1370
1524
|
});
|
|
1371
1525
|
var readBlob = async (params) => {
|
|
1372
|
-
const { contentId, ref, authHeader, credHeader, relayDID, store } = params;
|
|
1526
|
+
const { contentId, ref, authHeader, credHeader, relayDID, store, maxAuthTokenTTLSeconds } = params;
|
|
1373
1527
|
const chain = await store.getContentChain(contentId);
|
|
1374
1528
|
if (!chain) return jsonResponse({ error: "content chain not found" }, 404);
|
|
1375
1529
|
const publicAccess = await hasPublicStandingAuth(contentId, "read", store);
|
|
1376
1530
|
if (!publicAccess) {
|
|
1377
|
-
const auth = await authenticateRequest(authHeader, relayDID, store);
|
|
1531
|
+
const auth = await authenticateRequest(authHeader, relayDID, store, maxAuthTokenTTLSeconds);
|
|
1378
1532
|
if (!auth) return jsonResponse({ error: "authentication required" }, 401);
|
|
1379
1533
|
const credError = await verifyReadAccess(auth, chain, contentId, credHeader, store);
|
|
1380
1534
|
if (credError) return credError;
|
|
@@ -1385,7 +1539,7 @@ var readBlob = async (params) => {
|
|
|
1385
1539
|
documentCID = chain.state.currentDocumentCID;
|
|
1386
1540
|
} else {
|
|
1387
1541
|
for (const token of chain.log) {
|
|
1388
|
-
const decoded =
|
|
1542
|
+
const decoded = decodeJwsUnsafe3(token);
|
|
1389
1543
|
if (!decoded) continue;
|
|
1390
1544
|
if (decoded.header.cid === ref) {
|
|
1391
1545
|
operationFound = true;
|
|
@@ -1421,13 +1575,12 @@ var verifyReadAccess = async (auth, chain, contentId, credHeader, store) => {
|
|
|
1421
1575
|
|
|
1422
1576
|
// src/store.ts
|
|
1423
1577
|
import { verifyContentChain as verifyContentChain2, verifyIdentityChain as verifyIdentityChain2 } from "@metalabel/dfos-protocol/chain";
|
|
1424
|
-
import { decodeJwsUnsafe as
|
|
1578
|
+
import { decodeJwsUnsafe as decodeJwsUnsafe4 } from "@metalabel/dfos-protocol/crypto";
|
|
1425
1579
|
var blobKeyString = (key) => `${key.creatorDID}::${key.documentCID}`;
|
|
1426
1580
|
var MemoryRelayStore = class {
|
|
1427
1581
|
operations = /* @__PURE__ */ new Map();
|
|
1428
1582
|
identityChains = /* @__PURE__ */ new Map();
|
|
1429
1583
|
contentChains = /* @__PURE__ */ new Map();
|
|
1430
|
-
beacons = /* @__PURE__ */ new Map();
|
|
1431
1584
|
blobs = /* @__PURE__ */ new Map();
|
|
1432
1585
|
countersignatures = /* @__PURE__ */ new Map();
|
|
1433
1586
|
operationLog = [];
|
|
@@ -1454,12 +1607,6 @@ var MemoryRelayStore = class {
|
|
|
1454
1607
|
async putContentChain(chain) {
|
|
1455
1608
|
this.contentChains.set(chain.contentId, chain);
|
|
1456
1609
|
}
|
|
1457
|
-
async getBeacon(did) {
|
|
1458
|
-
return this.beacons.get(did);
|
|
1459
|
-
}
|
|
1460
|
-
async putBeacon(beacon) {
|
|
1461
|
-
this.beacons.set(beacon.did, beacon);
|
|
1462
|
-
}
|
|
1463
1610
|
async getBlob(key) {
|
|
1464
1611
|
return this.blobs.get(blobKeyString(key));
|
|
1465
1612
|
}
|
|
@@ -1471,12 +1618,12 @@ var MemoryRelayStore = class {
|
|
|
1471
1618
|
}
|
|
1472
1619
|
async addCountersignature(operationCID, jwsToken) {
|
|
1473
1620
|
const existing = this.countersignatures.get(operationCID) ?? [];
|
|
1474
|
-
const decoded =
|
|
1621
|
+
const decoded = decodeJwsUnsafe4(jwsToken);
|
|
1475
1622
|
if (decoded) {
|
|
1476
1623
|
const kid = decoded.header.kid;
|
|
1477
1624
|
const witnessDID = kid.includes("#") ? kid.split("#")[0] : kid;
|
|
1478
1625
|
for (const cs of existing) {
|
|
1479
|
-
const d =
|
|
1626
|
+
const d = decodeJwsUnsafe4(cs);
|
|
1480
1627
|
if (!d) continue;
|
|
1481
1628
|
const existingKid = d.header.kid;
|
|
1482
1629
|
const existingDID = existingKid.includes("#") ? existingKid.split("#")[0] : existingKid;
|
|
@@ -1531,7 +1678,7 @@ var MemoryRelayStore = class {
|
|
|
1531
1678
|
if (!chain) return { documents: [], cursor: null };
|
|
1532
1679
|
const entries = [];
|
|
1533
1680
|
for (const jws of chain.log) {
|
|
1534
|
-
const decoded =
|
|
1681
|
+
const decoded = decodeJwsUnsafe4(jws);
|
|
1535
1682
|
if (!decoded) continue;
|
|
1536
1683
|
const payload = decoded.payload;
|
|
1537
1684
|
const cid = typeof decoded.header.cid === "string" ? decoded.header.cid : "";
|
|
@@ -1593,7 +1740,7 @@ var MemoryRelayStore = class {
|
|
|
1593
1740
|
if (!chain) return null;
|
|
1594
1741
|
const opsByCID = /* @__PURE__ */ new Map();
|
|
1595
1742
|
for (const jws of chain.log) {
|
|
1596
|
-
const decoded =
|
|
1743
|
+
const decoded = decodeJwsUnsafe4(jws);
|
|
1597
1744
|
if (!decoded) continue;
|
|
1598
1745
|
const payload = decoded.payload;
|
|
1599
1746
|
const opCID = typeof decoded.header.cid === "string" ? decoded.header.cid : "";
|
|
@@ -1610,7 +1757,7 @@ var MemoryRelayStore = class {
|
|
|
1610
1757
|
currentCID = op.previousCID;
|
|
1611
1758
|
}
|
|
1612
1759
|
const identity = await verifyIdentityChain2({ didPrefix: "did:dfos", log: path });
|
|
1613
|
-
const targetDecoded =
|
|
1760
|
+
const targetDecoded = decodeJwsUnsafe4(opsByCID.get(cid).jws);
|
|
1614
1761
|
const lastCreatedAt = typeof targetDecoded?.payload?.["createdAt"] === "string" ? (targetDecoded?.payload)["createdAt"] : "";
|
|
1615
1762
|
return { state: identity, lastCreatedAt };
|
|
1616
1763
|
}
|
|
@@ -1619,7 +1766,7 @@ var MemoryRelayStore = class {
|
|
|
1619
1766
|
if (!chain) return null;
|
|
1620
1767
|
const opsByCID = /* @__PURE__ */ new Map();
|
|
1621
1768
|
for (const jws of chain.log) {
|
|
1622
|
-
const decoded =
|
|
1769
|
+
const decoded = decodeJwsUnsafe4(jws);
|
|
1623
1770
|
if (!decoded) continue;
|
|
1624
1771
|
const payload = decoded.payload;
|
|
1625
1772
|
const opCID = typeof decoded.header.cid === "string" ? decoded.header.cid : "";
|
|
@@ -1646,7 +1793,7 @@ var MemoryRelayStore = class {
|
|
|
1646
1793
|
enforceAuthorization: true,
|
|
1647
1794
|
resolveIdentity
|
|
1648
1795
|
});
|
|
1649
|
-
const targetDecoded =
|
|
1796
|
+
const targetDecoded = decodeJwsUnsafe4(opsByCID.get(cid).jws);
|
|
1650
1797
|
const lastCreatedAt = typeof targetDecoded?.payload?.["createdAt"] === "string" ? (targetDecoded?.payload)["createdAt"] : "";
|
|
1651
1798
|
return { state: content, lastCreatedAt };
|
|
1652
1799
|
}
|
|
@@ -1680,8 +1827,7 @@ var MemoryRelayStore = class {
|
|
|
1680
1827
|
}
|
|
1681
1828
|
}
|
|
1682
1829
|
async markOpRejected(cid, _reason) {
|
|
1683
|
-
|
|
1684
|
-
if (entry) entry.status = "rejected";
|
|
1830
|
+
this.rawOps.delete(cid);
|
|
1685
1831
|
}
|
|
1686
1832
|
async countUnsequenced() {
|
|
1687
1833
|
let count = 0;
|
|
@@ -1699,6 +1845,8 @@ var MemoryRelayStore = class {
|
|
|
1699
1845
|
export {
|
|
1700
1846
|
MemoryRelayStore,
|
|
1701
1847
|
bootstrapRelayIdentity,
|
|
1848
|
+
bootstrapRelayIdentityFromKey,
|
|
1849
|
+
chunkOps,
|
|
1702
1850
|
computeOpCID,
|
|
1703
1851
|
createCurrentKeyResolver,
|
|
1704
1852
|
createHistoricalIdentityResolver,
|