@fedify/fedify 2.3.0-dev.1137 → 2.3.0-dev.1150

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 (68) hide show
  1. package/dist/{builder-BCkBXxky.mjs → builder-Bjm1Jq9n.mjs} +2 -2
  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-DI2gRbyN.d.cts → context-CRXCkTM6.d.cts} +48 -6
  6. package/dist/{context-DCtsSHDv.d.ts → context-MgCh7YGu.d.ts} +48 -6
  7. package/dist/{deno-B_9yJW3w.mjs → deno-CKFE6Uya.mjs} +1 -1
  8. package/dist/{docloader-BT89tyFr.mjs → docloader-B-ZE1cZf.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1363 -44
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +60 -1
  13. package/dist/federation/middleware.test.mjs +1667 -163
  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 +8 -8
  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 +147 -2
  23. package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
  24. package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
  25. package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
  26. package/dist/{http-CWoeyogl.cjs → http-DQYEA7AZ.cjs} +53 -1
  27. package/dist/{http-CToqG5ap.js → http-WbS1gKzr.js} +48 -2
  28. package/dist/{http-Cyx5SNuu.mjs → http-vHCgbhTg.mjs} +3 -3
  29. package/dist/{key-CkkMJBjF.mjs → key-N0zP_oJA.mjs} +2 -2
  30. package/dist/{kv-cache-CuCn2xvM.js → kv-cache-DM2O-Yjy.js} +1 -1
  31. package/dist/{kv-cache-DuEwFYcN.cjs → kv-cache-Dsg_bi4N.cjs} +1 -1
  32. package/dist/{kv-cache-VHFP42vY.mjs → kv-cache-GXXZEemD.mjs} +1 -1
  33. package/dist/{ld-k8yqD2a-.mjs → ld-BwKhquPx.mjs} +302 -6
  34. package/dist/{metrics-iRBg8jTk.mjs → metrics-7Vy9FvEw.mjs} +48 -2
  35. package/dist/{middleware-D7FrhN9q.js → middleware-BscgvU-m.js} +496 -115
  36. package/dist/{middleware-BWLUrbS9.cjs → middleware-D_iXrYHJ.cjs} +497 -115
  37. package/dist/{middleware-CztxpARM.mjs → middleware-Db1_qAFG.mjs} +1 -1
  38. package/dist/{middleware-DQEgdr83.mjs → middleware-ZuUcO0t1.mjs} +416 -124
  39. package/dist/{mod-C504qevA.d.cts → mod-C7HOzGqH.d.cts} +11 -2
  40. package/dist/{mod-wYfuXeDE.d.ts → mod-CpQHB3Ys.d.ts} +11 -2
  41. package/dist/mod.cjs +4 -4
  42. package/dist/mod.d.cts +2 -2
  43. package/dist/mod.d.ts +2 -2
  44. package/dist/mod.js +4 -4
  45. package/dist/nodeinfo/handler.test.mjs +1 -1
  46. package/dist/{owner-nmXdvXpc.mjs → owner-FD0H_vpj.mjs} +2 -2
  47. package/dist/{proof-CcsIJLTn.cjs → proof-CYK8T8IS.cjs} +353 -3
  48. package/dist/{proof-NRmtrTDu.js → proof-I3EokKN-.js} +300 -4
  49. package/dist/{proof-DpwO1T4S.mjs → proof-V_lafPmA.mjs} +3 -3
  50. package/dist/{send-DvX2tYyZ.mjs → send-Cc2_10tF.mjs} +3 -3
  51. package/dist/sig/http.test.mjs +2 -2
  52. package/dist/sig/key.test.mjs +1 -1
  53. package/dist/sig/ld.test.mjs +558 -2
  54. package/dist/sig/mod.cjs +2 -2
  55. package/dist/sig/mod.js +2 -2
  56. package/dist/sig/owner.test.mjs +1 -1
  57. package/dist/sig/proof.test.mjs +1 -1
  58. package/dist/temporal-BkmBfs__.mjs +95 -0
  59. package/dist/testing/mod.d.mts +48 -6
  60. package/dist/utils/docloader.test.mjs +2 -2
  61. package/dist/utils/kv-cache.test.mjs +1 -1
  62. package/dist/utils/mod.cjs +1 -1
  63. package/dist/utils/mod.js +1 -1
  64. package/package.json +6 -6
  65. /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
  66. /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
  67. /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
  68. /package/dist/{retry-v_sGLH1d.mjs → retry-_VvV0h9f.mjs} +0 -0
@@ -4,14 +4,15 @@ globalThis.addEventListener = () => {};
4
4
  import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-DVoTs_wM.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
6
  import "../std__assert-BBjXFNOb.mjs";
7
- import { n as assertGreaterOrEqual } from "../assert_rejects-DN60FHPX.mjs";
7
+ import { n as assertGreaterOrEqual, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
8
  import { t as assertInstanceOf } from "../assert_instance_of-DBC5X09g.mjs";
9
9
  import { t as assert } from "../assert-OguE97r2.mjs";
10
10
  import { r as parseAcceptSignature } from "../accept-CceiKpCy.mjs";
11
- import { s as signRequest } from "../http-Cyx5SNuu.mjs";
11
+ import { s as signRequest } from "../http-vHCgbhTg.mjs";
12
12
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-C3kae-6B.mjs";
13
+ import { a as compactJsonLd, p as signJsonLd } from "../ld-BwKhquPx.mjs";
13
14
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
14
- import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-DQEgdr83.mjs";
15
+ import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-ZuUcO0t1.mjs";
15
16
  import { t as ActivityListenerSet } from "../activity-listener-tztVvlNb.mjs";
16
17
  import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
17
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -957,6 +958,11 @@ test("handleInbox()", async () => {
957
958
  if (identifier !== "someone") return null;
958
959
  return new Person({ name: "Someone" });
959
960
  };
961
+ const restrictiveContextLoader = async (resource) => {
962
+ const url = new URL(resource).href;
963
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
964
+ throw new Error(`Unexpected context: ${url}`);
965
+ };
960
966
  const inboxOptions = {
961
967
  kv: new MemoryKvStore(),
962
968
  kvPrefixes: {
@@ -971,83 +977,1081 @@ test("handleInbox()", async () => {
971
977
  };
972
978
  let response = await handleInbox(unsignedRequest, {
973
979
  recipient: null,
974
- context: unsignedContext,
980
+ context: unsignedContext,
981
+ inboxContextFactory(_activity) {
982
+ return createInboxContext({
983
+ ...unsignedContext,
984
+ clone: void 0
985
+ });
986
+ },
987
+ ...inboxOptions,
988
+ actorDispatcher: void 0
989
+ });
990
+ assertEquals(onNotFoundCalled, unsignedRequest);
991
+ assertEquals(response.status, 404);
992
+ onNotFoundCalled = null;
993
+ response = await handleInbox(unsignedRequest, {
994
+ recipient: "nobody",
995
+ context: unsignedContext,
996
+ inboxContextFactory(_activity) {
997
+ return createInboxContext({
998
+ ...unsignedContext,
999
+ clone: void 0,
1000
+ recipient: "nobody"
1001
+ });
1002
+ },
1003
+ ...inboxOptions
1004
+ });
1005
+ assertEquals(onNotFoundCalled, unsignedRequest);
1006
+ assertEquals(response.status, 404);
1007
+ onNotFoundCalled = null;
1008
+ response = await handleInbox(unsignedRequest, {
1009
+ recipient: null,
1010
+ context: unsignedContext,
1011
+ inboxContextFactory(_activity) {
1012
+ return createInboxContext({
1013
+ ...unsignedContext,
1014
+ clone: void 0
1015
+ });
1016
+ },
1017
+ ...inboxOptions
1018
+ });
1019
+ assertEquals(onNotFoundCalled, null);
1020
+ assertEquals(response.status, 401);
1021
+ response = await handleInbox(unsignedRequest, {
1022
+ recipient: "someone",
1023
+ context: unsignedContext,
1024
+ inboxContextFactory(_activity) {
1025
+ return createInboxContext({
1026
+ ...unsignedContext,
1027
+ clone: void 0,
1028
+ recipient: "someone"
1029
+ });
1030
+ },
1031
+ ...inboxOptions
1032
+ });
1033
+ assertEquals(onNotFoundCalled, null);
1034
+ assertEquals(response.status, 401);
1035
+ const malformedProofCreatedRequest = new Request("https://example.com/", {
1036
+ method: "POST",
1037
+ body: JSON.stringify({
1038
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
1039
+ id: "https://example.com/activities/invalid-proof-created",
1040
+ type: "Create",
1041
+ actor: "https://example.com/person2",
1042
+ object: {
1043
+ id: "https://example.com/notes/invalid-proof-created",
1044
+ type: "Note",
1045
+ attributedTo: "https://example.com/person2",
1046
+ content: "Hello, world!"
1047
+ },
1048
+ proof: {
1049
+ type: "DataIntegrityProof",
1050
+ cryptosuite: "eddsa-jcs-2022",
1051
+ verificationMethod: "https://example.com/person2#main-key",
1052
+ proofPurpose: "assertionMethod",
1053
+ created: { "@value": "not-a-date" },
1054
+ proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
1055
+ }
1056
+ })
1057
+ });
1058
+ const malformedProofCreatedContext = createRequestContext({
1059
+ federation,
1060
+ request: malformedProofCreatedRequest,
1061
+ url: new URL(malformedProofCreatedRequest.url),
1062
+ data: void 0,
1063
+ documentLoader: mockDocumentLoader,
1064
+ contextLoader: mockDocumentLoader
1065
+ });
1066
+ response = await handleInbox(malformedProofCreatedRequest, {
1067
+ recipient: null,
1068
+ context: malformedProofCreatedContext,
1069
+ inboxContextFactory(_activity) {
1070
+ return createInboxContext({
1071
+ ...malformedProofCreatedContext,
1072
+ clone: void 0
1073
+ });
1074
+ },
1075
+ ...inboxOptions
1076
+ });
1077
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1078
+ onNotFoundCalled = null;
1079
+ const signedRequest = await signRequest(unsignedRequest.clone(), rsaPrivateKey3, rsaPublicKey3.id);
1080
+ const signedContext = createRequestContext({
1081
+ federation,
1082
+ request: signedRequest,
1083
+ url: new URL(signedRequest.url),
1084
+ data: void 0,
1085
+ documentLoader: mockDocumentLoader
1086
+ });
1087
+ response = await handleInbox(signedRequest, {
1088
+ recipient: null,
1089
+ context: signedContext,
1090
+ inboxContextFactory(_activity) {
1091
+ return createInboxContext({
1092
+ ...unsignedContext,
1093
+ clone: void 0
1094
+ });
1095
+ },
1096
+ ...inboxOptions
1097
+ });
1098
+ assertEquals(onNotFoundCalled, null);
1099
+ assertEquals([response.status, await response.text()], [202, ""]);
1100
+ const ldSignedRequest = new Request("https://example.com/", {
1101
+ method: "POST",
1102
+ body: JSON.stringify(await signJsonLd({
1103
+ "@context": [
1104
+ "https://www.w3.org/ns/activitystreams",
1105
+ "https://w3id.org/identity/v1",
1106
+ "https://w3id.org/security/v1",
1107
+ "https://w3id.org/security/data-integrity/v1"
1108
+ ],
1109
+ id: "https://example.com/activities/ld-signed",
1110
+ type: "Create",
1111
+ actor: "https://example.com/person2",
1112
+ object: {
1113
+ id: "https://example.com/notes/ld-signed",
1114
+ type: "Note",
1115
+ attributedTo: "https://example.com/person2",
1116
+ content: "Hello, world!"
1117
+ }
1118
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
1119
+ });
1120
+ const ldSignedContext = createRequestContext({
1121
+ federation,
1122
+ request: ldSignedRequest,
1123
+ url: new URL(ldSignedRequest.url),
1124
+ data: void 0,
1125
+ documentLoader: mockDocumentLoader,
1126
+ contextLoader: restrictiveContextLoader
1127
+ });
1128
+ response = await handleInbox(ldSignedRequest, {
1129
+ recipient: null,
1130
+ context: ldSignedContext,
1131
+ inboxContextFactory(_activity) {
1132
+ return createInboxContext({
1133
+ ...ldSignedContext,
1134
+ clone: void 0
1135
+ });
1136
+ },
1137
+ ...inboxOptions
1138
+ });
1139
+ assertEquals(onNotFoundCalled, null);
1140
+ assertEquals([response.status, await response.text()], [202, ""]);
1141
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1142
+ let failRemoteContextOnce = true;
1143
+ const flakyContextLoader = async (resource) => {
1144
+ const url = new URL(resource).href;
1145
+ if (url === remoteContextUrl) {
1146
+ if (failRemoteContextOnce) {
1147
+ failRemoteContextOnce = false;
1148
+ throw new Error(`Unexpected context: ${url}`);
1149
+ }
1150
+ return {
1151
+ contextUrl: null,
1152
+ documentUrl: url,
1153
+ document: { "@context": { ext: "https://example.com/ext" } }
1154
+ };
1155
+ }
1156
+ return await mockDocumentLoader(url);
1157
+ };
1158
+ const httpSignedLdBody = {
1159
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1160
+ id: "https://example.com/activities/http-signed-ld",
1161
+ type: "Create",
1162
+ actor: "https://example.com/person2",
1163
+ ext: "preserve-me",
1164
+ object: {
1165
+ id: "https://example.com/notes/http-signed-ld",
1166
+ type: "Note",
1167
+ attributedTo: "https://example.com/person2",
1168
+ content: "Hello, world!"
1169
+ },
1170
+ signature: {
1171
+ type: "RsaSignature2017",
1172
+ creator: rsaPublicKey3.id.href,
1173
+ created: "2024-01-01T00:00:00Z",
1174
+ signatureValue: "bogus"
1175
+ }
1176
+ };
1177
+ const httpSignedLdRequest = await signRequest(new Request("https://example.com/", {
1178
+ method: "POST",
1179
+ body: JSON.stringify(httpSignedLdBody)
1180
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1181
+ const httpSignedLdContext = createRequestContext({
1182
+ federation,
1183
+ request: httpSignedLdRequest,
1184
+ url: new URL(httpSignedLdRequest.url),
1185
+ data: void 0,
1186
+ documentLoader: mockDocumentLoader,
1187
+ contextLoader: flakyContextLoader
1188
+ });
1189
+ response = await handleInbox(httpSignedLdRequest, {
1190
+ recipient: null,
1191
+ context: httpSignedLdContext,
1192
+ inboxContextFactory(_activity) {
1193
+ return createInboxContext({
1194
+ ...httpSignedLdContext,
1195
+ clone: void 0
1196
+ });
1197
+ },
1198
+ ...inboxOptions
1199
+ });
1200
+ assertEquals(onNotFoundCalled, null);
1201
+ assertEquals([response.status, await response.text()], [202, ""]);
1202
+ const ldSignedOnlyBody = await signJsonLd({
1203
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1204
+ id: "https://example.com/activities/ld-only-transient",
1205
+ type: "Create",
1206
+ actor: "https://example.com/person2",
1207
+ ext: "preserve-me",
1208
+ object: {
1209
+ id: "https://example.com/notes/ld-only-transient",
1210
+ type: "Note",
1211
+ attributedTo: "https://example.com/person2",
1212
+ content: "Hello, world!"
1213
+ }
1214
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1215
+ const url = new URL(resource).href;
1216
+ if (url === remoteContextUrl) return {
1217
+ contextUrl: null,
1218
+ documentUrl: url,
1219
+ document: { "@context": { ext: "https://example.com/ext" } }
1220
+ };
1221
+ return await mockDocumentLoader(url);
1222
+ } });
1223
+ const malformedTemporalLdSignedBody = await signJsonLd({
1224
+ "@context": "https://www.w3.org/ns/activitystreams",
1225
+ id: "https://example.com/activities/ld-only-invalid-published",
1226
+ type: "Create",
1227
+ actor: "https://example.com/person2",
1228
+ published: { "@value": "not-a-date" },
1229
+ object: {
1230
+ id: "https://example.com/notes/ld-only-invalid-published",
1231
+ type: "Note",
1232
+ attributedTo: "https://example.com/person2",
1233
+ content: "Hello, world!"
1234
+ }
1235
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1236
+ const malformedTemporalLdSignedRequest = new Request("https://example.com/", {
1237
+ method: "POST",
1238
+ body: JSON.stringify(malformedTemporalLdSignedBody)
1239
+ });
1240
+ const malformedTemporalLdSignedContext = createRequestContext({
1241
+ federation,
1242
+ request: malformedTemporalLdSignedRequest,
1243
+ url: new URL(malformedTemporalLdSignedRequest.url),
1244
+ data: void 0,
1245
+ documentLoader: mockDocumentLoader,
1246
+ contextLoader: mockDocumentLoader
1247
+ });
1248
+ response = await handleInbox(malformedTemporalLdSignedRequest, {
1249
+ recipient: null,
1250
+ context: malformedTemporalLdSignedContext,
1251
+ inboxContextFactory(_activity) {
1252
+ return createInboxContext({
1253
+ ...malformedTemporalLdSignedContext,
1254
+ clone: void 0
1255
+ });
1256
+ },
1257
+ ...inboxOptions
1258
+ });
1259
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1260
+ const malformedClosedLdSignedBody = await signJsonLd({
1261
+ "@context": "https://www.w3.org/ns/activitystreams",
1262
+ id: "https://example.com/questions/ld-only-invalid-closed",
1263
+ type: "Question",
1264
+ closed: "2024-02-31T00:00:00Z"
1265
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1266
+ const malformedClosedLdSignedRequest = new Request("https://example.com/", {
1267
+ method: "POST",
1268
+ body: JSON.stringify(malformedClosedLdSignedBody)
1269
+ });
1270
+ const malformedClosedLdSignedContext = createRequestContext({
1271
+ federation,
1272
+ request: malformedClosedLdSignedRequest,
1273
+ url: new URL(malformedClosedLdSignedRequest.url),
1274
+ data: void 0,
1275
+ documentLoader: mockDocumentLoader,
1276
+ contextLoader: mockDocumentLoader
1277
+ });
1278
+ response = await handleInbox(malformedClosedLdSignedRequest, {
1279
+ recipient: null,
1280
+ context: malformedClosedLdSignedContext,
1281
+ inboxContextFactory(_activity) {
1282
+ return createInboxContext({
1283
+ ...malformedClosedLdSignedContext,
1284
+ clone: void 0
1285
+ });
1286
+ },
1287
+ ...inboxOptions
1288
+ });
1289
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1290
+ const malformedIriHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1291
+ method: "POST",
1292
+ body: JSON.stringify({
1293
+ "@context": "https://www.w3.org/ns/activitystreams",
1294
+ id: "http://[",
1295
+ type: "Create",
1296
+ actor: "https://example.com/person2",
1297
+ object: {
1298
+ id: "https://example.com/notes/http-signed-invalid-iri",
1299
+ type: "Note",
1300
+ attributedTo: "https://example.com/person2",
1301
+ content: "Hello, world!"
1302
+ }
1303
+ })
1304
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1305
+ const malformedIriHttpSignedContext = createRequestContext({
1306
+ federation,
1307
+ request: malformedIriHttpSignedRequest,
1308
+ url: new URL(malformedIriHttpSignedRequest.url),
1309
+ data: void 0,
1310
+ documentLoader: mockDocumentLoader,
1311
+ contextLoader: mockDocumentLoader
1312
+ });
1313
+ response = await handleInbox(malformedIriHttpSignedRequest, {
1314
+ recipient: null,
1315
+ context: malformedIriHttpSignedContext,
1316
+ inboxContextFactory(_activity) {
1317
+ return createInboxContext({
1318
+ ...malformedIriHttpSignedContext,
1319
+ clone: void 0
1320
+ });
1321
+ },
1322
+ ...inboxOptions
1323
+ });
1324
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1325
+ const ldSignedOnlyRequest = new Request("https://example.com/", {
1326
+ method: "POST",
1327
+ body: JSON.stringify(ldSignedOnlyBody)
1328
+ });
1329
+ const ldSignedOnlyContext = createRequestContext({
1330
+ federation,
1331
+ request: ldSignedOnlyRequest,
1332
+ url: new URL(ldSignedOnlyRequest.url),
1333
+ data: void 0,
1334
+ documentLoader: mockDocumentLoader,
1335
+ contextLoader: async (resource) => {
1336
+ const url = new URL(resource).href;
1337
+ if (url === remoteContextUrl) throw new Error(`Unexpected context: ${url}`);
1338
+ return await mockDocumentLoader(url);
1339
+ }
1340
+ });
1341
+ await assertRejects(() => handleInbox(ldSignedOnlyRequest, {
1342
+ recipient: null,
1343
+ context: ldSignedOnlyContext,
1344
+ inboxContextFactory(_activity) {
1345
+ return createInboxContext({
1346
+ ...ldSignedOnlyContext,
1347
+ clone: void 0
1348
+ });
1349
+ },
1350
+ ...inboxOptions
1351
+ }), Error);
1352
+ failRemoteContextOnce = true;
1353
+ const invalidHttpFallbackRequest = new Request("https://example.com/", {
1354
+ method: "POST",
1355
+ body: JSON.stringify(ldSignedOnlyBody),
1356
+ headers: { Signature: "bogus" }
1357
+ });
1358
+ const invalidHttpFallbackContext = createRequestContext({
1359
+ federation,
1360
+ request: invalidHttpFallbackRequest,
1361
+ url: new URL(invalidHttpFallbackRequest.url),
1362
+ data: void 0,
1363
+ documentLoader: mockDocumentLoader,
1364
+ contextLoader: flakyContextLoader
1365
+ });
1366
+ await assertRejects(() => handleInbox(invalidHttpFallbackRequest, {
1367
+ recipient: null,
1368
+ context: invalidHttpFallbackContext,
1369
+ inboxContextFactory(_activity) {
1370
+ return createInboxContext({
1371
+ ...invalidHttpFallbackContext,
1372
+ clone: void 0
1373
+ });
1374
+ },
1375
+ ...inboxOptions
1376
+ }), Error);
1377
+ const transientKeyContextUrl = "https://remote.example/contexts/key";
1378
+ const transientCreatorUrl = "https://remote.example/keys/transient#main-key";
1379
+ const verificationFailureLdSignedBody = await signJsonLd({
1380
+ "@context": "https://www.w3.org/ns/activitystreams",
1381
+ id: "https://example.com/activities/ld-key-fetch-transient",
1382
+ type: "Create",
1383
+ actor: "https://example.com/person2",
1384
+ object: {
1385
+ id: "https://example.com/notes/ld-key-fetch-transient",
1386
+ type: "Note",
1387
+ attributedTo: "https://example.com/person2",
1388
+ content: "Hello, world!"
1389
+ }
1390
+ }, rsaPrivateKey3, new URL(transientCreatorUrl), { contextLoader: mockDocumentLoader });
1391
+ const verificationFailureLdSignedRequest = new Request("https://example.com/", {
1392
+ method: "POST",
1393
+ body: JSON.stringify(verificationFailureLdSignedBody),
1394
+ headers: { Signature: "bogus" }
1395
+ });
1396
+ const verificationFailureLdSignedContext = createRequestContext({
1397
+ federation,
1398
+ request: verificationFailureLdSignedRequest,
1399
+ url: new URL(verificationFailureLdSignedRequest.url),
1400
+ data: void 0,
1401
+ documentLoader: async (resource) => {
1402
+ if (resource === transientCreatorUrl) return {
1403
+ contextUrl: null,
1404
+ documentUrl: resource,
1405
+ document: {
1406
+ "@context": [transientKeyContextUrl],
1407
+ id: resource
1408
+ }
1409
+ };
1410
+ return await mockDocumentLoader(new URL(resource).href);
1411
+ },
1412
+ contextLoader: async (resource) => {
1413
+ if (resource === transientKeyContextUrl) throw new Error(`Transient key context failure: ${resource}`);
1414
+ return await mockDocumentLoader(new URL(resource).href);
1415
+ }
1416
+ });
1417
+ response = await handleInbox(verificationFailureLdSignedRequest, {
1418
+ recipient: null,
1419
+ context: verificationFailureLdSignedContext,
1420
+ inboxContextFactory(_activity) {
1421
+ return createInboxContext({
1422
+ ...verificationFailureLdSignedContext,
1423
+ clone: void 0
1424
+ });
1425
+ },
1426
+ ...inboxOptions
1427
+ });
1428
+ assertEquals([response.status, await response.text()], [401, "Failed to verify the request signature."]);
1429
+ failRemoteContextOnce = true;
1430
+ const deferredMalformedTemporalLdSignedBody = await signJsonLd({
1431
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1432
+ id: "https://example.com/activities/deferred-invalid-published",
1433
+ type: "Create",
1434
+ actor: "https://example.com/person2",
1435
+ ext: "preserve-me",
1436
+ published: { "@value": "not-a-date" },
1437
+ object: {
1438
+ id: "https://example.com/notes/deferred-invalid-published",
1439
+ type: "Note",
1440
+ attributedTo: "https://example.com/person2",
1441
+ content: "Hello, world!"
1442
+ }
1443
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1444
+ const url = new URL(resource).href;
1445
+ if (url === remoteContextUrl) return {
1446
+ contextUrl: null,
1447
+ documentUrl: url,
1448
+ document: { "@context": { ext: "https://example.com/ext" } }
1449
+ };
1450
+ return await mockDocumentLoader(url);
1451
+ } });
1452
+ const deferredMalformedTemporalLdSignedRequest = new Request("https://example.com/", {
1453
+ method: "POST",
1454
+ body: JSON.stringify(deferredMalformedTemporalLdSignedBody),
1455
+ headers: { Signature: "bogus" }
1456
+ });
1457
+ const deferredMalformedTemporalLdSignedContext = createRequestContext({
1458
+ federation,
1459
+ request: deferredMalformedTemporalLdSignedRequest,
1460
+ url: new URL(deferredMalformedTemporalLdSignedRequest.url),
1461
+ data: void 0,
1462
+ documentLoader: mockDocumentLoader,
1463
+ contextLoader: flakyContextLoader
1464
+ });
1465
+ response = await handleInbox(deferredMalformedTemporalLdSignedRequest, {
1466
+ recipient: null,
1467
+ context: deferredMalformedTemporalLdSignedContext,
1468
+ inboxContextFactory(_activity) {
1469
+ return createInboxContext({
1470
+ ...deferredMalformedTemporalLdSignedContext,
1471
+ clone: void 0
1472
+ });
1473
+ },
1474
+ ...inboxOptions
1475
+ });
1476
+ assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
1477
+ const malformedLdSignedRequest = new Request("https://example.com/", {
1478
+ method: "POST",
1479
+ body: JSON.stringify({
1480
+ ...ldSignedOnlyBody,
1481
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
1482
+ })
1483
+ });
1484
+ const malformedLdSignedContext = createRequestContext({
1485
+ federation,
1486
+ request: malformedLdSignedRequest,
1487
+ url: new URL(malformedLdSignedRequest.url),
1488
+ data: void 0,
1489
+ documentLoader: mockDocumentLoader,
1490
+ contextLoader: mockDocumentLoader
1491
+ });
1492
+ response = await handleInbox(malformedLdSignedRequest, {
1493
+ recipient: null,
1494
+ context: malformedLdSignedContext,
1495
+ inboxContextFactory(_activity) {
1496
+ return createInboxContext({
1497
+ ...malformedLdSignedContext,
1498
+ clone: void 0
1499
+ });
1500
+ },
1501
+ ...inboxOptions
1502
+ });
1503
+ assertEquals([response.status, await response.text()], [400, "Invalid JSON-LD."]);
1504
+ const dualSignedInvalidCreatorRequest = await signRequest(new Request("https://example.com/", {
1505
+ method: "POST",
1506
+ body: JSON.stringify({
1507
+ ...httpSignedLdBody,
1508
+ signature: {
1509
+ ...httpSignedLdBody.signature,
1510
+ creator: "not a url"
1511
+ }
1512
+ })
1513
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1514
+ const dualSignedInvalidCreatorContext = createRequestContext({
1515
+ federation,
1516
+ request: dualSignedInvalidCreatorRequest,
1517
+ url: new URL(dualSignedInvalidCreatorRequest.url),
1518
+ data: void 0,
1519
+ documentLoader: mockDocumentLoader,
1520
+ contextLoader: flakyContextLoader
1521
+ });
1522
+ response = await handleInbox(dualSignedInvalidCreatorRequest, {
1523
+ recipient: null,
1524
+ context: dualSignedInvalidCreatorContext,
1525
+ inboxContextFactory(_activity) {
1526
+ return createInboxContext({
1527
+ ...dualSignedInvalidCreatorContext,
1528
+ clone: void 0
1529
+ });
1530
+ },
1531
+ ...inboxOptions
1532
+ });
1533
+ assertEquals(onNotFoundCalled, null);
1534
+ assertEquals([response.status, await response.text()], [202, ""]);
1535
+ const invalidUrlHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1536
+ method: "POST",
1537
+ body: JSON.stringify({
1538
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1539
+ id: "https://example.com/activities/http-signed-invalid-context",
1540
+ type: "Create",
1541
+ actor: "https://example.com/person2",
1542
+ ext: "preserve-me",
1543
+ object: {
1544
+ id: "https://example.com/notes/http-signed-invalid-context",
1545
+ type: "Note",
1546
+ attributedTo: "https://example.com/person2",
1547
+ content: "Hello, world!"
1548
+ }
1549
+ })
1550
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1551
+ const invalidUrlHttpSignedContext = createRequestContext({
1552
+ federation,
1553
+ request: invalidUrlHttpSignedRequest,
1554
+ url: new URL(invalidUrlHttpSignedRequest.url),
1555
+ data: void 0,
1556
+ documentLoader: mockDocumentLoader,
1557
+ contextLoader: async (resource) => {
1558
+ const url = new URL(resource).href;
1559
+ if (url === remoteContextUrl) {
1560
+ const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
1561
+ error.name = "jsonld.InvalidUrl";
1562
+ error.details = {
1563
+ code: "loading remote context failed",
1564
+ url
1565
+ };
1566
+ throw error;
1567
+ }
1568
+ return await mockDocumentLoader(url);
1569
+ }
1570
+ });
1571
+ await assertRejects(() => handleInbox(invalidUrlHttpSignedRequest, {
1572
+ recipient: null,
1573
+ context: invalidUrlHttpSignedContext,
1574
+ inboxContextFactory(_activity) {
1575
+ return createInboxContext({
1576
+ ...invalidUrlHttpSignedContext,
1577
+ clone: void 0
1578
+ });
1579
+ },
1580
+ ...inboxOptions
1581
+ }), Error);
1582
+ const opaqueContextIdHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1583
+ method: "POST",
1584
+ body: JSON.stringify({
1585
+ "@context": ["app-context", "https://www.w3.org/ns/activitystreams"],
1586
+ id: "https://example.com/activities/http-signed-opaque-context",
1587
+ type: "Create",
1588
+ actor: "https://example.com/person2",
1589
+ object: {
1590
+ id: "https://example.com/notes/http-signed-opaque-context",
1591
+ type: "Note",
1592
+ attributedTo: "https://example.com/person2",
1593
+ content: "Hello, world!"
1594
+ }
1595
+ })
1596
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1597
+ const opaqueContextIdHttpSignedContext = createRequestContext({
1598
+ federation,
1599
+ request: opaqueContextIdHttpSignedRequest,
1600
+ url: new URL(opaqueContextIdHttpSignedRequest.url),
1601
+ data: void 0,
1602
+ documentLoader: mockDocumentLoader,
1603
+ contextLoader: async (resource) => {
1604
+ if (resource === "app-context") {
1605
+ const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
1606
+ error.name = "jsonld.InvalidUrl";
1607
+ error.details = {
1608
+ code: "loading remote context failed",
1609
+ url: resource
1610
+ };
1611
+ throw error;
1612
+ }
1613
+ return await mockDocumentLoader(new URL(resource).href);
1614
+ }
1615
+ });
1616
+ await assertRejects(() => handleInbox(opaqueContextIdHttpSignedRequest, {
1617
+ recipient: null,
1618
+ context: opaqueContextIdHttpSignedContext,
1619
+ inboxContextFactory(_activity) {
1620
+ return createInboxContext({
1621
+ ...opaqueContextIdHttpSignedContext,
1622
+ clone: void 0
1623
+ });
1624
+ },
1625
+ ...inboxOptions
1626
+ }), Error);
1627
+ const opaqueContextTypeErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1628
+ method: "POST",
1629
+ body: JSON.stringify({
1630
+ "@context": ["app:context", "https://www.w3.org/ns/activitystreams"],
1631
+ id: "https://example.com/activities/http-signed-opaque-typeerror",
1632
+ type: "Create",
1633
+ actor: "https://example.com/person2",
1634
+ object: {
1635
+ id: "https://example.com/notes/http-signed-opaque-typeerror",
1636
+ type: "Note",
1637
+ attributedTo: "https://example.com/person2",
1638
+ content: "Hello, world!"
1639
+ }
1640
+ })
1641
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1642
+ const opaqueContextTypeErrorHttpSignedContext = createRequestContext({
1643
+ federation,
1644
+ request: opaqueContextTypeErrorHttpSignedRequest,
1645
+ url: new URL(opaqueContextTypeErrorHttpSignedRequest.url),
1646
+ data: void 0,
1647
+ documentLoader: mockDocumentLoader,
1648
+ contextLoader: async (resource) => {
1649
+ if (resource === "app:context") throw new TypeError(`Invalid URL: ${resource}`);
1650
+ return await mockDocumentLoader(new URL(resource).href);
1651
+ }
1652
+ });
1653
+ await assertRejects(() => handleInbox(opaqueContextTypeErrorHttpSignedRequest, {
1654
+ recipient: null,
1655
+ context: opaqueContextTypeErrorHttpSignedContext,
1656
+ inboxContextFactory(_activity) {
1657
+ return createInboxContext({
1658
+ ...opaqueContextTypeErrorHttpSignedContext,
1659
+ clone: void 0
1660
+ });
1661
+ },
1662
+ ...inboxOptions
1663
+ }), Error);
1664
+ const networkPathContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1665
+ method: "POST",
1666
+ body: JSON.stringify({
1667
+ "@context": ["//cdn.example/ctx", "https://www.w3.org/ns/activitystreams"],
1668
+ id: "https://example.com/activities/http-signed-network-path-context",
1669
+ type: "Create",
1670
+ actor: "https://example.com/person2",
1671
+ object: {
1672
+ id: "https://example.com/notes/http-signed-network-path-context",
1673
+ type: "Note",
1674
+ attributedTo: "https://example.com/person2",
1675
+ content: "Hello, world!"
1676
+ }
1677
+ })
1678
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1679
+ const networkPathContextHttpSignedContext = createRequestContext({
1680
+ federation,
1681
+ request: networkPathContextHttpSignedRequest,
1682
+ url: new URL(networkPathContextHttpSignedRequest.url),
1683
+ data: void 0,
1684
+ documentLoader: mockDocumentLoader,
1685
+ contextLoader: async (resource) => {
1686
+ if (resource === "//cdn.example/ctx") {
1687
+ const error = /* @__PURE__ */ new Error(`Network-path context backend is unavailable: ${resource}`);
1688
+ error.name = "jsonld.InvalidUrl";
1689
+ error.details = {
1690
+ code: "loading remote context failed",
1691
+ url: resource
1692
+ };
1693
+ throw error;
1694
+ }
1695
+ return await mockDocumentLoader(new URL(resource).href);
1696
+ }
1697
+ });
1698
+ await assertRejects(() => handleInbox(networkPathContextHttpSignedRequest, {
1699
+ recipient: null,
1700
+ context: networkPathContextHttpSignedContext,
1701
+ inboxContextFactory(_activity) {
1702
+ return createInboxContext({
1703
+ ...networkPathContextHttpSignedContext,
1704
+ clone: void 0
1705
+ });
1706
+ },
1707
+ ...inboxOptions
1708
+ }), Error);
1709
+ const malformedNetworkPathContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1710
+ method: "POST",
1711
+ body: JSON.stringify({
1712
+ "@context": ["//[", "https://www.w3.org/ns/activitystreams"],
1713
+ id: "https://example.com/activities/http-signed-malformed-network-path-context",
1714
+ type: "Create",
1715
+ actor: "https://example.com/person2",
1716
+ object: {
1717
+ id: "https://example.com/notes/http-signed-malformed-network-path-context",
1718
+ type: "Note",
1719
+ attributedTo: "https://example.com/person2",
1720
+ content: "Hello, world!"
1721
+ }
1722
+ })
1723
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1724
+ const malformedNetworkPathContextHttpSignedContext = createRequestContext({
1725
+ federation,
1726
+ request: malformedNetworkPathContextHttpSignedRequest,
1727
+ url: new URL(malformedNetworkPathContextHttpSignedRequest.url),
1728
+ data: void 0,
1729
+ documentLoader: mockDocumentLoader,
1730
+ contextLoader: async (resource) => {
1731
+ if (resource === "//[") {
1732
+ const error = /* @__PURE__ */ new Error(`Malformed network-path context: ${resource}`);
1733
+ error.name = "jsonld.InvalidUrl";
1734
+ error.details = {
1735
+ code: "loading remote context failed",
1736
+ url: resource
1737
+ };
1738
+ throw error;
1739
+ }
1740
+ return await mockDocumentLoader(new URL(resource).href);
1741
+ }
1742
+ });
1743
+ response = await handleInbox(malformedNetworkPathContextHttpSignedRequest, {
1744
+ recipient: null,
1745
+ context: malformedNetworkPathContextHttpSignedContext,
1746
+ inboxContextFactory(_activity) {
1747
+ return createInboxContext({
1748
+ ...malformedNetworkPathContextHttpSignedContext,
1749
+ clone: void 0
1750
+ });
1751
+ },
1752
+ ...inboxOptions
1753
+ });
1754
+ assertEquals(response.status, 400);
1755
+ const malformedUrlLikeContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1756
+ method: "POST",
1757
+ body: JSON.stringify({
1758
+ "@context": ["http://[", "https://www.w3.org/ns/activitystreams"],
1759
+ id: "https://example.com/activities/http-signed-malformed-url-like-context",
1760
+ type: "Create",
1761
+ actor: "https://example.com/person2",
1762
+ object: {
1763
+ id: "https://example.com/notes/http-signed-malformed-url-like-context",
1764
+ type: "Note",
1765
+ attributedTo: "https://example.com/person2",
1766
+ content: "Hello, world!"
1767
+ }
1768
+ })
1769
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1770
+ const malformedUrlLikeContextHttpSignedContext = createRequestContext({
1771
+ federation,
1772
+ request: malformedUrlLikeContextHttpSignedRequest,
1773
+ url: new URL(malformedUrlLikeContextHttpSignedRequest.url),
1774
+ data: void 0,
1775
+ documentLoader: mockDocumentLoader,
1776
+ contextLoader: async (resource) => {
1777
+ if (resource === "http://[") {
1778
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
1779
+ error.name = "jsonld.InvalidUrl";
1780
+ error.details = {
1781
+ code: "loading remote context failed",
1782
+ url: resource
1783
+ };
1784
+ throw error;
1785
+ }
1786
+ return await mockDocumentLoader(new URL(resource).href);
1787
+ }
1788
+ });
1789
+ response = await handleInbox(malformedUrlLikeContextHttpSignedRequest, {
1790
+ recipient: null,
1791
+ context: malformedUrlLikeContextHttpSignedContext,
1792
+ inboxContextFactory(_activity) {
1793
+ return createInboxContext({
1794
+ ...malformedUrlLikeContextHttpSignedContext,
1795
+ clone: void 0
1796
+ });
1797
+ },
1798
+ ...inboxOptions
1799
+ });
1800
+ assertEquals(response.status, 400);
1801
+ const malformedContextUrlHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1802
+ method: "POST",
1803
+ body: JSON.stringify({
1804
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"],
1805
+ id: "https://example.com/activities/http-signed-malformed-context",
1806
+ type: "Create",
1807
+ actor: "https://example.com/person2",
1808
+ object: {
1809
+ id: "https://example.com/notes/http-signed-malformed-context",
1810
+ type: "Note",
1811
+ attributedTo: "https://example.com/person2",
1812
+ content: "Hello, world!"
1813
+ }
1814
+ })
1815
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1816
+ const malformedContextUrlHttpSignedContext = createRequestContext({
1817
+ federation,
1818
+ request: malformedContextUrlHttpSignedRequest,
1819
+ url: new URL(malformedContextUrlHttpSignedRequest.url),
1820
+ data: void 0,
1821
+ documentLoader: mockDocumentLoader,
1822
+ contextLoader: async (resource) => {
1823
+ if (resource === "not a url") {
1824
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
1825
+ error.name = "jsonld.InvalidUrl";
1826
+ error.details = {
1827
+ code: "loading remote context failed",
1828
+ url: resource
1829
+ };
1830
+ throw error;
1831
+ }
1832
+ return await mockDocumentLoader(new URL(resource).href);
1833
+ }
1834
+ });
1835
+ response = await handleInbox(malformedContextUrlHttpSignedRequest, {
1836
+ recipient: null,
1837
+ context: malformedContextUrlHttpSignedContext,
975
1838
  inboxContextFactory(_activity) {
976
1839
  return createInboxContext({
977
- ...unsignedContext,
1840
+ ...malformedContextUrlHttpSignedContext,
978
1841
  clone: void 0
979
1842
  });
980
1843
  },
981
- ...inboxOptions,
982
- actorDispatcher: void 0
1844
+ ...inboxOptions
983
1845
  });
984
- assertEquals(onNotFoundCalled, unsignedRequest);
985
- assertEquals(response.status, 404);
986
- onNotFoundCalled = null;
987
- response = await handleInbox(unsignedRequest, {
988
- recipient: "nobody",
989
- context: unsignedContext,
1846
+ assertEquals(response.status, 400);
1847
+ const invalidRemoteContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1848
+ method: "POST",
1849
+ body: JSON.stringify({
1850
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1851
+ id: "https://example.com/activities/http-signed-invalid-remote-context",
1852
+ type: "Create",
1853
+ actor: "https://example.com/person2",
1854
+ object: {
1855
+ id: "https://example.com/notes/http-signed-invalid-remote-context",
1856
+ type: "Note",
1857
+ attributedTo: "https://example.com/person2",
1858
+ content: "Hello, world!"
1859
+ }
1860
+ })
1861
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1862
+ const invalidRemoteContextHttpSignedContext = createRequestContext({
1863
+ federation,
1864
+ request: invalidRemoteContextHttpSignedRequest,
1865
+ url: new URL(invalidRemoteContextHttpSignedRequest.url),
1866
+ data: void 0,
1867
+ documentLoader: mockDocumentLoader,
1868
+ contextLoader: async (resource) => {
1869
+ const url = new URL(resource).href;
1870
+ if (url === remoteContextUrl) return {
1871
+ contextUrl: null,
1872
+ documentUrl: url,
1873
+ document: [
1874
+ "not",
1875
+ "an",
1876
+ "object"
1877
+ ]
1878
+ };
1879
+ return await mockDocumentLoader(url);
1880
+ }
1881
+ });
1882
+ response = await handleInbox(invalidRemoteContextHttpSignedRequest, {
1883
+ recipient: null,
1884
+ context: invalidRemoteContextHttpSignedContext,
990
1885
  inboxContextFactory(_activity) {
991
1886
  return createInboxContext({
992
- ...unsignedContext,
993
- clone: void 0,
994
- recipient: "nobody"
1887
+ ...invalidRemoteContextHttpSignedContext,
1888
+ clone: void 0
995
1889
  });
996
1890
  },
997
1891
  ...inboxOptions
998
1892
  });
999
- assertEquals(onNotFoundCalled, unsignedRequest);
1000
- assertEquals(response.status, 404);
1001
- onNotFoundCalled = null;
1002
- response = await handleInbox(unsignedRequest, {
1893
+ assertEquals(response.status, 400);
1894
+ const invalidUrlAbsoluteContextHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1895
+ method: "POST",
1896
+ body: JSON.stringify({
1897
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1898
+ id: "https://example.com/activities/http-signed-invalid-url-context",
1899
+ type: "Create",
1900
+ actor: "https://example.com/person2",
1901
+ ext: "preserve-me",
1902
+ object: {
1903
+ id: "https://example.com/notes/http-signed-invalid-url-context",
1904
+ type: "Note",
1905
+ attributedTo: "https://example.com/person2",
1906
+ content: "Hello, world!"
1907
+ }
1908
+ })
1909
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1910
+ const invalidUrlAbsoluteContextHttpSignedContext = createRequestContext({
1911
+ federation,
1912
+ request: invalidUrlAbsoluteContextHttpSignedRequest,
1913
+ url: new URL(invalidUrlAbsoluteContextHttpSignedRequest.url),
1914
+ data: void 0,
1915
+ documentLoader: mockDocumentLoader,
1916
+ contextLoader: async (resource) => {
1917
+ const url = new URL(resource).href;
1918
+ if (url === remoteContextUrl) throw new TypeError(`Invalid URL: ${url}`);
1919
+ return await mockDocumentLoader(url);
1920
+ }
1921
+ });
1922
+ await assertRejects(() => handleInbox(invalidUrlAbsoluteContextHttpSignedRequest, {
1003
1923
  recipient: null,
1004
- context: unsignedContext,
1924
+ context: invalidUrlAbsoluteContextHttpSignedContext,
1005
1925
  inboxContextFactory(_activity) {
1006
1926
  return createInboxContext({
1007
- ...unsignedContext,
1927
+ ...invalidUrlAbsoluteContextHttpSignedContext,
1008
1928
  clone: void 0
1009
1929
  });
1010
1930
  },
1011
1931
  ...inboxOptions
1932
+ }), Error);
1933
+ const typeErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1934
+ method: "POST",
1935
+ body: JSON.stringify({
1936
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1937
+ id: "https://example.com/activities/http-signed-typeerror-context",
1938
+ type: "Create",
1939
+ actor: "https://example.com/person2",
1940
+ ext: "preserve-me",
1941
+ object: {
1942
+ id: "https://example.com/notes/http-signed-typeerror-context",
1943
+ type: "Note",
1944
+ attributedTo: "https://example.com/person2",
1945
+ content: "Hello, world!"
1946
+ }
1947
+ })
1948
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1949
+ const typeErrorHttpSignedContext = createRequestContext({
1950
+ federation,
1951
+ request: typeErrorHttpSignedRequest,
1952
+ url: new URL(typeErrorHttpSignedRequest.url),
1953
+ data: void 0,
1954
+ documentLoader: mockDocumentLoader,
1955
+ contextLoader: async (resource) => {
1956
+ const url = new URL(resource).href;
1957
+ if (url === remoteContextUrl) throw new TypeError(`The remote context host timed out: ${url}`);
1958
+ return await mockDocumentLoader(url);
1959
+ }
1012
1960
  });
1013
- assertEquals(onNotFoundCalled, null);
1014
- assertEquals(response.status, 401);
1015
- response = await handleInbox(unsignedRequest, {
1016
- recipient: "someone",
1017
- context: unsignedContext,
1961
+ await assertRejects(() => handleInbox(typeErrorHttpSignedRequest, {
1962
+ recipient: null,
1963
+ context: typeErrorHttpSignedContext,
1018
1964
  inboxContextFactory(_activity) {
1019
1965
  return createInboxContext({
1020
- ...unsignedContext,
1021
- clone: void 0,
1022
- recipient: "someone"
1966
+ ...typeErrorHttpSignedContext,
1967
+ clone: void 0
1023
1968
  });
1024
1969
  },
1025
1970
  ...inboxOptions
1026
- });
1027
- assertEquals(onNotFoundCalled, null);
1028
- assertEquals(response.status, 401);
1029
- onNotFoundCalled = null;
1030
- const signedRequest = await signRequest(unsignedRequest.clone(), rsaPrivateKey3, rsaPublicKey3.id);
1031
- const signedContext = createRequestContext({
1971
+ }), Error);
1972
+ const rangeErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
1973
+ method: "POST",
1974
+ body: JSON.stringify({
1975
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1976
+ id: "https://example.com/activities/http-signed-rangeerror-context",
1977
+ type: "Create",
1978
+ actor: "https://example.com/person2",
1979
+ ext: "preserve-me",
1980
+ object: {
1981
+ id: "https://example.com/notes/http-signed-rangeerror-context",
1982
+ type: "Note",
1983
+ attributedTo: "https://example.com/person2",
1984
+ content: "Hello, world!"
1985
+ }
1986
+ })
1987
+ }), rsaPrivateKey3, rsaPublicKey3.id);
1988
+ const rangeErrorHttpSignedContext = createRequestContext({
1032
1989
  federation,
1033
- request: signedRequest,
1034
- url: new URL(signedRequest.url),
1990
+ request: rangeErrorHttpSignedRequest,
1991
+ url: new URL(rangeErrorHttpSignedRequest.url),
1035
1992
  data: void 0,
1036
- documentLoader: mockDocumentLoader
1993
+ documentLoader: mockDocumentLoader,
1994
+ contextLoader: async (resource) => {
1995
+ const url = new URL(resource).href;
1996
+ if (url === remoteContextUrl) throw new RangeError(`Temporary remote context cache window exceeded: ${url}`);
1997
+ return await mockDocumentLoader(url);
1998
+ }
1037
1999
  });
1038
- response = await handleInbox(signedRequest, {
2000
+ await assertRejects(() => handleInbox(rangeErrorHttpSignedRequest, {
1039
2001
  recipient: null,
1040
- context: signedContext,
2002
+ context: rangeErrorHttpSignedContext,
1041
2003
  inboxContextFactory(_activity) {
1042
2004
  return createInboxContext({
1043
- ...unsignedContext,
2005
+ ...rangeErrorHttpSignedContext,
1044
2006
  clone: void 0
1045
2007
  });
1046
2008
  },
1047
2009
  ...inboxOptions
2010
+ }), Error);
2011
+ const syntaxErrorHttpSignedRequest = await signRequest(new Request("https://example.com/", {
2012
+ method: "POST",
2013
+ body: JSON.stringify({
2014
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2015
+ id: "https://example.com/activities/http-signed-syntax-context",
2016
+ type: "Create",
2017
+ actor: "https://example.com/person2",
2018
+ ext: "preserve-me",
2019
+ object: {
2020
+ id: "https://example.com/notes/http-signed-syntax-context",
2021
+ type: "Note",
2022
+ attributedTo: "https://example.com/person2",
2023
+ content: "Hello, world!"
2024
+ }
2025
+ })
2026
+ }), rsaPrivateKey3, rsaPublicKey3.id);
2027
+ const syntaxErrorHttpSignedContext = createRequestContext({
2028
+ federation,
2029
+ request: syntaxErrorHttpSignedRequest,
2030
+ url: new URL(syntaxErrorHttpSignedRequest.url),
2031
+ data: void 0,
2032
+ documentLoader: mockDocumentLoader,
2033
+ contextLoader: async (resource) => {
2034
+ const url = new URL(resource).href;
2035
+ if (url === remoteContextUrl) {
2036
+ const error = /* @__PURE__ */ new Error(`Transient syntax failure: ${url}`);
2037
+ error.name = "jsonld.SyntaxError";
2038
+ error.details = { code: "loading remote context failed" };
2039
+ throw error;
2040
+ }
2041
+ return await mockDocumentLoader(url);
2042
+ }
1048
2043
  });
1049
- assertEquals(onNotFoundCalled, null);
1050
- assertEquals([response.status, await response.text()], [202, ""]);
2044
+ await assertRejects(() => handleInbox(syntaxErrorHttpSignedRequest, {
2045
+ recipient: null,
2046
+ context: syntaxErrorHttpSignedContext,
2047
+ inboxContextFactory(_activity) {
2048
+ return createInboxContext({
2049
+ ...syntaxErrorHttpSignedContext,
2050
+ clone: void 0
2051
+ });
2052
+ },
2053
+ ...inboxOptions
2054
+ }), Error);
1051
2055
  response = await handleInbox(signedRequest, {
1052
2056
  recipient: "someone",
1053
2057
  context: signedContext,
@@ -1091,6 +2095,64 @@ test("handleInbox()", async () => {
1091
2095
  });
1092
2096
  assertEquals(onNotFoundCalled, null);
1093
2097
  assertEquals(response.status, 202);
2098
+ const unsafeJson = {
2099
+ "@context": ["https://www.w3.org/ns/activitystreams", { rev: "@reverse" }],
2100
+ id: "https://example.com/activities/unsafe",
2101
+ type: "Announce",
2102
+ actor: "https://example.com/person2",
2103
+ object: "https://example.com/notes/1",
2104
+ rev: { object: {
2105
+ id: "https://example.com/activities/undo",
2106
+ type: "Undo",
2107
+ actor: "https://example.com/person2"
2108
+ } }
2109
+ };
2110
+ const unsafeRequest = await signRequest(new Request("https://example.com/", {
2111
+ method: "POST",
2112
+ body: JSON.stringify(unsafeJson)
2113
+ }), rsaPrivateKey3, rsaPublicKey3.id);
2114
+ const unsafeContext = createRequestContext({
2115
+ federation,
2116
+ request: unsafeRequest,
2117
+ url: new URL(unsafeRequest.url),
2118
+ data: void 0,
2119
+ documentLoader: mockDocumentLoader
2120
+ });
2121
+ response = await handleInbox(unsafeRequest, {
2122
+ recipient: null,
2123
+ context: unsafeContext,
2124
+ inboxContextFactory(_activity) {
2125
+ return createInboxContext({
2126
+ ...unsafeContext,
2127
+ clone: void 0
2128
+ });
2129
+ },
2130
+ ...inboxOptions
2131
+ });
2132
+ assertEquals(response.status, 202);
2133
+ const unsafeLdRequest = new Request("https://example.com/", {
2134
+ method: "POST",
2135
+ body: JSON.stringify(await signJsonLd(unsafeJson, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
2136
+ });
2137
+ const unsafeLdContext = createRequestContext({
2138
+ federation,
2139
+ request: unsafeLdRequest,
2140
+ url: new URL(unsafeLdRequest.url),
2141
+ data: void 0,
2142
+ documentLoader: mockDocumentLoader
2143
+ });
2144
+ response = await handleInbox(unsafeLdRequest, {
2145
+ recipient: null,
2146
+ context: unsafeLdContext,
2147
+ inboxContextFactory(_activity) {
2148
+ return createInboxContext({
2149
+ ...unsafeLdContext,
2150
+ clone: void 0
2151
+ });
2152
+ },
2153
+ ...inboxOptions
2154
+ });
2155
+ assertEquals(response.status, 400);
1094
2156
  const signedInvalidRequest = await signRequest(new Request("https://example.com/", {
1095
2157
  method: "POST",
1096
2158
  body: JSON.stringify({
@@ -1488,6 +2550,263 @@ test("handleOutbox()", async () => {
1488
2550
  assertEquals(response.status, 500);
1489
2551
  assertEquals(onErrorCalled, true);
1490
2552
  });
2553
+ test("handleInbox() preserves the raw signed payload for inboxContextFactory", async () => {
2554
+ const federation = createFederation({ kv: new MemoryKvStore() });
2555
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2556
+ const sourceContextLoader = async (resource) => {
2557
+ const url = new URL(resource).href;
2558
+ if (url === remoteContextUrl) return {
2559
+ contextUrl: null,
2560
+ documentUrl: url,
2561
+ document: { "@context": { ext: "https://example.com/ext" } }
2562
+ };
2563
+ return await mockDocumentLoader(url);
2564
+ };
2565
+ const signed = await signJsonLd({
2566
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2567
+ id: "https://example.com/activities/preserve-raw",
2568
+ type: "Create",
2569
+ actor: "https://example.com/person2",
2570
+ ext: "preserve-me",
2571
+ object: {
2572
+ id: "https://example.com/notes/preserve-raw",
2573
+ type: "Note",
2574
+ attributedTo: "https://example.com/person2",
2575
+ content: "Hello, world!"
2576
+ }
2577
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
2578
+ const request = new Request("https://example.com/", {
2579
+ method: "POST",
2580
+ body: JSON.stringify(signed)
2581
+ });
2582
+ const context = createRequestContext({
2583
+ federation,
2584
+ request,
2585
+ url: new URL(request.url),
2586
+ data: void 0,
2587
+ documentLoader: mockDocumentLoader,
2588
+ contextLoader: sourceContextLoader
2589
+ });
2590
+ let receivedRaw = null;
2591
+ let receivedTyped = null;
2592
+ const inboxListeners = new ActivityListenerSet();
2593
+ inboxListeners.add(Create, (ctx, activity) => {
2594
+ receivedRaw = ctx.activity;
2595
+ receivedTyped = activity;
2596
+ });
2597
+ const response = await handleInbox(request, {
2598
+ recipient: "someone",
2599
+ context,
2600
+ inboxContextFactory(recipient, activity, activityId, activityType) {
2601
+ return {
2602
+ ...createInboxContext({
2603
+ ...context,
2604
+ clone: void 0,
2605
+ recipient
2606
+ }),
2607
+ activity,
2608
+ activityId,
2609
+ activityType
2610
+ };
2611
+ },
2612
+ kv: new MemoryKvStore(),
2613
+ kvPrefixes: {
2614
+ activityIdempotence: ["_fedify", "activityIdempotence"],
2615
+ publicKey: ["_fedify", "publicKey"],
2616
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
2617
+ },
2618
+ actorDispatcher: (_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null,
2619
+ inboxListeners,
2620
+ onNotFound: () => new Response("Not found", { status: 404 }),
2621
+ signatureTimeWindow: { minutes: 5 },
2622
+ skipSignatureVerification: false
2623
+ });
2624
+ assertEquals([response.status, await response.text()], [202, ""]);
2625
+ assertEquals(receivedRaw, signed);
2626
+ const delivered = receivedTyped;
2627
+ assert(delivered != null);
2628
+ assertEquals(delivered.id?.href, "https://example.com/activities/preserve-raw");
2629
+ });
2630
+ test("handleInbox() enqueues normalizedActivity for LD-signed inbox work", async () => {
2631
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2632
+ const sourceContextLoader = async (resource) => {
2633
+ const url = new URL(resource).href;
2634
+ if (url === remoteContextUrl) return {
2635
+ contextUrl: null,
2636
+ documentUrl: url,
2637
+ document: { "@context": { ext: "https://example.com/ext" } }
2638
+ };
2639
+ return await mockDocumentLoader(url);
2640
+ };
2641
+ let queuedMessage = null;
2642
+ const queue = {
2643
+ enqueue(message) {
2644
+ queuedMessage = message;
2645
+ return Promise.resolve();
2646
+ },
2647
+ async listen() {}
2648
+ };
2649
+ const federation = createFederation({
2650
+ kv: new MemoryKvStore(),
2651
+ queue
2652
+ });
2653
+ const signed = await signJsonLd({
2654
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2655
+ id: "https://example.com/activities/enqueued-normalized",
2656
+ type: "Create",
2657
+ actor: "https://example.com/person2",
2658
+ ext: "preserve-me",
2659
+ object: {
2660
+ id: "https://example.com/notes/enqueued-normalized",
2661
+ type: "Note",
2662
+ attributedTo: "https://example.com/person2",
2663
+ content: "Hello, world!"
2664
+ }
2665
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
2666
+ const request = new Request("https://example.com/", {
2667
+ method: "POST",
2668
+ body: JSON.stringify(signed)
2669
+ });
2670
+ const context = createRequestContext({
2671
+ federation,
2672
+ request,
2673
+ url: new URL(request.url),
2674
+ data: void 0,
2675
+ documentLoader: mockDocumentLoader,
2676
+ contextLoader: sourceContextLoader
2677
+ });
2678
+ const response = await handleInbox(request, {
2679
+ recipient: "someone",
2680
+ context,
2681
+ inboxContextFactory(recipient, activity, activityId, activityType) {
2682
+ return {
2683
+ ...createInboxContext({
2684
+ ...context,
2685
+ clone: void 0,
2686
+ recipient
2687
+ }),
2688
+ activity,
2689
+ activityId,
2690
+ activityType
2691
+ };
2692
+ },
2693
+ kv: new MemoryKvStore(),
2694
+ kvPrefixes: {
2695
+ activityIdempotence: ["_fedify", "activityIdempotence"],
2696
+ publicKey: ["_fedify", "publicKey"],
2697
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
2698
+ },
2699
+ queue,
2700
+ actorDispatcher: (_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null,
2701
+ onNotFound: () => new Response("Not found", { status: 404 }),
2702
+ signatureTimeWindow: { minutes: 5 },
2703
+ skipSignatureVerification: false
2704
+ });
2705
+ assertEquals([response.status, await response.text()], [202, "Activity is enqueued."]);
2706
+ const enqueued = queuedMessage;
2707
+ assert(enqueued != null);
2708
+ const inboxMessage = enqueued;
2709
+ assertEquals(inboxMessage.activity, signed);
2710
+ assertEquals(inboxMessage.normalizedActivity, await compactJsonLd(signed, sourceContextLoader));
2711
+ assertEquals(inboxMessage.ldSignatureVerified, true);
2712
+ });
2713
+ test("handleInbox() caches normalizedActivity for queued signature-bearing fallback traffic", async () => {
2714
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2715
+ let queuedMessage = null;
2716
+ const queue = {
2717
+ enqueue(message) {
2718
+ queuedMessage = message;
2719
+ return Promise.resolve();
2720
+ },
2721
+ async listen() {}
2722
+ };
2723
+ const federation = createFederation({
2724
+ kv: new MemoryKvStore(),
2725
+ queue
2726
+ });
2727
+ const sourceContextLoader = async (resource) => {
2728
+ const url = new URL(resource).href;
2729
+ if (url === remoteContextUrl) return {
2730
+ contextUrl: null,
2731
+ documentUrl: url,
2732
+ document: { "@context": { ext: "https://example.com/ext" } }
2733
+ };
2734
+ return await mockDocumentLoader(url);
2735
+ };
2736
+ const unsignedBody = {
2737
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2738
+ id: "https://example.com/activities/non-lds-queued-signature",
2739
+ type: "Create",
2740
+ actor: "https://example.com/person2",
2741
+ ext: "preserve-me",
2742
+ object: {
2743
+ id: "https://example.com/notes/non-lds-queued-signature",
2744
+ type: "Note",
2745
+ attributedTo: "https://example.com/person2",
2746
+ content: "Hello, world!"
2747
+ },
2748
+ signature: {
2749
+ type: "RsaSignature2017",
2750
+ creator: "not a url",
2751
+ created: "2024-09-12T16:50:46Z",
2752
+ signatureValue: "Zm9v"
2753
+ }
2754
+ };
2755
+ const request = await signRequest(new Request("https://example.com/", {
2756
+ method: "POST",
2757
+ body: JSON.stringify(unsignedBody)
2758
+ }), rsaPrivateKey3, rsaPublicKey3.id);
2759
+ const context = createRequestContext({
2760
+ federation,
2761
+ request,
2762
+ url: new URL(request.url),
2763
+ data: void 0,
2764
+ documentLoader: mockDocumentLoader,
2765
+ contextLoader: sourceContextLoader
2766
+ });
2767
+ const response = await handleInbox(request, {
2768
+ recipient: "someone",
2769
+ context,
2770
+ inboxContextFactory(recipient, activity, activityId, activityType) {
2771
+ return {
2772
+ ...createInboxContext({
2773
+ ...context,
2774
+ clone: void 0,
2775
+ recipient
2776
+ }),
2777
+ activity,
2778
+ activityId,
2779
+ activityType
2780
+ };
2781
+ },
2782
+ kv: new MemoryKvStore(),
2783
+ kvPrefixes: {
2784
+ activityIdempotence: ["_fedify", "activityIdempotence"],
2785
+ publicKey: ["_fedify", "publicKey"],
2786
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
2787
+ },
2788
+ queue,
2789
+ actorDispatcher: (_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null,
2790
+ onNotFound: () => new Response("Not found", { status: 404 }),
2791
+ signatureTimeWindow: { minutes: 5 },
2792
+ skipSignatureVerification: false
2793
+ });
2794
+ assertEquals([response.status, await response.text()], [202, "Activity is enqueued."]);
2795
+ if (queuedMessage == null) throw new Error("Inbox message not queued.");
2796
+ const inboxMessage = queuedMessage;
2797
+ assertEquals(inboxMessage, {
2798
+ type: "inbox",
2799
+ id: inboxMessage.id,
2800
+ baseUrl: "https://example.com",
2801
+ activity: unsignedBody,
2802
+ normalizedActivity: await compactJsonLd(unsignedBody, sourceContextLoader),
2803
+ ldSignatureVerified: false,
2804
+ started: inboxMessage.started,
2805
+ attempt: 0,
2806
+ identifier: "someone",
2807
+ traceContext: inboxMessage.traceContext
2808
+ });
2809
+ });
1491
2810
  test("respondWithObject()", async () => {
1492
2811
  const response = await respondWithObject(new Note({
1493
2812
  id: new URL("https://example.com/notes/1"),