@fedify/fedify 2.4.0-dev.2077 → 2.4.0-dev.2093

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/{builder-i3jxVe9m.mjs → builder-Ld4Y9ymR.mjs} +34 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-Cayy-ECj.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
  4. package/dist/{deno-Djkzd-G_.mjs → deno-8t8k_6Yv.mjs} +1 -1
  5. package/dist/{docloader-dA0RswUV.mjs → docloader-DJKcsAiF.mjs} +2 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  9. package/dist/federation/handler.test.mjs +5 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +7 -7
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/outgoing-proof.test.mjs +254 -5
  17. package/dist/federation/portable-gateway-keys.test.mjs +72 -11
  18. package/dist/federation/portable-inbox.test.mjs +513 -6
  19. package/dist/federation/portable.test.mjs +182 -4
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  22. package/dist/federation/tasks/tasks.test.mjs +2 -2
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +89 -1
  25. package/dist/{federation-CFd0Jhhv.cjs → federation-aSHO04ob.cjs} +472 -74
  26. package/dist/{http-CCJ97Jql.mjs → http-B2ftY0Ha.mjs} +4 -4
  27. package/dist/{http-Dvy_IwRa.cjs → http-DSaSAN7X.cjs} +1609 -1457
  28. package/dist/{http-aqh3yFdM.js → http-DyT7DP1Y.js} +1518 -1390
  29. package/dist/{key-IxTTC8Yf.mjs → key-Dwm4VswT.mjs} +39 -99
  30. package/dist/{kv-cache-BP69NV0P.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
  31. package/dist/{kv-cache-Bwwbh-xR.mjs → kv-cache-D0yYukqk.mjs} +1 -1
  32. package/dist/{kv-cache-Y6SDRSX6.js → kv-cache-DmroAz3M.js} +1 -1
  33. package/dist/{ld-l0B9nREb.mjs → ld-BYZOoDzZ.mjs} +3 -3
  34. package/dist/{metrics-D1p6hv4z.mjs → metrics-B7YwKTFr.mjs} +1 -1
  35. package/dist/{middleware-BDjCUHMS.mjs → middleware-BNeqKSsL.mjs} +1 -1
  36. package/dist/{middleware-SSfryVzY.js → middleware-Blq0882d.js} +480 -82
  37. package/dist/{middleware-BMgzqBtr.mjs → middleware-E_0IH72m.mjs} +278 -80
  38. package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
  39. package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
  40. package/dist/mod.cjs +5 -5
  41. package/dist/mod.d.cts +1 -1
  42. package/dist/mod.d.ts +1 -1
  43. package/dist/mod.js +5 -5
  44. package/dist/nodeinfo/handler.test.mjs +1 -1
  45. package/dist/{owner-DGbYmXqU.mjs → owner-Cgx2Dc7I.mjs} +5 -4
  46. package/dist/{owner-jH1z_YfD.js → owner-DAGSC5gX.js} +2 -2
  47. package/dist/{owner-BpOxL5PG.cjs → owner-szIZl4qI.cjs} +2 -2
  48. package/dist/{portable-key-CCwJGSsU.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
  49. package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
  50. package/dist/{proof-CVL7JnZd.mjs → proof-DVXC6nYz.mjs} +59 -18
  51. package/dist/{send-Ejy1jOz9.mjs → send-D-hOvYKy.mjs} +3 -3
  52. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  53. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  54. package/dist/sig/compound-proof-verification.test.mjs +160 -3
  55. package/dist/sig/compound-proof.test.mjs +2 -2
  56. package/dist/sig/http.test.mjs +2 -2
  57. package/dist/sig/key.test.mjs +1 -1
  58. package/dist/sig/ld.test.mjs +2 -2
  59. package/dist/sig/mod.cjs +3 -3
  60. package/dist/sig/mod.d.cts +1 -1
  61. package/dist/sig/mod.d.ts +1 -1
  62. package/dist/sig/mod.js +3 -3
  63. package/dist/sig/owner.test.mjs +1 -1
  64. package/dist/sig/portable-collection.test.mjs +69 -5
  65. package/dist/sig/portable-key.test.mjs +176 -10
  66. package/dist/sig/proof.test.mjs +107 -4
  67. package/dist/{sig-Dpy1B1Ad.js → sig-B5Ell7pO.js} +19 -3
  68. package/dist/{sig-xoqy3ATq.cjs → sig-CXOKN4Br.cjs} +19 -3
  69. package/dist/{temporal-DNJ1cNpr.mjs → temporal-CqpKGEPk.mjs} +1 -1
  70. package/dist/testing/kv.test.mjs +1 -1
  71. package/dist/utils/docloader.test.mjs +2 -2
  72. package/dist/utils/kv-cache.test.mjs +1 -1
  73. package/dist/utils/mod.cjs +1 -1
  74. package/dist/utils/mod.js +1 -1
  75. package/package.json +7 -7
@@ -7,12 +7,13 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
8
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
9
9
  import { t as assert } from "../assert-D1Eafmd_.mjs";
10
- import { t as exportJwk } from "../key-IxTTC8Yf.mjs";
11
- import { s as detachSignature } from "../ld-l0B9nREb.mjs";
12
- import { r as signObject } from "../proof-CVL7JnZd.mjs";
10
+ import { t as exportJwk } from "../key-Dwm4VswT.mjs";
11
+ import { s as detachSignature } from "../ld-BYZOoDzZ.mjs";
12
+ import { i as signObject } from "../proof-DVXC6nYz.mjs";
13
13
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { n as FederationImpl, o as createFederation } from "../middleware-BMgzqBtr.mjs";
15
- import { a as verifyCompoundPortableObjectProofs } from "../compound-proof-Cayy-ECj.mjs";
14
+ import { c as assertPortableActorActivity, n as FederationImpl, o as createFederation } from "../middleware-E_0IH72m.mjs";
15
+ import { a as verifyCompoundPortableObjectProofs } from "../compound-proof-CTx_lewQ.mjs";
16
+ import { configure, reset } from "@logtape/logtape";
16
17
  import { Create, Note, Person } from "@fedify/vocab";
17
18
  import { mockDocumentLoader, test } from "@fedify/fixture";
18
19
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
@@ -222,6 +223,29 @@ test("a non-portable activity embedding portable objects needs one key", async (
222
223
  assertEquals(accepted.bodies.length, 1);
223
224
  assertEquals(proofOf(accepted.bodies[0]).verificationMethod, ed25519PublicKey.id.href);
224
225
  });
226
+ test("a non-portable activity embedding compatible-ID objects needs one key", async () => {
227
+ const owner = await didKey();
228
+ const compatibleId = (path) => new URL(`https://gw.example/.well-known/apgateway/${owner.did}${path}`);
229
+ const child = await signObject(new Note({
230
+ id: compatibleId(`/objects/${crypto.randomUUID()}`),
231
+ attribution: compatibleId("/actor"),
232
+ content: "A portable note"
233
+ }), owner.privateKey, owner.keyId, {
234
+ ...options,
235
+ context: childContext,
236
+ created
237
+ });
238
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
239
+ const keys = [sender(await didKey()), sender(await didKey())];
240
+ assertEquals((await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, ...keys], recipient, httpCreate(child)), TypeError, "exactly one Ed25519 key"))).bodies.length, 0);
241
+ const key = {
242
+ keyId: ed25519PublicKey.id,
243
+ privateKey: ed25519PrivateKey
244
+ };
245
+ const accepted = await capture(() => ctx.sendActivity([rsaKey, key], recipient, httpCreate(child)));
246
+ assertEquals(accepted.bodies.length, 1);
247
+ await assertCompoundVerifies(accepted.bodies[0]);
248
+ });
225
249
  test("an activity without portable objects is still signed by every key", async () => {
226
250
  const ctx = createTestFederation().createContext(new URL("https://example.com/"));
227
251
  const keys = [await didKey(), await didKey()];
@@ -392,5 +416,230 @@ test("a Fedify inbox accepts a portable activity Fedify produced", async () => {
392
416
  });
393
417
  assertEquals([(await receiving.fetch(requests[0], { contextData: void 0 })).status, received], [202, 1]);
394
418
  });
419
+ const gateway = "https://gw.example";
420
+ function compatibleId(did, path, origin = gateway) {
421
+ return new URL(`${origin}/.well-known/apgateway/${did}${path}`);
422
+ }
423
+ function compatibleCreate(owner, object, { id, actor } = {}) {
424
+ return new Create({
425
+ id: id ?? compatibleId(owner.did, `/activities/${crypto.randomUUID()}`),
426
+ actor: actor ?? compatibleId(owner.did, "/actor"),
427
+ object
428
+ });
429
+ }
430
+ test("a compatible-ID activity is signed only by the key matching its DID", async () => {
431
+ const owner = await didKey();
432
+ const childOwner = await didKey();
433
+ const child = await signedChild(childOwner);
434
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
435
+ const { bodies, requests } = await capture(async () => ctx.sendActivity([
436
+ rsaKey,
437
+ sender(await didKey()),
438
+ sender(owner),
439
+ sender(childOwner)
440
+ ], recipient, compatibleCreate(owner, child)));
441
+ assertEquals(bodies.length, 1);
442
+ assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
443
+ assert(requests[0].headers.has("Signature"));
444
+ assertEquals(bodies[0].signature, void 0);
445
+ await assertCompoundVerifies(bodies[0]);
446
+ });
447
+ test("a compatible-ID actor's activity needs an ID of the actor's DID", async () => {
448
+ const owner = await didKey();
449
+ const other = await didKey();
450
+ const cases = [
451
+ [
452
+ "a compatible activity ID of another DID",
453
+ compatibleCreate(owner, void 0, { id: compatibleId(other.did, "/activities/1") }),
454
+ "with the same DID"
455
+ ],
456
+ [
457
+ "an ap: activity ID of another DID",
458
+ compatibleCreate(owner, void 0, { id: parseIri(`ap://${other.did}/activities/1`) }),
459
+ "with the same DID"
460
+ ],
461
+ [
462
+ "an ordinary activity ID",
463
+ compatibleCreate(owner, void 0, { id: new URL("https://gw.example/activities/1") }),
464
+ "with the same DID"
465
+ ],
466
+ [
467
+ "a malformed compatible activity ID",
468
+ compatibleCreate(owner, void 0, { id: new URL(compatibleId(owner.did, "/activities/1").href + "?@gateway=https%3A%2F%2Fgw.example") }),
469
+ "with the same DID"
470
+ ],
471
+ [
472
+ "a malformed compatible actor ID",
473
+ compatibleCreate(owner, void 0, { actor: new URL(compatibleId(owner.did, "/actor").href + "?@gateway=https%3A%2F%2Fgw.example") }),
474
+ "malformed FEP-ef61 portable ID"
475
+ ],
476
+ [
477
+ "compatible actors of different DIDs",
478
+ new Create({
479
+ id: compatibleId(owner.did, "/activities/1"),
480
+ actors: [compatibleId(owner.did, "/actor"), compatibleId(other.did, "/actor")]
481
+ }),
482
+ "different DIDs"
483
+ ]
484
+ ];
485
+ for (const [name, activity, message] of cases) {
486
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
487
+ const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, activity), TypeError, message));
488
+ assertEquals(bodies.length, 0, name);
489
+ }
490
+ });
491
+ test("a compatible-ID activity without exactly one DID-matching key is rejected", async () => {
492
+ const owner = await didKey();
493
+ const cases = [
494
+ [
495
+ "no key matches",
496
+ [sender(await didKey())],
497
+ "none of its 1 Ed25519 keys"
498
+ ],
499
+ [
500
+ "only a gateway key",
501
+ [{
502
+ keyId: compatibleId(owner.did, "/actor#main-key"),
503
+ privateKey: owner.privateKey
504
+ }],
505
+ "none of its 0 Ed25519 keys"
506
+ ],
507
+ [
508
+ "several keys match",
509
+ [sender(owner), {
510
+ keyId: new URL(`${owner.did}#second`),
511
+ privateKey: owner.privateKey
512
+ }],
513
+ "2 of its Ed25519 keys"
514
+ ]
515
+ ];
516
+ for (const [name, keys, message] of cases) {
517
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
518
+ const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, ...keys], recipient, compatibleCreate(owner)), TypeError, message));
519
+ assertEquals(bodies.length, 0, name);
520
+ }
521
+ });
522
+ test("compatible and ap: IDs of the same DID can be mixed", async () => {
523
+ const owner = await didKey();
524
+ const activities = [
525
+ compatibleCreate(owner, void 0, { id: parseIri(`ap://${owner.did}/activities/${crypto.randomUUID()}`) }),
526
+ compatibleCreate(owner, void 0, { actor: parseIri(`ap+ef61://${owner.did}/actor`) }),
527
+ compatibleCreate(owner, void 0, { id: compatibleId(owner.did, "/activities/1", "https://other.example") })
528
+ ];
529
+ for (const activity of activities) {
530
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
531
+ const { bodies } = await capture(async () => ctx.sendActivity([
532
+ rsaKey,
533
+ sender(await didKey()),
534
+ sender(owner)
535
+ ], recipient, activity));
536
+ assertEquals(bodies.length, 1, activity.id?.href);
537
+ assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
538
+ }
539
+ });
540
+ test("a pre-signed compatible-ID activity is not re-signed", async () => {
541
+ const owner = await didKey();
542
+ const activity = await signObject(compatibleCreate(owner, await signedChild(await didKey())), owner.privateKey, owner.keyId, {
543
+ ...options,
544
+ created
545
+ });
546
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
547
+ const { bodies } = await capture(async () => ctx.sendActivity([
548
+ rsaKey,
549
+ sender(await didKey()),
550
+ sender(await didKey())
551
+ ], recipient, activity));
552
+ assertEquals(bodies.length, 1);
553
+ assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
554
+ await assertCompoundVerifies(bodies[0]);
555
+ });
556
+ test("forced fanout selects the DID-matching key for a compatible-ID activity", async () => {
557
+ const owner = await didKey();
558
+ const { queue, queued } = createQueue();
559
+ const federation = createTestFederation(queue);
560
+ const ctx = federation.createContext(new URL("https://example.com/"));
561
+ const { bodies } = await capture(async () => {
562
+ await ctx.sendActivity([
563
+ rsaKey,
564
+ sender(await didKey()),
565
+ sender(owner)
566
+ ], recipient, compatibleCreate(owner, await signedChild(await didKey())), { fanout: "force" });
567
+ assertEquals(queued.length, 1);
568
+ for (let i = 0; i < queued.length; i++) await federation.processQueuedTask(void 0, queued[i]);
569
+ });
570
+ assertEquals(bodies.length, 1);
571
+ assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
572
+ await assertCompoundVerifies(bodies[0]);
573
+ });
574
+ test("a Fedify inbox accepts an activity Fedify produced for a compatible-ID actor", async () => {
575
+ const owner = await didKey();
576
+ const child = await signedChild(await didKey());
577
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
578
+ const { requests } = await capture(async () => ctx.sendActivity([sender(await didKey()), sender(owner)], recipient, compatibleCreate(owner, child)));
579
+ assertEquals(requests.length, 1);
580
+ let received = 0;
581
+ const receiving = createFederation({
582
+ kv: new MemoryKvStore(),
583
+ contextLoaderFactory: () => mockDocumentLoader,
584
+ documentLoaderFactory: () => mockDocumentLoader
585
+ });
586
+ receiving.setActorDispatcher("/users/{identifier}", () => null);
587
+ receiving.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
588
+ received++;
589
+ });
590
+ const body = await requests[0].text();
591
+ const deliver = (json) => receiving.fetch(new Request(requests[0].url, {
592
+ method: "POST",
593
+ headers: requests[0].headers,
594
+ body: json
595
+ }), { contextData: void 0 });
596
+ assertEquals([(await deliver(body)).status, received], [202, 1]);
597
+ const forged = JSON.parse(body);
598
+ forged.actor = compatibleId((await didKey()).did, "/actor").href;
599
+ assertEquals([(await deliver(JSON.stringify(forged))).status, received], [401, 1]);
600
+ });
601
+ async function captureLogs(run) {
602
+ const records = [];
603
+ await reset();
604
+ try {
605
+ await configure({
606
+ sinks: { buffer: (record) => records.push(record) },
607
+ filters: {},
608
+ loggers: [{
609
+ category: ["logtape", "meta"],
610
+ sinks: []
611
+ }, {
612
+ category: [],
613
+ sinks: ["buffer"],
614
+ lowestLevel: "warning"
615
+ }]
616
+ });
617
+ await run();
618
+ } finally {
619
+ await reset();
620
+ }
621
+ return records.filter((record) => record.category[0] === "fedify");
622
+ }
623
+ test("an activity on another gateway than its compatible-ID actor is warned about", async () => {
624
+ const owner = await didKey();
625
+ const warnings = (activity) => captureLogs(() => assertPortableActorActivity(activity)).then((records) => records.map((r) => r.properties.activityId));
626
+ const other = compatibleId(owner.did, "/activities/1", "https://other.example");
627
+ assertEquals(await warnings(compatibleCreate(owner, void 0, { id: other })), [other.href]);
628
+ assertEquals(await warnings(compatibleCreate(owner)), []);
629
+ assertEquals(await warnings(compatibleCreate(owner, void 0, { id: parseIri(`ap://${owner.did}/activities/1`) })), []);
630
+ assertEquals(await warnings(compatibleCreate(owner, void 0, {
631
+ id: other,
632
+ actor: parseIri(`ap://${owner.did}/actor`)
633
+ })), []);
634
+ });
635
+ test("an unsigned activity with a malformed compatible ID is rejected", async () => {
636
+ const owner = await didKey();
637
+ const ctx = createTestFederation().createContext(new URL("https://example.com/"));
638
+ const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, new Create({
639
+ id: new URL(compatibleId(owner.did, "/activities/1").href + "?@gateway=https%3A%2F%2Fgw.example"),
640
+ actor: new URL("https://example.com/users/alice")
641
+ })), TypeError, "malformed FEP-ef61 portable ID"));
642
+ assertEquals(bodies.length, 0);
643
+ });
395
644
  //#endregion
396
645
  export {};
@@ -7,10 +7,10 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
7
7
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
- import { p as signJsonLd } from "../ld-l0B9nREb.mjs";
11
- import { s as signRequest } from "../http-CCJ97Jql.mjs";
10
+ import { p as signJsonLd } from "../ld-BYZOoDzZ.mjs";
11
+ import { s as signRequest } from "../http-B2ftY0Ha.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { n as FederationImpl, o as createFederation, p as handleInbox } from "../middleware-BMgzqBtr.mjs";
13
+ import { m as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-E_0IH72m.mjs";
14
14
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
15
15
  import { t as KvKeyCache } from "../keycache-BUDHB5rY.mjs";
16
16
  import { Create, CryptographicKey, Person } from "@fedify/vocab";
@@ -229,11 +229,11 @@ test("Context.getDocumentLoader() signs requests with gateway keys for portable
229
229
  }
230
230
  assertEquals(keyId, gatewayKeyId.href);
231
231
  });
232
- async function actorDocument(gateways) {
233
- const keys = await createTestFederation(() => actorId).createContext(new URL(gateway)).getActorKeyPairs("alice");
232
+ async function actorDocument(gateways, id = actorId, signed = true) {
233
+ const keys = await createTestFederation(() => id).createContext(new URL(gateway)).getActorKeyPairs("alice");
234
234
  const json = await new Person({
235
- id: actorId,
236
- inbox: parseIri(`${actorId.href}/inbox`),
235
+ id,
236
+ inbox: parseIri(`${id.href}/inbox`),
237
237
  gateways: gateways.map((g) => new URL(g)),
238
238
  publicKey: keys[0].cryptographicKey,
239
239
  assertionMethods: keys.map((k) => k.multikey)
@@ -242,7 +242,7 @@ async function actorDocument(gateways) {
242
242
  contextLoader: mockDocumentLoader
243
243
  });
244
244
  json["@context"] = [...json["@context"], "https://w3id.org/security/data-integrity/v1"];
245
- return await sign(json);
245
+ return signed ? await sign(json) : json;
246
246
  }
247
247
  function createLoader(responses) {
248
248
  return (url) => {
@@ -255,8 +255,8 @@ function createLoader(responses) {
255
255
  });
256
256
  };
257
257
  }
258
- async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true } = {}) {
259
- const documentLoader = createLoader({ [compatibleActorId(gateway)]: await actorDocument(gateways) });
258
+ async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor } = {}) {
259
+ const documentLoader = createLoader({ [compatibleActorId(gateway)]: actor ?? await actorDocument(gateways) });
260
260
  const unsigned = new Request("https://local.example/inbox", {
261
261
  method: "POST",
262
262
  body: JSON.stringify(body),
@@ -357,7 +357,68 @@ test("handleInbox() does not take Linked Data Signatures for portable actors", a
357
357
  status: 401,
358
358
  dispatched: 0
359
359
  });
360
- assertEquals(lastResponseText, "Activities of portable actors must have valid Object Integrity Proofs.");
360
+ assertEquals(lastResponseText, "Failed to verify the request signature.");
361
+ });
362
+ const otherDid = await exportDidKey((await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])).publicKey);
363
+ function compatibleCreateJson(id, activityDid = did) {
364
+ return {
365
+ "@context": portableContext,
366
+ id: `${gateway}/.well-known/apgateway/${activityDid}/activities/${id}`,
367
+ type: "Create",
368
+ actor: compatibleActorId(gateway),
369
+ object: "https://example.com/notes/1"
370
+ };
371
+ }
372
+ test("handleInbox() does not trust compatible-ID actors by web origin", async () => {
373
+ const id = new URL(compatibleActorId(gateway));
374
+ for (const signed of [true, false]) {
375
+ const actor = await actorDocument([gateway], id, signed);
376
+ assertEquals(await deliver(compatibleCreateJson(`unsigned-${signed}`), [gateway], { actor }), {
377
+ status: 401,
378
+ dispatched: 0
379
+ }, `actor document signed: ${signed}`);
380
+ }
381
+ assertEquals(await deliver(await sign(compatibleCreateJson("signed")), [gateway], { actor: await actorDocument([gateway], id) }), {
382
+ status: 202,
383
+ dispatched: 1
384
+ });
385
+ });
386
+ test("handleInbox() requires proofs of compatible-ID actors", async () => {
387
+ assertEquals(await deliver(compatibleCreateJson("unsigned"), [gateway]), {
388
+ status: 401,
389
+ dispatched: 0
390
+ });
391
+ assertEquals(await deliver(await sign(compatibleCreateJson("signed")), [gateway]), {
392
+ status: 202,
393
+ dispatched: 1
394
+ });
395
+ assertEquals(await deliver(await sign(compatibleCreateJson("unsigned-request")), [gateway], { httpSignature: false }), {
396
+ status: 202,
397
+ dispatched: 1
398
+ });
399
+ });
400
+ test("handleInbox() checks the DIDs of compatible activity IDs", async () => {
401
+ for (const actor of [actorId.href, compatibleActorId(gateway)]) assertEquals(await deliver(await sign({
402
+ ...compatibleCreateJson("forged", otherDid),
403
+ actor
404
+ }), [gateway]), {
405
+ status: 401,
406
+ dispatched: 0
407
+ }, actor);
408
+ assertEquals(await deliver(await sign({
409
+ ...compatibleCreateJson("forged-ap"),
410
+ id: `ap://${otherDid}/activities/forged-ap`
411
+ }), [gateway]), {
412
+ status: 401,
413
+ dispatched: 0
414
+ });
415
+ assertEquals(await deliver(await sign({
416
+ ...compatibleCreateJson("malformed"),
417
+ id: `https://user@example.com/.well-known/apgateway/${did}/activities/2`
418
+ }), [gateway]), {
419
+ status: 401,
420
+ dispatched: 0
421
+ });
361
422
  });
362
423
  //#endregion
363
424
  export {};