@fedify/fedify 2.0.17 → 2.0.19

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 (62) hide show
  1. package/dist/{builder-Dgk0oJse.mjs → builder-DpMJ796G.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-BzRJ49gL.d.cts → context-CJZHWLmz.d.cts} +48 -6
  6. package/dist/{context-CdJ9wAQa.d.ts → context-DnsFj3yK.d.ts} +48 -6
  7. package/dist/{deno-Cb0U4mYY.mjs → deno-OvlgTe2z.mjs} +1 -1
  8. package/dist/{docloader-BtgBOjR2.mjs → docloader-LahMFnq1.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1358 -41
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +1588 -84
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.d.cts +2 -2
  16. package/dist/federation/mod.d.ts +2 -2
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/send.test.mjs +3 -3
  20. package/dist/federation/temporal.test.d.mts +2 -0
  21. package/dist/federation/temporal.test.mjs +71 -0
  22. package/dist/federation/webfinger.test.mjs +1 -1
  23. package/dist/{http-CkpjqreM.mjs → http-BGqCzFFW.mjs} +2 -2
  24. package/dist/{http-C8fyMUjD.cjs → http-B_rBGJzj.cjs} +1 -1
  25. package/dist/{http-DOir4i5c.js → http-GMhBzdUg.js} +1 -1
  26. package/dist/{inbox-VNZAN2rq.mjs → inbox-B0rurFqy.mjs} +7 -4
  27. package/dist/{key-jLdTQH2f.mjs → key-v_BpXvjh.mjs} +1 -1
  28. package/dist/{kv-cache-D7YHL8kZ.cjs → kv-cache-B-QyKmN6.cjs} +1 -1
  29. package/dist/{kv-cache-BT1ZahAh.js → kv-cache-DIgdPeX1.js} +1 -1
  30. package/dist/ld-DHdnBuQl.mjs +550 -0
  31. package/dist/{middleware-Bua1eJKj.mjs → middleware-B0dEe1fJ.mjs} +309 -76
  32. package/dist/{middleware-ChZir5hz.mjs → middleware-D-GLLKcV.mjs} +1 -1
  33. package/dist/{middleware-BIaxjP-q.cjs → middleware-DQE7csTG.cjs} +399 -73
  34. package/dist/{middleware-o4he3VG2.js → middleware-DwMR-UJ5.js} +396 -71
  35. package/dist/{middleware-BmYsY7pr.cjs → middleware-zX2hBOnb.cjs} +1 -1
  36. package/dist/{mod-CKkpvznG.d.cts → mod-43DJyETz.d.cts} +1 -1
  37. package/dist/{mod-Kzr52ns8.d.ts → mod-COgmn-UO.d.ts} +1 -1
  38. package/dist/mod.cjs +4 -4
  39. package/dist/mod.d.cts +2 -2
  40. package/dist/mod.d.ts +2 -2
  41. package/dist/mod.js +4 -4
  42. package/dist/nodeinfo/handler.test.mjs +1 -1
  43. package/dist/{owner-9JLKNjOK.mjs → owner-B8gYOXYm.mjs} +2 -2
  44. package/dist/{proof-Bds_rOyf.js → proof-D2j9J3Sr.js} +298 -4
  45. package/dist/{proof-CVNf-KlO.mjs → proof-DSGV862v.mjs} +2 -2
  46. package/dist/{proof-DWK4MOe5.cjs → proof-RC4Q3Ozn.cjs} +345 -3
  47. package/dist/{send-j6vHPA3l.mjs → send-myUWQlOg.mjs} +2 -2
  48. package/dist/sig/http.test.mjs +2 -2
  49. package/dist/sig/key.test.mjs +1 -1
  50. package/dist/sig/ld.test.mjs +558 -2
  51. package/dist/sig/mod.cjs +2 -2
  52. package/dist/sig/mod.js +2 -2
  53. package/dist/sig/owner.test.mjs +1 -1
  54. package/dist/sig/proof.test.mjs +1 -1
  55. package/dist/temporal-D4MiakbY.mjs +95 -0
  56. package/dist/testing/mod.d.mts +48 -6
  57. package/dist/utils/docloader.test.mjs +2 -2
  58. package/dist/utils/mod.cjs +1 -1
  59. package/dist/utils/mod.js +1 -1
  60. package/package.json +6 -6
  61. package/dist/ld-C48i1Mdp.mjs +0 -256
  62. /package/dist/{retry-Ddbq3AcK.mjs → retry-B_E3V_Dx.mjs} +0 -0
@@ -4,12 +4,14 @@ globalThis.addEventListener = () => {};
4
4
  import { n as createRequestContext, t as createInboxContext } from "../context-Juj6bdHC.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-Duiq_YC9.mjs";
7
+ import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
7
8
  import { t as assert } from "../assert-ddO5KLpe.mjs";
8
- import { s as signRequest } from "../http-CkpjqreM.mjs";
9
+ import { s as signRequest } from "../http-BGqCzFFW.mjs";
9
10
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-CtZLJq76.mjs";
11
+ import { a as compactJsonLd, f as signJsonLd } from "../ld-DHdnBuQl.mjs";
10
12
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
11
- import { a as createFederation, c as handleCollection, d as handleObject, f as respondWithObject, l as handleCustomCollection, p as respondWithObjectIfAcceptable, s as handleActor, u as handleInbox } from "../middleware-Bua1eJKj.mjs";
12
- import { t as InboxListenerSet } from "../inbox-VNZAN2rq.mjs";
13
+ import { a as createFederation, c as handleCollection, d as handleObject, f as respondWithObject, l as handleCustomCollection, p as respondWithObjectIfAcceptable, s as handleActor, u as handleInbox } from "../middleware-B0dEe1fJ.mjs";
14
+ import { t as InboxListenerSet } from "../inbox-B0rurFqy.mjs";
13
15
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
14
16
  import { Create, Note, Person } from "@fedify/vocab";
15
17
  //#region src/federation/handler.test.ts
@@ -873,6 +875,11 @@ test("handleInbox()", async () => {
873
875
  if (identifier !== "someone") return null;
874
876
  return new Person({ name: "Someone" });
875
877
  };
878
+ const restrictiveContextLoader = async (resource) => {
879
+ const url = new URL(resource).href;
880
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
881
+ throw new Error(`Unexpected context: ${url}`);
882
+ };
876
883
  const inboxOptions = {
877
884
  kv: new MemoryKvStore(),
878
885
  kvPrefixes: {
@@ -941,6 +948,49 @@ test("handleInbox()", async () => {
941
948
  });
942
949
  assertEquals(onNotFoundCalled, null);
943
950
  assertEquals(response.status, 401);
951
+ const malformedProofCreatedRequest = new Request("https://example.com/", {
952
+ method: "POST",
953
+ body: JSON.stringify({
954
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
955
+ id: "https://example.com/activities/invalid-proof-created",
956
+ type: "Create",
957
+ actor: "https://example.com/person2",
958
+ object: {
959
+ id: "https://example.com/notes/invalid-proof-created",
960
+ type: "Note",
961
+ attributedTo: "https://example.com/person2",
962
+ content: "Hello, world!"
963
+ },
964
+ proof: {
965
+ type: "DataIntegrityProof",
966
+ cryptosuite: "eddsa-jcs-2022",
967
+ verificationMethod: "https://example.com/person2#main-key",
968
+ proofPurpose: "assertionMethod",
969
+ created: { "@value": "not-a-date" },
970
+ proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
971
+ }
972
+ })
973
+ });
974
+ const malformedProofCreatedContext = createRequestContext({
975
+ federation,
976
+ request: malformedProofCreatedRequest,
977
+ url: new URL(malformedProofCreatedRequest.url),
978
+ data: void 0,
979
+ documentLoader: mockDocumentLoader,
980
+ contextLoader: mockDocumentLoader
981
+ });
982
+ response = await handleInbox(malformedProofCreatedRequest, {
983
+ recipient: null,
984
+ context: malformedProofCreatedContext,
985
+ inboxContextFactory(_activity) {
986
+ return createInboxContext({
987
+ ...malformedProofCreatedContext,
988
+ clone: void 0
989
+ });
990
+ },
991
+ ...inboxOptions
992
+ });
993
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
944
994
  onNotFoundCalled = null;
945
995
  const signedRequest = await signRequest(unsignedRequest.clone(), rsaPrivateKey3, rsaPublicKey3.id);
946
996
  const signedContext = createRequestContext({
@@ -963,85 +1013,1352 @@ test("handleInbox()", async () => {
963
1013
  });
964
1014
  assertEquals(onNotFoundCalled, null);
965
1015
  assertEquals([response.status, await response.text()], [202, ""]);
966
- response = await handleInbox(signedRequest, {
967
- recipient: "someone",
968
- context: signedContext,
1016
+ const ldSignedRequest = new Request("https://example.com/", {
1017
+ method: "POST",
1018
+ body: JSON.stringify(await signJsonLd({
1019
+ "@context": [
1020
+ "https://www.w3.org/ns/activitystreams",
1021
+ "https://w3id.org/identity/v1",
1022
+ "https://w3id.org/security/v1",
1023
+ "https://w3id.org/security/data-integrity/v1"
1024
+ ],
1025
+ id: "https://example.com/activities/ld-signed",
1026
+ type: "Create",
1027
+ actor: "https://example.com/person2",
1028
+ object: {
1029
+ id: "https://example.com/notes/ld-signed",
1030
+ type: "Note",
1031
+ attributedTo: "https://example.com/person2",
1032
+ content: "Hello, world!"
1033
+ }
1034
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
1035
+ });
1036
+ const ldSignedContext = createRequestContext({
1037
+ federation,
1038
+ request: ldSignedRequest,
1039
+ url: new URL(ldSignedRequest.url),
1040
+ data: void 0,
1041
+ documentLoader: mockDocumentLoader,
1042
+ contextLoader: restrictiveContextLoader
1043
+ });
1044
+ response = await handleInbox(ldSignedRequest, {
1045
+ recipient: null,
1046
+ context: ldSignedContext,
969
1047
  inboxContextFactory(_activity) {
970
1048
  return createInboxContext({
971
- ...unsignedContext,
972
- clone: void 0,
973
- recipient: "someone"
1049
+ ...ldSignedContext,
1050
+ clone: void 0
974
1051
  });
975
1052
  },
976
1053
  ...inboxOptions
977
1054
  });
978
1055
  assertEquals(onNotFoundCalled, null);
979
1056
  assertEquals([response.status, await response.text()], [202, ""]);
980
- response = await handleInbox(unsignedRequest, {
1057
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1058
+ let failRemoteContextOnce = true;
1059
+ const flakyContextLoader = async (resource) => {
1060
+ const url = new URL(resource).href;
1061
+ if (url === remoteContextUrl) {
1062
+ if (failRemoteContextOnce) {
1063
+ failRemoteContextOnce = false;
1064
+ throw new Error(`Unexpected context: ${url}`);
1065
+ }
1066
+ return {
1067
+ contextUrl: null,
1068
+ documentUrl: url,
1069
+ document: { "@context": { ext: "https://example.com/ext" } }
1070
+ };
1071
+ }
1072
+ return await mockDocumentLoader(url);
1073
+ };
1074
+ const httpSignedLdBody = {
1075
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1076
+ id: "https://example.com/activities/http-signed-ld",
1077
+ type: "Create",
1078
+ actor: "https://example.com/person2",
1079
+ ext: "preserve-me",
1080
+ object: {
1081
+ id: "https://example.com/notes/http-signed-ld",
1082
+ type: "Note",
1083
+ attributedTo: "https://example.com/person2",
1084
+ content: "Hello, world!"
1085
+ },
1086
+ signature: {
1087
+ type: "RsaSignature2017",
1088
+ creator: rsaPublicKey3.id.href,
1089
+ created: "2024-01-01T00:00:00Z",
1090
+ signatureValue: "bogus"
1091
+ }
1092
+ };
1093
+ const httpSignedLdRequest = await signRequest(new Request("https://example.com/", {
1094
+ method: "POST",
1095
+ body: JSON.stringify(httpSignedLdBody)
1096
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1097
+ const httpSignedLdContext = createRequestContext({
1098
+ federation,
1099
+ request: httpSignedLdRequest,
1100
+ url: new URL(httpSignedLdRequest.url),
1101
+ data: void 0,
1102
+ documentLoader: mockDocumentLoader,
1103
+ contextLoader: flakyContextLoader
1104
+ });
1105
+ response = await handleInbox(httpSignedLdRequest, {
981
1106
  recipient: null,
982
- context: unsignedContext,
1107
+ context: httpSignedLdContext,
983
1108
  inboxContextFactory(_activity) {
984
1109
  return createInboxContext({
985
- ...unsignedContext,
1110
+ ...httpSignedLdContext,
986
1111
  clone: void 0
987
1112
  });
988
1113
  },
989
- ...inboxOptions,
990
- skipSignatureVerification: true
1114
+ ...inboxOptions
991
1115
  });
992
1116
  assertEquals(onNotFoundCalled, null);
993
- assertEquals(response.status, 202);
994
- response = await handleInbox(unsignedRequest, {
995
- recipient: "someone",
996
- context: unsignedContext,
1117
+ assertEquals([response.status, await response.text()], [202, ""]);
1118
+ const ldSignedOnlyBody = await signJsonLd({
1119
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1120
+ id: "https://example.com/activities/ld-only-transient",
1121
+ type: "Create",
1122
+ actor: "https://example.com/person2",
1123
+ ext: "preserve-me",
1124
+ object: {
1125
+ id: "https://example.com/notes/ld-only-transient",
1126
+ type: "Note",
1127
+ attributedTo: "https://example.com/person2",
1128
+ content: "Hello, world!"
1129
+ }
1130
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1131
+ const url = new URL(resource).href;
1132
+ if (url === remoteContextUrl) return {
1133
+ contextUrl: null,
1134
+ documentUrl: url,
1135
+ document: { "@context": { ext: "https://example.com/ext" } }
1136
+ };
1137
+ return await mockDocumentLoader(url);
1138
+ } });
1139
+ const malformedTemporalLdSignedBody = await signJsonLd({
1140
+ "@context": "https://www.w3.org/ns/activitystreams",
1141
+ id: "https://example.com/activities/ld-only-invalid-published",
1142
+ type: "Create",
1143
+ actor: "https://example.com/person2",
1144
+ published: { "@value": "not-a-date" },
1145
+ object: {
1146
+ id: "https://example.com/notes/ld-only-invalid-published",
1147
+ type: "Note",
1148
+ attributedTo: "https://example.com/person2",
1149
+ content: "Hello, world!"
1150
+ }
1151
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1152
+ const malformedTemporalLdSignedRequest = new Request("https://example.com/", {
1153
+ method: "POST",
1154
+ body: JSON.stringify(malformedTemporalLdSignedBody)
1155
+ });
1156
+ const malformedTemporalLdSignedContext = createRequestContext({
1157
+ federation,
1158
+ request: malformedTemporalLdSignedRequest,
1159
+ url: new URL(malformedTemporalLdSignedRequest.url),
1160
+ data: void 0,
1161
+ documentLoader: mockDocumentLoader,
1162
+ contextLoader: mockDocumentLoader
1163
+ });
1164
+ response = await handleInbox(malformedTemporalLdSignedRequest, {
1165
+ recipient: null,
1166
+ context: malformedTemporalLdSignedContext,
997
1167
  inboxContextFactory(_activity) {
998
1168
  return createInboxContext({
999
- ...unsignedContext,
1000
- clone: void 0,
1001
- recipient: "someone"
1169
+ ...malformedTemporalLdSignedContext,
1170
+ clone: void 0
1002
1171
  });
1003
1172
  },
1004
- ...inboxOptions,
1005
- skipSignatureVerification: true
1173
+ ...inboxOptions
1006
1174
  });
1007
- assertEquals(onNotFoundCalled, null);
1008
- assertEquals(response.status, 202);
1009
- const signedInvalidRequest = await signRequest(new Request("https://example.com/", {
1175
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1176
+ const malformedClosedLdSignedBody = await signJsonLd({
1177
+ "@context": "https://www.w3.org/ns/activitystreams",
1178
+ id: "https://example.com/questions/ld-only-invalid-closed",
1179
+ type: "Question",
1180
+ closed: "2024-02-31T00:00:00Z"
1181
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1182
+ const malformedClosedLdSignedRequest = new Request("https://example.com/", {
1183
+ method: "POST",
1184
+ body: JSON.stringify(malformedClosedLdSignedBody)
1185
+ });
1186
+ const malformedClosedLdSignedContext = createRequestContext({
1187
+ federation,
1188
+ request: malformedClosedLdSignedRequest,
1189
+ url: new URL(malformedClosedLdSignedRequest.url),
1190
+ data: void 0,
1191
+ documentLoader: mockDocumentLoader,
1192
+ contextLoader: mockDocumentLoader
1193
+ });
1194
+ response = await handleInbox(malformedClosedLdSignedRequest, {
1195
+ recipient: null,
1196
+ context: malformedClosedLdSignedContext,
1197
+ inboxContextFactory(_activity) {
1198
+ return createInboxContext({
1199
+ ...malformedClosedLdSignedContext,
1200
+ clone: void 0
1201
+ });
1202
+ },
1203
+ ...inboxOptions
1204
+ });
1205
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1206
+ const malformedIriHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1010
1207
  method: "POST",
1011
1208
  body: JSON.stringify({
1012
- "@context": [
1013
- "https://www.w3.org/ns/activitystreams",
1014
- true,
1015
- 23
1016
- ],
1209
+ "@context": "https://www.w3.org/ns/activitystreams",
1210
+ id: "http://[",
1017
1211
  type: "Create",
1212
+ actor: "https://example.com/person2",
1018
1213
  object: {
1214
+ id: "https://example.com/notes/http-signed-invalid-iri",
1019
1215
  type: "Note",
1216
+ attributedTo: "https://example.com/person2",
1020
1217
  content: "Hello, world!"
1021
- },
1022
- actor: "https://example.com/users/alice"
1218
+ }
1023
1219
  })
1024
1220
  }), rsaPrivateKey3, rsaPublicKey3.id);
1025
- const signedInvalidContext = createRequestContext({
1221
+ const malformedIriHttpSignedContext = createRequestContext({
1026
1222
  federation,
1027
- request: signedInvalidRequest,
1028
- url: new URL(signedInvalidRequest.url),
1223
+ request: malformedIriHttpSignedRequest,
1224
+ url: new URL(malformedIriHttpSignedRequest.url),
1029
1225
  data: void 0,
1030
- documentLoader: mockDocumentLoader
1226
+ documentLoader: mockDocumentLoader,
1227
+ contextLoader: mockDocumentLoader
1031
1228
  });
1032
- response = await handleInbox(signedInvalidRequest, {
1229
+ response = await handleInbox(malformedIriHttpSignedRequest, {
1033
1230
  recipient: null,
1034
- context: signedContext,
1231
+ context: malformedIriHttpSignedContext,
1035
1232
  inboxContextFactory(_activity) {
1036
1233
  return createInboxContext({
1037
- ...signedInvalidContext,
1234
+ ...malformedIriHttpSignedContext,
1235
+ clone: void 0
1236
+ });
1237
+ },
1238
+ ...inboxOptions
1239
+ });
1240
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1241
+ const ldSignedOnlyRequest = new Request("https://example.com/", {
1242
+ method: "POST",
1243
+ body: JSON.stringify(ldSignedOnlyBody)
1244
+ });
1245
+ const ldSignedOnlyContext = createRequestContext({
1246
+ federation,
1247
+ request: ldSignedOnlyRequest,
1248
+ url: new URL(ldSignedOnlyRequest.url),
1249
+ data: void 0,
1250
+ documentLoader: mockDocumentLoader,
1251
+ contextLoader: async (resource) => {
1252
+ const url = new URL(resource).href;
1253
+ if (url === remoteContextUrl) throw new Error(`Unexpected context: ${url}`);
1254
+ return await mockDocumentLoader(url);
1255
+ }
1256
+ });
1257
+ await assertRejects(() => handleInbox(ldSignedOnlyRequest, {
1258
+ recipient: null,
1259
+ context: ldSignedOnlyContext,
1260
+ inboxContextFactory(_activity) {
1261
+ return createInboxContext({
1262
+ ...ldSignedOnlyContext,
1263
+ clone: void 0
1264
+ });
1265
+ },
1266
+ ...inboxOptions
1267
+ }), Error);
1268
+ failRemoteContextOnce = true;
1269
+ const invalidHttpFallbackRequest = new Request("https://example.com/", {
1270
+ method: "POST",
1271
+ body: JSON.stringify(ldSignedOnlyBody),
1272
+ headers: { Signature: "bogus" }
1273
+ });
1274
+ const invalidHttpFallbackContext = createRequestContext({
1275
+ federation,
1276
+ request: invalidHttpFallbackRequest,
1277
+ url: new URL(invalidHttpFallbackRequest.url),
1278
+ data: void 0,
1279
+ documentLoader: mockDocumentLoader,
1280
+ contextLoader: flakyContextLoader
1281
+ });
1282
+ await assertRejects(() => handleInbox(invalidHttpFallbackRequest, {
1283
+ recipient: null,
1284
+ context: invalidHttpFallbackContext,
1285
+ inboxContextFactory(_activity) {
1286
+ return createInboxContext({
1287
+ ...invalidHttpFallbackContext,
1288
+ clone: void 0
1289
+ });
1290
+ },
1291
+ ...inboxOptions
1292
+ }), Error);
1293
+ const transientKeyContextUrl = "https://remote.example/contexts/key";
1294
+ const transientCreatorUrl = "https://remote.example/keys/transient#main-key";
1295
+ const verificationFailureLdSignedBody = await signJsonLd({
1296
+ "@context": "https://www.w3.org/ns/activitystreams",
1297
+ id: "https://example.com/activities/ld-key-fetch-transient",
1298
+ type: "Create",
1299
+ actor: "https://example.com/person2",
1300
+ object: {
1301
+ id: "https://example.com/notes/ld-key-fetch-transient",
1302
+ type: "Note",
1303
+ attributedTo: "https://example.com/person2",
1304
+ content: "Hello, world!"
1305
+ }
1306
+ }, rsaPrivateKey3, new URL(transientCreatorUrl), { contextLoader: mockDocumentLoader });
1307
+ const verificationFailureLdSignedRequest = new Request("https://example.com/", {
1308
+ method: "POST",
1309
+ body: JSON.stringify(verificationFailureLdSignedBody),
1310
+ headers: { Signature: "bogus" }
1311
+ });
1312
+ const verificationFailureLdSignedContext = createRequestContext({
1313
+ federation,
1314
+ request: verificationFailureLdSignedRequest,
1315
+ url: new URL(verificationFailureLdSignedRequest.url),
1316
+ data: void 0,
1317
+ documentLoader: async (resource) => {
1318
+ if (resource === transientCreatorUrl) return {
1319
+ contextUrl: null,
1320
+ documentUrl: resource,
1321
+ document: {
1322
+ "@context": [transientKeyContextUrl],
1323
+ id: resource
1324
+ }
1325
+ };
1326
+ return await mockDocumentLoader(new URL(resource).href);
1327
+ },
1328
+ contextLoader: async (resource) => {
1329
+ if (resource === transientKeyContextUrl) throw new Error(`Transient key context failure: ${resource}`);
1330
+ return await mockDocumentLoader(new URL(resource).href);
1331
+ }
1332
+ });
1333
+ response = await handleInbox(verificationFailureLdSignedRequest, {
1334
+ recipient: null,
1335
+ context: verificationFailureLdSignedContext,
1336
+ inboxContextFactory(_activity) {
1337
+ return createInboxContext({
1338
+ ...verificationFailureLdSignedContext,
1339
+ clone: void 0
1340
+ });
1341
+ },
1342
+ ...inboxOptions
1343
+ });
1344
+ assertEquals([response.status, await response.text()], [401, "Failed to verify the request signature."]);
1345
+ failRemoteContextOnce = true;
1346
+ const deferredMalformedTemporalLdSignedBody = await signJsonLd({
1347
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1348
+ id: "https://example.com/activities/deferred-invalid-published",
1349
+ type: "Create",
1350
+ actor: "https://example.com/person2",
1351
+ ext: "preserve-me",
1352
+ published: { "@value": "not-a-date" },
1353
+ object: {
1354
+ id: "https://example.com/notes/deferred-invalid-published",
1355
+ type: "Note",
1356
+ attributedTo: "https://example.com/person2",
1357
+ content: "Hello, world!"
1358
+ }
1359
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1360
+ const url = new URL(resource).href;
1361
+ if (url === remoteContextUrl) return {
1362
+ contextUrl: null,
1363
+ documentUrl: url,
1364
+ document: { "@context": { ext: "https://example.com/ext" } }
1365
+ };
1366
+ return await mockDocumentLoader(url);
1367
+ } });
1368
+ const deferredMalformedTemporalLdSignedRequest = new Request("https://example.com/", {
1369
+ method: "POST",
1370
+ body: JSON.stringify(deferredMalformedTemporalLdSignedBody),
1371
+ headers: { Signature: "bogus" }
1372
+ });
1373
+ const deferredMalformedTemporalLdSignedContext = createRequestContext({
1374
+ federation,
1375
+ request: deferredMalformedTemporalLdSignedRequest,
1376
+ url: new URL(deferredMalformedTemporalLdSignedRequest.url),
1377
+ data: void 0,
1378
+ documentLoader: mockDocumentLoader,
1379
+ contextLoader: flakyContextLoader
1380
+ });
1381
+ response = await handleInbox(deferredMalformedTemporalLdSignedRequest, {
1382
+ recipient: null,
1383
+ context: deferredMalformedTemporalLdSignedContext,
1384
+ inboxContextFactory(_activity) {
1385
+ return createInboxContext({
1386
+ ...deferredMalformedTemporalLdSignedContext,
1387
+ clone: void 0
1388
+ });
1389
+ },
1390
+ ...inboxOptions
1391
+ });
1392
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1393
+ const malformedLdSignedRequest = new Request("https://example.com/", {
1394
+ method: "POST",
1395
+ body: JSON.stringify({
1396
+ ...ldSignedOnlyBody,
1397
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
1398
+ })
1399
+ });
1400
+ const malformedLdSignedContext = createRequestContext({
1401
+ federation,
1402
+ request: malformedLdSignedRequest,
1403
+ url: new URL(malformedLdSignedRequest.url),
1404
+ data: void 0,
1405
+ documentLoader: mockDocumentLoader,
1406
+ contextLoader: mockDocumentLoader
1407
+ });
1408
+ response = await handleInbox(malformedLdSignedRequest, {
1409
+ recipient: null,
1410
+ context: malformedLdSignedContext,
1411
+ inboxContextFactory(_activity) {
1412
+ return createInboxContext({
1413
+ ...malformedLdSignedContext,
1414
+ clone: void 0
1415
+ });
1416
+ },
1417
+ ...inboxOptions
1418
+ });
1419
+ assertEquals([response.status, await response.text()], [400, "Invalid JSON-LD."]);
1420
+ const dualSignedInvalidCreatorRequest = await signRequest(new Request("https://example.com/", {
1421
+ method: "POST",
1422
+ body: JSON.stringify({
1423
+ ...httpSignedLdBody,
1424
+ signature: {
1425
+ ...httpSignedLdBody.signature,
1426
+ creator: "not a url"
1427
+ }
1428
+ })
1429
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1430
+ const dualSignedInvalidCreatorContext = createRequestContext({
1431
+ federation,
1432
+ request: dualSignedInvalidCreatorRequest,
1433
+ url: new URL(dualSignedInvalidCreatorRequest.url),
1434
+ data: void 0,
1435
+ documentLoader: mockDocumentLoader,
1436
+ contextLoader: flakyContextLoader
1437
+ });
1438
+ response = await handleInbox(dualSignedInvalidCreatorRequest, {
1439
+ recipient: null,
1440
+ context: dualSignedInvalidCreatorContext,
1441
+ inboxContextFactory(_activity) {
1442
+ return createInboxContext({
1443
+ ...dualSignedInvalidCreatorContext,
1038
1444
  clone: void 0
1039
1445
  });
1040
1446
  },
1041
1447
  ...inboxOptions
1042
1448
  });
1043
1449
  assertEquals(onNotFoundCalled, null);
1044
- assertEquals(response.status, 400);
1450
+ assertEquals([response.status, await response.text()], [202, ""]);
1451
+ const invalidUrlHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1452
+ method: "POST",
1453
+ body: JSON.stringify({
1454
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1455
+ id: "https://example.com/activities/http-signed-invalid-context",
1456
+ type: "Create",
1457
+ actor: "https://example.com/person2",
1458
+ ext: "preserve-me",
1459
+ object: {
1460
+ id: "https://example.com/notes/http-signed-invalid-context",
1461
+ type: "Note",
1462
+ attributedTo: "https://example.com/person2",
1463
+ content: "Hello, world!"
1464
+ }
1465
+ })
1466
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1467
+ const invalidUrlHttpSignedContext = createRequestContext({
1468
+ federation,
1469
+ request: invalidUrlHttpSignedRequest,
1470
+ url: new URL(invalidUrlHttpSignedRequest.url),
1471
+ data: void 0,
1472
+ documentLoader: mockDocumentLoader,
1473
+ contextLoader: async (resource) => {
1474
+ const url = new URL(resource).href;
1475
+ if (url === remoteContextUrl) {
1476
+ const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
1477
+ error.name = "jsonld.InvalidUrl";
1478
+ error.details = {
1479
+ code: "loading remote context failed",
1480
+ url
1481
+ };
1482
+ throw error;
1483
+ }
1484
+ return await mockDocumentLoader(url);
1485
+ }
1486
+ });
1487
+ await assertRejects(() => handleInbox(invalidUrlHttpSignedRequest, {
1488
+ recipient: null,
1489
+ context: invalidUrlHttpSignedContext,
1490
+ inboxContextFactory(_activity) {
1491
+ return createInboxContext({
1492
+ ...invalidUrlHttpSignedContext,
1493
+ clone: void 0
1494
+ });
1495
+ },
1496
+ ...inboxOptions
1497
+ }), Error);
1498
+ const opaqueContextIdHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1499
+ method: "POST",
1500
+ body: JSON.stringify({
1501
+ "@context": ["app-context", "https://www.w3.org/ns/activitystreams"],
1502
+ id: "https://example.com/activities/http-signed-opaque-context",
1503
+ type: "Create",
1504
+ actor: "https://example.com/person2",
1505
+ object: {
1506
+ id: "https://example.com/notes/http-signed-opaque-context",
1507
+ type: "Note",
1508
+ attributedTo: "https://example.com/person2",
1509
+ content: "Hello, world!"
1510
+ }
1511
+ })
1512
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1513
+ const opaqueContextIdHttpSignedContext = createRequestContext({
1514
+ federation,
1515
+ request: opaqueContextIdHttpSignedRequest,
1516
+ url: new URL(opaqueContextIdHttpSignedRequest.url),
1517
+ data: void 0,
1518
+ documentLoader: mockDocumentLoader,
1519
+ contextLoader: async (resource) => {
1520
+ if (resource === "app-context") {
1521
+ const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
1522
+ error.name = "jsonld.InvalidUrl";
1523
+ error.details = {
1524
+ code: "loading remote context failed",
1525
+ url: resource
1526
+ };
1527
+ throw error;
1528
+ }
1529
+ return await mockDocumentLoader(new URL(resource).href);
1530
+ }
1531
+ });
1532
+ await assertRejects(() => handleInbox(opaqueContextIdHttpSignedRequest, {
1533
+ recipient: null,
1534
+ context: opaqueContextIdHttpSignedContext,
1535
+ inboxContextFactory(_activity) {
1536
+ return createInboxContext({
1537
+ ...opaqueContextIdHttpSignedContext,
1538
+ clone: void 0
1539
+ });
1540
+ },
1541
+ ...inboxOptions
1542
+ }), Error);
1543
+ const opaqueContextTypeErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1544
+ method: "POST",
1545
+ body: JSON.stringify({
1546
+ "@context": ["app:context", "https://www.w3.org/ns/activitystreams"],
1547
+ id: "https://example.com/activities/http-signed-opaque-typeerror",
1548
+ type: "Create",
1549
+ actor: "https://example.com/person2",
1550
+ object: {
1551
+ id: "https://example.com/notes/http-signed-opaque-typeerror",
1552
+ type: "Note",
1553
+ attributedTo: "https://example.com/person2",
1554
+ content: "Hello, world!"
1555
+ }
1556
+ })
1557
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1558
+ const opaqueContextTypeErrorHttpSignedContext = createRequestContext({
1559
+ federation,
1560
+ request: opaqueContextTypeErrorHttpSignedRequest,
1561
+ url: new URL(opaqueContextTypeErrorHttpSignedRequest.url),
1562
+ data: void 0,
1563
+ documentLoader: mockDocumentLoader,
1564
+ contextLoader: async (resource) => {
1565
+ if (resource === "app:context") throw new TypeError(`Invalid URL: ${resource}`);
1566
+ return await mockDocumentLoader(new URL(resource).href);
1567
+ }
1568
+ });
1569
+ await assertRejects(() => handleInbox(opaqueContextTypeErrorHttpSignedRequest, {
1570
+ recipient: null,
1571
+ context: opaqueContextTypeErrorHttpSignedContext,
1572
+ inboxContextFactory(_activity) {
1573
+ return createInboxContext({
1574
+ ...opaqueContextTypeErrorHttpSignedContext,
1575
+ clone: void 0
1576
+ });
1577
+ },
1578
+ ...inboxOptions
1579
+ }), Error);
1580
+ const networkPathContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1581
+ method: "POST",
1582
+ body: JSON.stringify({
1583
+ "@context": ["//cdn.example/ctx", "https://www.w3.org/ns/activitystreams"],
1584
+ id: "https://example.com/activities/http-signed-network-path-context",
1585
+ type: "Create",
1586
+ actor: "https://example.com/person2",
1587
+ object: {
1588
+ id: "https://example.com/notes/http-signed-network-path-context",
1589
+ type: "Note",
1590
+ attributedTo: "https://example.com/person2",
1591
+ content: "Hello, world!"
1592
+ }
1593
+ })
1594
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1595
+ const networkPathContextHttpSignedContext = createRequestContext({
1596
+ federation,
1597
+ request: networkPathContextHttpSignedRequest,
1598
+ url: new URL(networkPathContextHttpSignedRequest.url),
1599
+ data: void 0,
1600
+ documentLoader: mockDocumentLoader,
1601
+ contextLoader: async (resource) => {
1602
+ if (resource === "//cdn.example/ctx") {
1603
+ const error = /* @__PURE__ */ new Error(`Network-path context backend is unavailable: ${resource}`);
1604
+ error.name = "jsonld.InvalidUrl";
1605
+ error.details = {
1606
+ code: "loading remote context failed",
1607
+ url: resource
1608
+ };
1609
+ throw error;
1610
+ }
1611
+ return await mockDocumentLoader(new URL(resource).href);
1612
+ }
1613
+ });
1614
+ await assertRejects(() => handleInbox(networkPathContextHttpSignedRequest, {
1615
+ recipient: null,
1616
+ context: networkPathContextHttpSignedContext,
1617
+ inboxContextFactory(_activity) {
1618
+ return createInboxContext({
1619
+ ...networkPathContextHttpSignedContext,
1620
+ clone: void 0
1621
+ });
1622
+ },
1623
+ ...inboxOptions
1624
+ }), Error);
1625
+ const malformedNetworkPathContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1626
+ method: "POST",
1627
+ body: JSON.stringify({
1628
+ "@context": ["//[", "https://www.w3.org/ns/activitystreams"],
1629
+ id: "https://example.com/activities/http-signed-malformed-network-path-context",
1630
+ type: "Create",
1631
+ actor: "https://example.com/person2",
1632
+ object: {
1633
+ id: "https://example.com/notes/http-signed-malformed-network-path-context",
1634
+ type: "Note",
1635
+ attributedTo: "https://example.com/person2",
1636
+ content: "Hello, world!"
1637
+ }
1638
+ })
1639
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1640
+ const malformedNetworkPathContextHttpSignedContext = createRequestContext({
1641
+ federation,
1642
+ request: malformedNetworkPathContextHttpSignedRequest,
1643
+ url: new URL(malformedNetworkPathContextHttpSignedRequest.url),
1644
+ data: void 0,
1645
+ documentLoader: mockDocumentLoader,
1646
+ contextLoader: async (resource) => {
1647
+ if (resource === "//[") {
1648
+ const error = /* @__PURE__ */ new Error(`Malformed network-path context: ${resource}`);
1649
+ error.name = "jsonld.InvalidUrl";
1650
+ error.details = {
1651
+ code: "loading remote context failed",
1652
+ url: resource
1653
+ };
1654
+ throw error;
1655
+ }
1656
+ return await mockDocumentLoader(new URL(resource).href);
1657
+ }
1658
+ });
1659
+ response = await handleInbox(malformedNetworkPathContextHttpSignedRequest, {
1660
+ recipient: null,
1661
+ context: malformedNetworkPathContextHttpSignedContext,
1662
+ inboxContextFactory(_activity) {
1663
+ return createInboxContext({
1664
+ ...malformedNetworkPathContextHttpSignedContext,
1665
+ clone: void 0
1666
+ });
1667
+ },
1668
+ ...inboxOptions
1669
+ });
1670
+ assertEquals(response.status, 400);
1671
+ const malformedUrlLikeContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1672
+ method: "POST",
1673
+ body: JSON.stringify({
1674
+ "@context": ["http://[", "https://www.w3.org/ns/activitystreams"],
1675
+ id: "https://example.com/activities/http-signed-malformed-url-like-context",
1676
+ type: "Create",
1677
+ actor: "https://example.com/person2",
1678
+ object: {
1679
+ id: "https://example.com/notes/http-signed-malformed-url-like-context",
1680
+ type: "Note",
1681
+ attributedTo: "https://example.com/person2",
1682
+ content: "Hello, world!"
1683
+ }
1684
+ })
1685
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1686
+ const malformedUrlLikeContextHttpSignedContext = createRequestContext({
1687
+ federation,
1688
+ request: malformedUrlLikeContextHttpSignedRequest,
1689
+ url: new URL(malformedUrlLikeContextHttpSignedRequest.url),
1690
+ data: void 0,
1691
+ documentLoader: mockDocumentLoader,
1692
+ contextLoader: async (resource) => {
1693
+ if (resource === "http://[") {
1694
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
1695
+ error.name = "jsonld.InvalidUrl";
1696
+ error.details = {
1697
+ code: "loading remote context failed",
1698
+ url: resource
1699
+ };
1700
+ throw error;
1701
+ }
1702
+ return await mockDocumentLoader(new URL(resource).href);
1703
+ }
1704
+ });
1705
+ response = await handleInbox(malformedUrlLikeContextHttpSignedRequest, {
1706
+ recipient: null,
1707
+ context: malformedUrlLikeContextHttpSignedContext,
1708
+ inboxContextFactory(_activity) {
1709
+ return createInboxContext({
1710
+ ...malformedUrlLikeContextHttpSignedContext,
1711
+ clone: void 0
1712
+ });
1713
+ },
1714
+ ...inboxOptions
1715
+ });
1716
+ assertEquals(response.status, 400);
1717
+ const malformedContextUrlHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1718
+ method: "POST",
1719
+ body: JSON.stringify({
1720
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"],
1721
+ id: "https://example.com/activities/http-signed-malformed-context",
1722
+ type: "Create",
1723
+ actor: "https://example.com/person2",
1724
+ object: {
1725
+ id: "https://example.com/notes/http-signed-malformed-context",
1726
+ type: "Note",
1727
+ attributedTo: "https://example.com/person2",
1728
+ content: "Hello, world!"
1729
+ }
1730
+ })
1731
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1732
+ const malformedContextUrlHttpSignedContext = createRequestContext({
1733
+ federation,
1734
+ request: malformedContextUrlHttpSignedRequest,
1735
+ url: new URL(malformedContextUrlHttpSignedRequest.url),
1736
+ data: void 0,
1737
+ documentLoader: mockDocumentLoader,
1738
+ contextLoader: async (resource) => {
1739
+ if (resource === "not a url") {
1740
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
1741
+ error.name = "jsonld.InvalidUrl";
1742
+ error.details = {
1743
+ code: "loading remote context failed",
1744
+ url: resource
1745
+ };
1746
+ throw error;
1747
+ }
1748
+ return await mockDocumentLoader(new URL(resource).href);
1749
+ }
1750
+ });
1751
+ response = await handleInbox(malformedContextUrlHttpSignedRequest, {
1752
+ recipient: null,
1753
+ context: malformedContextUrlHttpSignedContext,
1754
+ inboxContextFactory(_activity) {
1755
+ return createInboxContext({
1756
+ ...malformedContextUrlHttpSignedContext,
1757
+ clone: void 0
1758
+ });
1759
+ },
1760
+ ...inboxOptions
1761
+ });
1762
+ assertEquals(response.status, 400);
1763
+ const invalidRemoteContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1764
+ method: "POST",
1765
+ body: JSON.stringify({
1766
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1767
+ id: "https://example.com/activities/http-signed-invalid-remote-context",
1768
+ type: "Create",
1769
+ actor: "https://example.com/person2",
1770
+ object: {
1771
+ id: "https://example.com/notes/http-signed-invalid-remote-context",
1772
+ type: "Note",
1773
+ attributedTo: "https://example.com/person2",
1774
+ content: "Hello, world!"
1775
+ }
1776
+ })
1777
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1778
+ const invalidRemoteContextHttpSignedContext = createRequestContext({
1779
+ federation,
1780
+ request: invalidRemoteContextHttpSignedRequest,
1781
+ url: new URL(invalidRemoteContextHttpSignedRequest.url),
1782
+ data: void 0,
1783
+ documentLoader: mockDocumentLoader,
1784
+ contextLoader: async (resource) => {
1785
+ const url = new URL(resource).href;
1786
+ if (url === remoteContextUrl) return {
1787
+ contextUrl: null,
1788
+ documentUrl: url,
1789
+ document: [
1790
+ "not",
1791
+ "an",
1792
+ "object"
1793
+ ]
1794
+ };
1795
+ return await mockDocumentLoader(url);
1796
+ }
1797
+ });
1798
+ response = await handleInbox(invalidRemoteContextHttpSignedRequest, {
1799
+ recipient: null,
1800
+ context: invalidRemoteContextHttpSignedContext,
1801
+ inboxContextFactory(_activity) {
1802
+ return createInboxContext({
1803
+ ...invalidRemoteContextHttpSignedContext,
1804
+ clone: void 0
1805
+ });
1806
+ },
1807
+ ...inboxOptions
1808
+ });
1809
+ assertEquals(response.status, 400);
1810
+ const invalidUrlAbsoluteContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1811
+ method: "POST",
1812
+ body: JSON.stringify({
1813
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1814
+ id: "https://example.com/activities/http-signed-invalid-url-context",
1815
+ type: "Create",
1816
+ actor: "https://example.com/person2",
1817
+ ext: "preserve-me",
1818
+ object: {
1819
+ id: "https://example.com/notes/http-signed-invalid-url-context",
1820
+ type: "Note",
1821
+ attributedTo: "https://example.com/person2",
1822
+ content: "Hello, world!"
1823
+ }
1824
+ })
1825
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1826
+ const invalidUrlAbsoluteContextHttpSignedContext = createRequestContext({
1827
+ federation,
1828
+ request: invalidUrlAbsoluteContextHttpSignedRequest,
1829
+ url: new URL(invalidUrlAbsoluteContextHttpSignedRequest.url),
1830
+ data: void 0,
1831
+ documentLoader: mockDocumentLoader,
1832
+ contextLoader: async (resource) => {
1833
+ const url = new URL(resource).href;
1834
+ if (url === remoteContextUrl) throw new TypeError(`Invalid URL: ${url}`);
1835
+ return await mockDocumentLoader(url);
1836
+ }
1837
+ });
1838
+ await assertRejects(() => handleInbox(invalidUrlAbsoluteContextHttpSignedRequest, {
1839
+ recipient: null,
1840
+ context: invalidUrlAbsoluteContextHttpSignedContext,
1841
+ inboxContextFactory(_activity) {
1842
+ return createInboxContext({
1843
+ ...invalidUrlAbsoluteContextHttpSignedContext,
1844
+ clone: void 0
1845
+ });
1846
+ },
1847
+ ...inboxOptions
1848
+ }), Error);
1849
+ const typeErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1850
+ method: "POST",
1851
+ body: JSON.stringify({
1852
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1853
+ id: "https://example.com/activities/http-signed-typeerror-context",
1854
+ type: "Create",
1855
+ actor: "https://example.com/person2",
1856
+ ext: "preserve-me",
1857
+ object: {
1858
+ id: "https://example.com/notes/http-signed-typeerror-context",
1859
+ type: "Note",
1860
+ attributedTo: "https://example.com/person2",
1861
+ content: "Hello, world!"
1862
+ }
1863
+ })
1864
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1865
+ const typeErrorHttpSignedContext = createRequestContext({
1866
+ federation,
1867
+ request: typeErrorHttpSignedRequest,
1868
+ url: new URL(typeErrorHttpSignedRequest.url),
1869
+ data: void 0,
1870
+ documentLoader: mockDocumentLoader,
1871
+ contextLoader: async (resource) => {
1872
+ const url = new URL(resource).href;
1873
+ if (url === remoteContextUrl) throw new TypeError(`The remote context host timed out: ${url}`);
1874
+ return await mockDocumentLoader(url);
1875
+ }
1876
+ });
1877
+ await assertRejects(() => handleInbox(typeErrorHttpSignedRequest, {
1878
+ recipient: null,
1879
+ context: typeErrorHttpSignedContext,
1880
+ inboxContextFactory(_activity) {
1881
+ return createInboxContext({
1882
+ ...typeErrorHttpSignedContext,
1883
+ clone: void 0
1884
+ });
1885
+ },
1886
+ ...inboxOptions
1887
+ }), Error);
1888
+ const rangeErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1889
+ method: "POST",
1890
+ body: JSON.stringify({
1891
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1892
+ id: "https://example.com/activities/http-signed-rangeerror-context",
1893
+ type: "Create",
1894
+ actor: "https://example.com/person2",
1895
+ ext: "preserve-me",
1896
+ object: {
1897
+ id: "https://example.com/notes/http-signed-rangeerror-context",
1898
+ type: "Note",
1899
+ attributedTo: "https://example.com/person2",
1900
+ content: "Hello, world!"
1901
+ }
1902
+ })
1903
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1904
+ const rangeErrorHttpSignedContext = createRequestContext({
1905
+ federation,
1906
+ request: rangeErrorHttpSignedRequest,
1907
+ url: new URL(rangeErrorHttpSignedRequest.url),
1908
+ data: void 0,
1909
+ documentLoader: mockDocumentLoader,
1910
+ contextLoader: async (resource) => {
1911
+ const url = new URL(resource).href;
1912
+ if (url === remoteContextUrl) throw new RangeError(`Temporary remote context cache window exceeded: ${url}`);
1913
+ return await mockDocumentLoader(url);
1914
+ }
1915
+ });
1916
+ await assertRejects(() => handleInbox(rangeErrorHttpSignedRequest, {
1917
+ recipient: null,
1918
+ context: rangeErrorHttpSignedContext,
1919
+ inboxContextFactory(_activity) {
1920
+ return createInboxContext({
1921
+ ...rangeErrorHttpSignedContext,
1922
+ clone: void 0
1923
+ });
1924
+ },
1925
+ ...inboxOptions
1926
+ }), Error);
1927
+ const syntaxErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1928
+ method: "POST",
1929
+ body: JSON.stringify({
1930
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1931
+ id: "https://example.com/activities/http-signed-syntax-context",
1932
+ type: "Create",
1933
+ actor: "https://example.com/person2",
1934
+ ext: "preserve-me",
1935
+ object: {
1936
+ id: "https://example.com/notes/http-signed-syntax-context",
1937
+ type: "Note",
1938
+ attributedTo: "https://example.com/person2",
1939
+ content: "Hello, world!"
1940
+ }
1941
+ })
1942
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1943
+ const syntaxErrorHttpSignedContext = createRequestContext({
1944
+ federation,
1945
+ request: syntaxErrorHttpSignedRequest,
1946
+ url: new URL(syntaxErrorHttpSignedRequest.url),
1947
+ data: void 0,
1948
+ documentLoader: mockDocumentLoader,
1949
+ contextLoader: async (resource) => {
1950
+ const url = new URL(resource).href;
1951
+ if (url === remoteContextUrl) {
1952
+ const error = /* @__PURE__ */ new Error(`Transient syntax failure: ${url}`);
1953
+ error.name = "jsonld.SyntaxError";
1954
+ error.details = { code: "loading remote context failed" };
1955
+ throw error;
1956
+ }
1957
+ return await mockDocumentLoader(url);
1958
+ }
1959
+ });
1960
+ await assertRejects(() => handleInbox(syntaxErrorHttpSignedRequest, {
1961
+ recipient: null,
1962
+ context: syntaxErrorHttpSignedContext,
1963
+ inboxContextFactory(_activity) {
1964
+ return createInboxContext({
1965
+ ...syntaxErrorHttpSignedContext,
1966
+ clone: void 0
1967
+ });
1968
+ },
1969
+ ...inboxOptions
1970
+ }), Error);
1971
+ response = await handleInbox(signedRequest, {
1972
+ recipient: "someone",
1973
+ context: signedContext,
1974
+ inboxContextFactory(_activity) {
1975
+ return createInboxContext({
1976
+ ...unsignedContext,
1977
+ clone: void 0,
1978
+ recipient: "someone"
1979
+ });
1980
+ },
1981
+ ...inboxOptions
1982
+ });
1983
+ assertEquals(onNotFoundCalled, null);
1984
+ assertEquals([response.status, await response.text()], [202, ""]);
1985
+ response = await handleInbox(unsignedRequest, {
1986
+ recipient: null,
1987
+ context: unsignedContext,
1988
+ inboxContextFactory(_activity) {
1989
+ return createInboxContext({
1990
+ ...unsignedContext,
1991
+ clone: void 0
1992
+ });
1993
+ },
1994
+ ...inboxOptions,
1995
+ skipSignatureVerification: true
1996
+ });
1997
+ assertEquals(onNotFoundCalled, null);
1998
+ assertEquals(response.status, 202);
1999
+ response = await handleInbox(unsignedRequest, {
2000
+ recipient: "someone",
2001
+ context: unsignedContext,
2002
+ inboxContextFactory(_activity) {
2003
+ return createInboxContext({
2004
+ ...unsignedContext,
2005
+ clone: void 0,
2006
+ recipient: "someone"
2007
+ });
2008
+ },
2009
+ ...inboxOptions,
2010
+ skipSignatureVerification: true
2011
+ });
2012
+ assertEquals(onNotFoundCalled, null);
2013
+ assertEquals(response.status, 202);
2014
+ const unsafeJson = {
2015
+ "@context": ["https://www.w3.org/ns/activitystreams", { rev: "@reverse" }],
2016
+ id: "https://example.com/activities/unsafe",
2017
+ type: "Announce",
2018
+ actor: "https://example.com/person2",
2019
+ object: "https://example.com/notes/1",
2020
+ rev: { object: {
2021
+ id: "https://example.com/activities/undo",
2022
+ type: "Undo",
2023
+ actor: "https://example.com/person2"
2024
+ } }
2025
+ };
2026
+ const unsafeRequest = await signRequest(new Request("https://example.com/", {
2027
+ method: "POST",
2028
+ body: JSON.stringify(unsafeJson)
2029
+ }), rsaPrivateKey3, rsaPublicKey3.id);
2030
+ const unsafeContext = createRequestContext({
2031
+ federation,
2032
+ request: unsafeRequest,
2033
+ url: new URL(unsafeRequest.url),
2034
+ data: void 0,
2035
+ documentLoader: mockDocumentLoader
2036
+ });
2037
+ response = await handleInbox(unsafeRequest, {
2038
+ recipient: null,
2039
+ context: unsafeContext,
2040
+ inboxContextFactory(_activity) {
2041
+ return createInboxContext({
2042
+ ...unsafeContext,
2043
+ clone: void 0
2044
+ });
2045
+ },
2046
+ ...inboxOptions
2047
+ });
2048
+ assertEquals(response.status, 202);
2049
+ const unsafeLdRequest = new Request("https://example.com/", {
2050
+ method: "POST",
2051
+ body: JSON.stringify(await signJsonLd(unsafeJson, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
2052
+ });
2053
+ const unsafeLdContext = createRequestContext({
2054
+ federation,
2055
+ request: unsafeLdRequest,
2056
+ url: new URL(unsafeLdRequest.url),
2057
+ data: void 0,
2058
+ documentLoader: mockDocumentLoader
2059
+ });
2060
+ response = await handleInbox(unsafeLdRequest, {
2061
+ recipient: null,
2062
+ context: unsafeLdContext,
2063
+ inboxContextFactory(_activity) {
2064
+ return createInboxContext({
2065
+ ...unsafeLdContext,
2066
+ clone: void 0
2067
+ });
2068
+ },
2069
+ ...inboxOptions
2070
+ });
2071
+ assertEquals(response.status, 400);
2072
+ const signedInvalidRequest = await signRequest(new Request("https://example.com/", {
2073
+ method: "POST",
2074
+ body: JSON.stringify({
2075
+ "@context": [
2076
+ "https://www.w3.org/ns/activitystreams",
2077
+ true,
2078
+ 23
2079
+ ],
2080
+ type: "Create",
2081
+ object: {
2082
+ type: "Note",
2083
+ content: "Hello, world!"
2084
+ },
2085
+ actor: "https://example.com/users/alice"
2086
+ })
2087
+ }), rsaPrivateKey3, rsaPublicKey3.id);
2088
+ const signedInvalidContext = createRequestContext({
2089
+ federation,
2090
+ request: signedInvalidRequest,
2091
+ url: new URL(signedInvalidRequest.url),
2092
+ data: void 0,
2093
+ documentLoader: mockDocumentLoader
2094
+ });
2095
+ response = await handleInbox(signedInvalidRequest, {
2096
+ recipient: null,
2097
+ context: signedContext,
2098
+ inboxContextFactory(_activity) {
2099
+ return createInboxContext({
2100
+ ...signedInvalidContext,
2101
+ clone: void 0
2102
+ });
2103
+ },
2104
+ ...inboxOptions
2105
+ });
2106
+ assertEquals(onNotFoundCalled, null);
2107
+ assertEquals(response.status, 400);
2108
+ });
2109
+ test("handleInbox() preserves the raw signed payload for inboxContextFactory", async () => {
2110
+ const federation = createFederation({ kv: new MemoryKvStore() });
2111
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2112
+ const sourceContextLoader = async (resource) => {
2113
+ const url = new URL(resource).href;
2114
+ if (url === remoteContextUrl) return {
2115
+ contextUrl: null,
2116
+ documentUrl: url,
2117
+ document: { "@context": { ext: "https://example.com/ext" } }
2118
+ };
2119
+ return await mockDocumentLoader(url);
2120
+ };
2121
+ const signed = await signJsonLd({
2122
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2123
+ id: "https://example.com/activities/preserve-raw",
2124
+ type: "Create",
2125
+ actor: "https://example.com/person2",
2126
+ ext: "preserve-me",
2127
+ object: {
2128
+ id: "https://example.com/notes/preserve-raw",
2129
+ type: "Note",
2130
+ attributedTo: "https://example.com/person2",
2131
+ content: "Hello, world!"
2132
+ }
2133
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
2134
+ const request = new Request("https://example.com/", {
2135
+ method: "POST",
2136
+ body: JSON.stringify(signed)
2137
+ });
2138
+ const context = createRequestContext({
2139
+ federation,
2140
+ request,
2141
+ url: new URL(request.url),
2142
+ data: void 0,
2143
+ documentLoader: mockDocumentLoader,
2144
+ contextLoader: sourceContextLoader
2145
+ });
2146
+ let receivedRaw = null;
2147
+ let receivedTyped = null;
2148
+ const inboxListeners = new InboxListenerSet();
2149
+ inboxListeners.add(Create, (ctx, activity) => {
2150
+ receivedRaw = ctx.activity;
2151
+ receivedTyped = activity;
2152
+ });
2153
+ const response = await handleInbox(request, {
2154
+ recipient: "someone",
2155
+ context,
2156
+ inboxContextFactory(recipient, activity, activityId, activityType) {
2157
+ return {
2158
+ ...createInboxContext({
2159
+ ...context,
2160
+ clone: void 0,
2161
+ recipient
2162
+ }),
2163
+ activity,
2164
+ activityId,
2165
+ activityType
2166
+ };
2167
+ },
2168
+ kv: new MemoryKvStore(),
2169
+ kvPrefixes: {
2170
+ activityIdempotence: ["_fedify", "activityIdempotence"],
2171
+ publicKey: ["_fedify", "publicKey"]
2172
+ },
2173
+ actorDispatcher: (_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null,
2174
+ inboxListeners,
2175
+ onNotFound: () => new Response("Not found", { status: 404 }),
2176
+ signatureTimeWindow: { minutes: 5 },
2177
+ skipSignatureVerification: false
2178
+ });
2179
+ assertEquals([response.status, await response.text()], [202, ""]);
2180
+ assertEquals(receivedRaw, signed);
2181
+ const delivered = receivedTyped;
2182
+ assert(delivered != null);
2183
+ assertEquals(delivered.id?.href, "https://example.com/activities/preserve-raw");
2184
+ });
2185
+ test("handleInbox() enqueues normalizedActivity for LD-signed inbox work", async () => {
2186
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2187
+ const sourceContextLoader = async (resource) => {
2188
+ const url = new URL(resource).href;
2189
+ if (url === remoteContextUrl) return {
2190
+ contextUrl: null,
2191
+ documentUrl: url,
2192
+ document: { "@context": { ext: "https://example.com/ext" } }
2193
+ };
2194
+ return await mockDocumentLoader(url);
2195
+ };
2196
+ let queuedMessage = null;
2197
+ const queue = {
2198
+ enqueue(message) {
2199
+ queuedMessage = message;
2200
+ return Promise.resolve();
2201
+ },
2202
+ async listen() {}
2203
+ };
2204
+ const federation = createFederation({
2205
+ kv: new MemoryKvStore(),
2206
+ queue
2207
+ });
2208
+ const signed = await signJsonLd({
2209
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2210
+ id: "https://example.com/activities/enqueued-normalized",
2211
+ type: "Create",
2212
+ actor: "https://example.com/person2",
2213
+ ext: "preserve-me",
2214
+ object: {
2215
+ id: "https://example.com/notes/enqueued-normalized",
2216
+ type: "Note",
2217
+ attributedTo: "https://example.com/person2",
2218
+ content: "Hello, world!"
2219
+ }
2220
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
2221
+ const request = new Request("https://example.com/", {
2222
+ method: "POST",
2223
+ body: JSON.stringify(signed)
2224
+ });
2225
+ const context = createRequestContext({
2226
+ federation,
2227
+ request,
2228
+ url: new URL(request.url),
2229
+ data: void 0,
2230
+ documentLoader: mockDocumentLoader,
2231
+ contextLoader: sourceContextLoader
2232
+ });
2233
+ const response = await handleInbox(request, {
2234
+ recipient: "someone",
2235
+ context,
2236
+ inboxContextFactory(recipient, activity, activityId, activityType) {
2237
+ return {
2238
+ ...createInboxContext({
2239
+ ...context,
2240
+ clone: void 0,
2241
+ recipient
2242
+ }),
2243
+ activity,
2244
+ activityId,
2245
+ activityType
2246
+ };
2247
+ },
2248
+ kv: new MemoryKvStore(),
2249
+ kvPrefixes: {
2250
+ activityIdempotence: ["_fedify", "activityIdempotence"],
2251
+ publicKey: ["_fedify", "publicKey"]
2252
+ },
2253
+ queue,
2254
+ actorDispatcher: (_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null,
2255
+ onNotFound: () => new Response("Not found", { status: 404 }),
2256
+ signatureTimeWindow: { minutes: 5 },
2257
+ skipSignatureVerification: false
2258
+ });
2259
+ assertEquals([response.status, await response.text()], [202, "Activity is enqueued."]);
2260
+ const enqueued = queuedMessage;
2261
+ assert(enqueued != null);
2262
+ const inboxMessage = enqueued;
2263
+ assertEquals(inboxMessage.activity, signed);
2264
+ assertEquals(inboxMessage.normalizedActivity, await compactJsonLd(signed, sourceContextLoader));
2265
+ assertEquals(inboxMessage.ldSignatureVerified, true);
2266
+ });
2267
+ test("handleInbox() caches normalizedActivity for queued signature-bearing fallback traffic", async () => {
2268
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2269
+ let queuedMessage = null;
2270
+ const queue = {
2271
+ enqueue(message) {
2272
+ queuedMessage = message;
2273
+ return Promise.resolve();
2274
+ },
2275
+ async listen() {}
2276
+ };
2277
+ const federation = createFederation({
2278
+ kv: new MemoryKvStore(),
2279
+ queue
2280
+ });
2281
+ const sourceContextLoader = async (resource) => {
2282
+ const url = new URL(resource).href;
2283
+ if (url === remoteContextUrl) return {
2284
+ contextUrl: null,
2285
+ documentUrl: url,
2286
+ document: { "@context": { ext: "https://example.com/ext" } }
2287
+ };
2288
+ return await mockDocumentLoader(url);
2289
+ };
2290
+ const unsignedBody = {
2291
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2292
+ id: "https://example.com/activities/non-lds-queued-signature",
2293
+ type: "Create",
2294
+ actor: "https://example.com/person2",
2295
+ ext: "preserve-me",
2296
+ object: {
2297
+ id: "https://example.com/notes/non-lds-queued-signature",
2298
+ type: "Note",
2299
+ attributedTo: "https://example.com/person2",
2300
+ content: "Hello, world!"
2301
+ },
2302
+ signature: {
2303
+ type: "RsaSignature2017",
2304
+ creator: "not a url",
2305
+ created: "2024-09-12T16:50:46Z",
2306
+ signatureValue: "Zm9v"
2307
+ }
2308
+ };
2309
+ const request = await signRequest(new Request("https://example.com/", {
2310
+ method: "POST",
2311
+ body: JSON.stringify(unsignedBody)
2312
+ }), rsaPrivateKey3, rsaPublicKey3.id);
2313
+ const context = createRequestContext({
2314
+ federation,
2315
+ request,
2316
+ url: new URL(request.url),
2317
+ data: void 0,
2318
+ documentLoader: mockDocumentLoader,
2319
+ contextLoader: sourceContextLoader
2320
+ });
2321
+ const response = await handleInbox(request, {
2322
+ recipient: "someone",
2323
+ context,
2324
+ inboxContextFactory(recipient, activity, activityId, activityType) {
2325
+ return {
2326
+ ...createInboxContext({
2327
+ ...context,
2328
+ clone: void 0,
2329
+ recipient
2330
+ }),
2331
+ activity,
2332
+ activityId,
2333
+ activityType
2334
+ };
2335
+ },
2336
+ kv: new MemoryKvStore(),
2337
+ kvPrefixes: {
2338
+ activityIdempotence: ["_fedify", "activityIdempotence"],
2339
+ publicKey: ["_fedify", "publicKey"]
2340
+ },
2341
+ queue,
2342
+ actorDispatcher: (_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null,
2343
+ onNotFound: () => new Response("Not found", { status: 404 }),
2344
+ signatureTimeWindow: { minutes: 5 },
2345
+ skipSignatureVerification: false
2346
+ });
2347
+ assertEquals([response.status, await response.text()], [202, "Activity is enqueued."]);
2348
+ if (queuedMessage == null) throw new Error("Inbox message not queued.");
2349
+ const inboxMessage = queuedMessage;
2350
+ assertEquals(inboxMessage, {
2351
+ type: "inbox",
2352
+ id: inboxMessage.id,
2353
+ baseUrl: "https://example.com",
2354
+ activity: unsignedBody,
2355
+ normalizedActivity: await compactJsonLd(unsignedBody, sourceContextLoader),
2356
+ ldSignatureVerified: false,
2357
+ started: inboxMessage.started,
2358
+ attempt: 0,
2359
+ identifier: "someone",
2360
+ traceContext: inboxMessage.traceContext
2361
+ });
1045
2362
  });
1046
2363
  test("respondWithObject()", async () => {
1047
2364
  const response = await respondWithObject(new Note({