@fedify/fedify 2.2.3-dev.1098 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-mqtih91o.mjs → builder-CaVN56-q.mjs} +2 -2
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-BPMgyX7m.d.ts → context-BU-1O90h.d.ts} +48 -6
- package/dist/{context-DwkhwUX9.d.cts → context-DVA8wHZ0.d.cts} +48 -6
- package/dist/{deno-CziVFvS6.mjs → deno-DMg4SgCb.mjs} +1 -1
- package/dist/{docloader-fI9DeYyB.mjs → docloader-Da15YRxG.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +1363 -43
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/middleware.test.mjs +1584 -80
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-D8qsXrUS.js → http-BPPaA2uz.js} +1 -1
- package/dist/{http-BDCGf4Ac.mjs → http-C_edJspG.mjs} +2 -2
- package/dist/{http-kPc328Pc.cjs → http-Cl0Q2bUO.cjs} +1 -1
- package/dist/{key-D3TgMhcs.mjs → key-BAQuZEU1.mjs} +1 -1
- package/dist/{kv-cache-D_eVhctK.js → kv-cache-C4DGZ_t4.js} +1 -1
- package/dist/{kv-cache-zxW74Wfd.cjs → kv-cache-DmGi6uC-.cjs} +1 -1
- package/dist/ld-tusP_XxG.mjs +573 -0
- package/dist/{middleware-xR9KxICq.cjs → middleware-0V-9qj7m.cjs} +399 -73
- package/dist/{middleware-gXlDLkok.js → middleware-Ar1QOOPG.js} +396 -71
- package/dist/{middleware-2gmMVy8b.mjs → middleware-D9k0Knum.mjs} +314 -78
- package/dist/{middleware-BuOXw_hM.cjs → middleware-OQPBzyvx.cjs} +1 -1
- package/dist/{middleware-CfaiRKQ9.mjs → middleware-madKLp2f.mjs} +1 -1
- package/dist/{mod-CNAHY39V.d.ts → mod-BVt6iTmH.d.ts} +1 -1
- package/dist/{mod-Bi6WOdti.d.cts → mod-q-NFLW6B.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DBSV2TSl.mjs → owner-DRHNR5YO.mjs} +2 -2
- package/dist/{proof-tz91vdtN.mjs → proof-DLhLRv3m.mjs} +2 -2
- package/dist/{proof-CZDkoeWG.cjs → proof-DfrItHmh.cjs} +351 -3
- package/dist/{proof-z93OkIov.js → proof-SQ4cQs3A.js} +298 -4
- package/dist/{send-CNjG31rJ.mjs → send-C7tim5U9.mjs} +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +558 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/temporal-LL61Ddf2.mjs +95 -0
- package/dist/testing/mod.d.mts +48 -6
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +5 -5
- package/dist/ld-D_u8mdpv.mjs +0 -279
- /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
|
@@ -4,13 +4,15 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-Dk_tacqz.mjs";
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
6
|
import "../std__assert-CRDpx_HF.mjs";
|
|
7
|
+
import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
7
8
|
import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
|
|
8
9
|
import { t as assert } from "../assert-DikXweDx.mjs";
|
|
9
10
|
import { r as parseAcceptSignature } from "../accept-CPkZzmGN.mjs";
|
|
10
|
-
import { s as signRequest } from "../http-
|
|
11
|
+
import { s as signRequest } from "../http-C_edJspG.mjs";
|
|
11
12
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-DGu1NFwu.mjs";
|
|
13
|
+
import { a as compactJsonLd, p as signJsonLd } from "../ld-tusP_XxG.mjs";
|
|
12
14
|
import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
|
|
13
|
-
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-
|
|
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-D9k0Knum.mjs";
|
|
14
16
|
import { t as ActivityListenerSet } from "../activity-listener-ell7W1s9.mjs";
|
|
15
17
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
16
18
|
import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
|
|
@@ -956,6 +958,11 @@ test("handleInbox()", async () => {
|
|
|
956
958
|
if (identifier !== "someone") return null;
|
|
957
959
|
return new Person({ name: "Someone" });
|
|
958
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
|
+
};
|
|
959
966
|
const inboxOptions = {
|
|
960
967
|
kv: new MemoryKvStore(),
|
|
961
968
|
kvPrefixes: {
|
|
@@ -970,83 +977,1081 @@ test("handleInbox()", async () => {
|
|
|
970
977
|
};
|
|
971
978
|
let response = await handleInbox(unsignedRequest, {
|
|
972
979
|
recipient: null,
|
|
973
|
-
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,
|
|
974
1838
|
inboxContextFactory(_activity) {
|
|
975
1839
|
return createInboxContext({
|
|
976
|
-
...
|
|
1840
|
+
...malformedContextUrlHttpSignedContext,
|
|
977
1841
|
clone: void 0
|
|
978
1842
|
});
|
|
979
1843
|
},
|
|
980
|
-
...inboxOptions
|
|
981
|
-
actorDispatcher: void 0
|
|
1844
|
+
...inboxOptions
|
|
982
1845
|
});
|
|
983
|
-
assertEquals(
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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,
|
|
989
1885
|
inboxContextFactory(_activity) {
|
|
990
1886
|
return createInboxContext({
|
|
991
|
-
...
|
|
992
|
-
clone: void 0
|
|
993
|
-
recipient: "nobody"
|
|
1887
|
+
...invalidRemoteContextHttpSignedContext,
|
|
1888
|
+
clone: void 0
|
|
994
1889
|
});
|
|
995
1890
|
},
|
|
996
1891
|
...inboxOptions
|
|
997
1892
|
});
|
|
998
|
-
assertEquals(
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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, {
|
|
1002
1923
|
recipient: null,
|
|
1003
|
-
context:
|
|
1924
|
+
context: invalidUrlAbsoluteContextHttpSignedContext,
|
|
1004
1925
|
inboxContextFactory(_activity) {
|
|
1005
1926
|
return createInboxContext({
|
|
1006
|
-
...
|
|
1927
|
+
...invalidUrlAbsoluteContextHttpSignedContext,
|
|
1007
1928
|
clone: void 0
|
|
1008
1929
|
});
|
|
1009
1930
|
},
|
|
1010
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
|
+
}
|
|
1011
1960
|
});
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
recipient: "someone",
|
|
1016
|
-
context: unsignedContext,
|
|
1961
|
+
await assertRejects(() => handleInbox(typeErrorHttpSignedRequest, {
|
|
1962
|
+
recipient: null,
|
|
1963
|
+
context: typeErrorHttpSignedContext,
|
|
1017
1964
|
inboxContextFactory(_activity) {
|
|
1018
1965
|
return createInboxContext({
|
|
1019
|
-
...
|
|
1020
|
-
clone: void 0
|
|
1021
|
-
recipient: "someone"
|
|
1966
|
+
...typeErrorHttpSignedContext,
|
|
1967
|
+
clone: void 0
|
|
1022
1968
|
});
|
|
1023
1969
|
},
|
|
1024
1970
|
...inboxOptions
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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({
|
|
1031
1989
|
federation,
|
|
1032
|
-
request:
|
|
1033
|
-
url: new URL(
|
|
1990
|
+
request: rangeErrorHttpSignedRequest,
|
|
1991
|
+
url: new URL(rangeErrorHttpSignedRequest.url),
|
|
1034
1992
|
data: void 0,
|
|
1035
|
-
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
|
+
}
|
|
1036
1999
|
});
|
|
1037
|
-
|
|
2000
|
+
await assertRejects(() => handleInbox(rangeErrorHttpSignedRequest, {
|
|
1038
2001
|
recipient: null,
|
|
1039
|
-
context:
|
|
2002
|
+
context: rangeErrorHttpSignedContext,
|
|
1040
2003
|
inboxContextFactory(_activity) {
|
|
1041
2004
|
return createInboxContext({
|
|
1042
|
-
...
|
|
2005
|
+
...rangeErrorHttpSignedContext,
|
|
1043
2006
|
clone: void 0
|
|
1044
2007
|
});
|
|
1045
2008
|
},
|
|
1046
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
|
+
}
|
|
1047
2043
|
});
|
|
1048
|
-
|
|
1049
|
-
|
|
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);
|
|
1050
2055
|
response = await handleInbox(signedRequest, {
|
|
1051
2056
|
recipient: "someone",
|
|
1052
2057
|
context: signedContext,
|
|
@@ -1090,6 +2095,64 @@ test("handleInbox()", async () => {
|
|
|
1090
2095
|
});
|
|
1091
2096
|
assertEquals(onNotFoundCalled, null);
|
|
1092
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);
|
|
1093
2156
|
const signedInvalidRequest = await signRequest(new Request("https://example.com/", {
|
|
1094
2157
|
method: "POST",
|
|
1095
2158
|
body: JSON.stringify({
|
|
@@ -1487,6 +2550,263 @@ test("handleOutbox()", async () => {
|
|
|
1487
2550
|
assertEquals(response.status, 500);
|
|
1488
2551
|
assertEquals(onErrorCalled, true);
|
|
1489
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
|
+
});
|
|
1490
2810
|
test("respondWithObject()", async () => {
|
|
1491
2811
|
const response = await respondWithObject(new Note({
|
|
1492
2812
|
id: new URL("https://example.com/notes/1"),
|