@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2448
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/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
- package/dist/{builder-Da8b9O6v.mjs → builder-DbC8bn6k.mjs} +5 -5
- 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/{compound-proof-D1djO5fa.mjs → compound-proof-heeXW2_A.mjs} +2 -2
- package/dist/{context-CQ9H8vNW.d.cts → context-BazwQrHn.d.cts} +304 -4
- package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
- package/dist/{context-BdB54V-A.d.ts → context-GlHrzdSP.d.ts} +304 -4
- package/dist/{deno-eNpfj4oR.mjs → deno-Dpewt_jJ.mjs} +1 -1
- package/dist/{docloader-B-0nqOB1.mjs → docloader-CpsDyqVN.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +5 -5
- package/dist/federation/handler.test.mjs +21 -6
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.d.mts +2 -0
- package/dist/federation/inbox-actorless.test.mjs +487 -0
- package/dist/federation/inbox-authentication.test.d.mts +2 -0
- package/dist/federation/inbox-authentication.test.mjs +116 -0
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.d.mts +2 -0
- package/dist/federation/inbox-view.test.mjs +369 -0
- package/dist/federation/inbox.test.mjs +22 -12
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +9 -9
- package/dist/federation/mod.cjs +2 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +8 -2
- package/dist/federation/mq.test.mjs +36 -1
- package/dist/federation/outgoing-proof.test.mjs +4 -4
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +6 -6
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +3 -3
- package/dist/federation/tasks/tasks.test.mjs +4 -4
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-BVH7drJA.cjs → federation-D8YexkP1.cjs} +1172 -131
- package/dist/{http-DlNxxW_5.js → http-BBr-VLil.js} +143 -16
- package/dist/{http-BUJ5SFh6.mjs → http-BZkyQIkb.mjs} +4 -4
- package/dist/{http-BfW3wyQ4.cjs → http-CAl7Num5.cjs} +161 -16
- package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
- package/dist/inbox-view-NE9wXdLo.mjs +385 -0
- package/dist/inbox-wDuMKaFW.mjs +312 -0
- package/dist/{kv-cache-D_m3A241.mjs → kv-cache-CTQSfkmp.mjs} +1 -1
- package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-CjgCOz3i.cjs} +1 -1
- package/dist/{kv-cache-7015lbrx.js → kv-cache-U7uz5nx4.js} +1 -1
- package/dist/{ld-CD2CsPxq.mjs → ld-CQFuiah9.mjs} +52 -9
- package/dist/ld-CcuZ4NZa.d.ts +250 -0
- package/dist/ld-Cel1RRZ5.d.cts +250 -0
- package/dist/{metrics-D3LHk6Z4.mjs → metrics-B-QxRSSR.mjs} +1 -1
- package/dist/{middleware-CCDXvXOG.mjs → middleware-BdlNa8BD.mjs} +1 -1
- package/dist/{middleware-CzoWvrdZ.mjs → middleware-CuVa0UuS.mjs} +459 -98
- package/dist/{middleware-N9eM16XE.js → middleware-P-AZEQMn.js} +1169 -141
- package/dist/mod-B2783_3v.d.cts +154 -0
- package/dist/mod-BHQE-7WQ.d.ts +154 -0
- package/dist/{mod-D0bxB8jS.d.ts → mod-DjZfJj9R.d.ts} +1 -1
- package/dist/{mod-B0dGgQsg.d.cts → mod-DvWOv2Ho.d.cts} +1 -1
- package/dist/mod.cjs +5 -4
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/{owner-C5-FVX6t.mjs → owner-ccKkQOAf.mjs} +3 -3
- package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CndnpXfo.mjs} +2 -2
- package/dist/{portable-collection-CC2zNAje.js → portable-collection-DUlQ3s2M.js} +1 -1
- package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-TC-kvkVI.cjs} +1 -1
- package/dist/{portable-key-DNc_flH-.mjs → portable-key-D5tFOoV-.mjs} +1 -1
- package/dist/{proof-Cryb1c70.mjs → proof-DPoF06nz.mjs} +98 -10
- package/dist/{send-AF25pWaJ.mjs → send-CF44O1Df.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.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 +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +44 -4
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-BsAeuUJj.mjs → temporal-XlZ4Wb94.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +262 -2
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/dist/inbox-CHvMaIFX.mjs +0 -188
- package/dist/mod-BRpER-B0.d.ts +0 -320
- package/dist/mod-xAfaLS4T.d.cts +0 -320
- package/dist/owner-BF0L9cMt.d.cts +0 -78
- package/dist/owner-BSzr5-I8.d.ts +0 -78
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
|
|
3
3
|
const require_transformers = require("./transformers-BalCrSsr.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-CAl7Num5.cjs");
|
|
5
5
|
const require_types = require("./types-poIao40f.cjs");
|
|
6
|
-
const require_portable_collection = require("./portable-collection-
|
|
7
|
-
const require_kv_cache = require("./kv-cache-
|
|
6
|
+
const require_portable_collection = require("./portable-collection-TC-kvkVI.cjs");
|
|
7
|
+
const require_kv_cache = require("./kv-cache-CjgCOz3i.cjs");
|
|
8
8
|
let _logtape_logtape = require("@logtape/logtape");
|
|
9
9
|
let _fedify_uri_template = require("@fedify/uri-template");
|
|
10
10
|
let _fedify_vocab = require("@fedify/vocab");
|
|
@@ -15,6 +15,8 @@ let byte_encodings_hex = require("byte-encodings/hex");
|
|
|
15
15
|
let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
|
|
16
16
|
_fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
|
|
17
17
|
let es_toolkit = require("es-toolkit");
|
|
18
|
+
let json_canon = require("json-canon");
|
|
19
|
+
json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
18
20
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
19
21
|
let _fedify_webfinger = require("@fedify/webfinger");
|
|
20
22
|
let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
@@ -32,9 +34,12 @@ var ActivityListenerSet = class {
|
|
|
32
34
|
clone.#listeners = new Map(this.#listeners);
|
|
33
35
|
return clone;
|
|
34
36
|
}
|
|
35
|
-
add(type, listener) {
|
|
37
|
+
add(type, listener, options = {}) {
|
|
36
38
|
if (this.#listeners.has(type)) throw new TypeError("Listener already set for this type.");
|
|
37
|
-
this.#listeners.set(type,
|
|
39
|
+
this.#listeners.set(type, {
|
|
40
|
+
listener,
|
|
41
|
+
options: { ...options }
|
|
42
|
+
});
|
|
38
43
|
}
|
|
39
44
|
dispatchWithClass(activity) {
|
|
40
45
|
let cls = activity.constructor;
|
|
@@ -44,10 +49,11 @@ var ActivityListenerSet = class {
|
|
|
44
49
|
cls = globalThis.Object.getPrototypeOf(cls);
|
|
45
50
|
}
|
|
46
51
|
if (cls == null) return null;
|
|
47
|
-
const listener = this.#listeners.get(cls);
|
|
52
|
+
const { listener, options } = this.#listeners.get(cls);
|
|
48
53
|
return {
|
|
49
54
|
class: cls,
|
|
50
|
-
listener
|
|
55
|
+
listener,
|
|
56
|
+
options
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
dispatch(activity) {
|
|
@@ -635,8 +641,8 @@ var FederationBuilderImpl = class {
|
|
|
635
641
|
}
|
|
636
642
|
const listeners = this.inboxListeners = new ActivityListenerSet();
|
|
637
643
|
const setters = {
|
|
638
|
-
on(type, listener) {
|
|
639
|
-
listeners.add(type, listener);
|
|
644
|
+
on(type, listener, options) {
|
|
645
|
+
listeners.add(type, listener, options);
|
|
640
646
|
return setters;
|
|
641
647
|
},
|
|
642
648
|
onError: (handler) => {
|
|
@@ -2805,6 +2811,172 @@ async function isInAudience(object, actor, options = {}) {
|
|
|
2805
2811
|
return false;
|
|
2806
2812
|
}
|
|
2807
2813
|
//#endregion
|
|
2814
|
+
//#region src/federation/inbox-authentication.ts
|
|
2815
|
+
/**
|
|
2816
|
+
* The evidence of an activity that has not been authenticated by Fedify.
|
|
2817
|
+
*/
|
|
2818
|
+
const EMPTY_EVIDENCE = Object.freeze({
|
|
2819
|
+
verified: false,
|
|
2820
|
+
requestSigners: Object.freeze([]),
|
|
2821
|
+
documentSigners: Object.freeze([]),
|
|
2822
|
+
originAuthenticated: Object.freeze([]),
|
|
2823
|
+
expandedActivity: Object.freeze([]),
|
|
2824
|
+
baseUrl: null
|
|
2825
|
+
});
|
|
2826
|
+
/**
|
|
2827
|
+
* Gets the evidence of an activity that has not been authenticated.
|
|
2828
|
+
* @internal
|
|
2829
|
+
*/
|
|
2830
|
+
function getEmptyAuthenticationEvidence() {
|
|
2831
|
+
return EMPTY_EVIDENCE;
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Gets the principals that an authorization decision should rely on.
|
|
2835
|
+
* @param evidence The evidence.
|
|
2836
|
+
* @param scope See {@link IsActorAuthenticatedOptions.scope}.
|
|
2837
|
+
* @returns The principals.
|
|
2838
|
+
* @internal
|
|
2839
|
+
*/
|
|
2840
|
+
function getEffectivePrincipals(evidence, scope = "effective") {
|
|
2841
|
+
if (scope === "document" || evidence.documentSigners.length > 0) return evidence.documentSigners;
|
|
2842
|
+
if (evidence.requestSigners.length > 0) return evidence.requestSigners;
|
|
2843
|
+
return evidence.originAuthenticated;
|
|
2844
|
+
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Normalizes an actor ID for comparison: portable IDs and compatible
|
|
2847
|
+
* identifiers to their canonical portable ID, other IDs to their `href`.
|
|
2848
|
+
* @returns The normalized ID, or `null` for a malformed portable ID.
|
|
2849
|
+
*/
|
|
2850
|
+
function normalizeActorId(id) {
|
|
2851
|
+
return require_http.isPortableId(id) ? require_http.getCanonicalPortableId(id) : id.href;
|
|
2852
|
+
}
|
|
2853
|
+
/**
|
|
2854
|
+
* Checks whether the given principal authenticates the given actor.
|
|
2855
|
+
* @param principal The principal.
|
|
2856
|
+
* @param actorId The actor ID.
|
|
2857
|
+
* @returns `true` if the principal authenticates the actor.
|
|
2858
|
+
* @internal
|
|
2859
|
+
*/
|
|
2860
|
+
function principalAuthenticates(principal, actorId) {
|
|
2861
|
+
if (principal.kind === "portableAuthority") {
|
|
2862
|
+
if (!require_http.isPortableId(actorId)) return false;
|
|
2863
|
+
const did = require_http.getPortableDid(actorId);
|
|
2864
|
+
return did != null && did === require_http.getPortableDid(principal.id);
|
|
2865
|
+
}
|
|
2866
|
+
const expected = normalizeActorId(actorId);
|
|
2867
|
+
return expected != null && normalizeActorId(principal.id) === expected;
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Checks whether the given evidence authenticates the given actor.
|
|
2871
|
+
*
|
|
2872
|
+
* Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
|
|
2873
|
+
* compatible identifiers are compared by their canonical portable IDs, and
|
|
2874
|
+
* a portable authority (a DID) authenticates the portable actors of that DID.
|
|
2875
|
+
*
|
|
2876
|
+
* @example
|
|
2877
|
+
* ``` typescript
|
|
2878
|
+
* federation
|
|
2879
|
+
* .setInboxListeners("/users/{identifier}/inbox", "/inbox")
|
|
2880
|
+
* .on(Follow, async (ctx, follow) => {
|
|
2881
|
+
* if (follow.actorId == null) return;
|
|
2882
|
+
* // The actor is always authenticated for activities that declare one,
|
|
2883
|
+
* // but the same check works for any actor an activity names:
|
|
2884
|
+
* isActorAuthenticated(ctx.authentication, follow.actorId);
|
|
2885
|
+
* });
|
|
2886
|
+
* ```
|
|
2887
|
+
*
|
|
2888
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
2889
|
+
* @param evidence The authentication evidence, usually
|
|
2890
|
+
* {@link InboxContext.authentication}.
|
|
2891
|
+
* @param actorId The actor ID to check.
|
|
2892
|
+
* @param options Options.
|
|
2893
|
+
* @returns `true` if the actor is authenticated.
|
|
2894
|
+
* @since 2.5.0
|
|
2895
|
+
*/
|
|
2896
|
+
function isActorAuthenticated(evidence, actorId, options = {}) {
|
|
2897
|
+
return getEffectivePrincipals(evidence, options.scope).some((principal) => principalAuthenticates(principal, actorId));
|
|
2898
|
+
}
|
|
2899
|
+
function serializePrincipal(principal) {
|
|
2900
|
+
return {
|
|
2901
|
+
kind: principal.kind,
|
|
2902
|
+
id: principal.id.href,
|
|
2903
|
+
mechanism: principal.mechanism,
|
|
2904
|
+
keyId: principal.keyId?.href ?? null
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Serializes the principals of the given evidence for a queued message.
|
|
2909
|
+
* @internal
|
|
2910
|
+
*/
|
|
2911
|
+
function serializeAuthentication(evidence) {
|
|
2912
|
+
return {
|
|
2913
|
+
verified: evidence.verified,
|
|
2914
|
+
requestSigners: evidence.requestSigners.map(serializePrincipal),
|
|
2915
|
+
documentSigners: evidence.documentSigners.map(serializePrincipal),
|
|
2916
|
+
originAuthenticated: evidence.originAuthenticated.map(serializePrincipal)
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
const KINDS = /* @__PURE__ */ new Set(["actor", "portableAuthority"]);
|
|
2920
|
+
const MECHANISMS = /* @__PURE__ */ new Set([
|
|
2921
|
+
"httpSignatures",
|
|
2922
|
+
"linkedDataSignatures",
|
|
2923
|
+
"objectIntegrityProofs",
|
|
2924
|
+
"origin"
|
|
2925
|
+
]);
|
|
2926
|
+
function deserializePrincipals(value) {
|
|
2927
|
+
if (!Array.isArray(value)) return null;
|
|
2928
|
+
const principals = [];
|
|
2929
|
+
for (const item of value) {
|
|
2930
|
+
if (item == null || typeof item !== "object" || !KINDS.has(item.kind) || !MECHANISMS.has(item.mechanism) || typeof item.id !== "string" || !URL.canParse(item.id) || item.keyId != null && (typeof item.keyId !== "string" || !URL.canParse(item.keyId))) return null;
|
|
2931
|
+
principals.push(Object.freeze({
|
|
2932
|
+
kind: item.kind,
|
|
2933
|
+
id: new URL(item.id),
|
|
2934
|
+
mechanism: item.mechanism,
|
|
2935
|
+
keyId: item.keyId == null ? null : new URL(item.keyId)
|
|
2936
|
+
}));
|
|
2937
|
+
}
|
|
2938
|
+
return principals;
|
|
2939
|
+
}
|
|
2940
|
+
/**
|
|
2941
|
+
* Restores the evidence of a queued message.
|
|
2942
|
+
* @param value The serialized principals.
|
|
2943
|
+
* @param expandedActivity The expanded activity, restored from the queued
|
|
2944
|
+
* verified view.
|
|
2945
|
+
* @param baseUrl The base URL, restored from the queued verified view.
|
|
2946
|
+
* @returns The evidence, or `null` if the serialized value is malformed.
|
|
2947
|
+
* @internal
|
|
2948
|
+
*/
|
|
2949
|
+
function deserializeAuthentication(value, expandedActivity, baseUrl) {
|
|
2950
|
+
if (value == null || typeof value !== "object" || typeof value.verified !== "boolean") return null;
|
|
2951
|
+
const record = value;
|
|
2952
|
+
const requestSigners = deserializePrincipals(record.requestSigners);
|
|
2953
|
+
const documentSigners = deserializePrincipals(record.documentSigners);
|
|
2954
|
+
const originAuthenticated = deserializePrincipals(record.originAuthenticated);
|
|
2955
|
+
if (requestSigners == null || documentSigners == null || originAuthenticated == null) return null;
|
|
2956
|
+
return createAuthenticationEvidence({
|
|
2957
|
+
verified: record.verified,
|
|
2958
|
+
requestSigners,
|
|
2959
|
+
documentSigners,
|
|
2960
|
+
originAuthenticated,
|
|
2961
|
+
expandedActivity,
|
|
2962
|
+
baseUrl
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Creates frozen evidence.
|
|
2967
|
+
* @internal
|
|
2968
|
+
*/
|
|
2969
|
+
function createAuthenticationEvidence(evidence) {
|
|
2970
|
+
return Object.freeze({
|
|
2971
|
+
verified: evidence.verified,
|
|
2972
|
+
requestSigners: Object.freeze([...evidence.requestSigners]),
|
|
2973
|
+
documentSigners: Object.freeze([...evidence.documentSigners]),
|
|
2974
|
+
originAuthenticated: Object.freeze([...evidence.originAuthenticated]),
|
|
2975
|
+
expandedActivity: evidence.expandedActivity,
|
|
2976
|
+
baseUrl: evidence.baseUrl
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
//#endregion
|
|
2808
2980
|
//#region src/federation/inbox.ts
|
|
2809
2981
|
const Temporal$10 = require_http.Temporal;
|
|
2810
2982
|
/**
|
|
@@ -2827,13 +2999,135 @@ function actorsHaveSameOrigin(activity) {
|
|
|
2827
2999
|
const origin = require_http.getAuthenticationOrigin(activity.id);
|
|
2828
3000
|
return origin != null && activity.actorIds.every((actor) => require_http.getAuthenticationOrigin(actor) === origin);
|
|
2829
3001
|
}
|
|
2830
|
-
|
|
3002
|
+
/**
|
|
3003
|
+
* The headroom that the size check of a queued inbox message leaves for the
|
|
3004
|
+
* fields that grow when Fedify retries the message, e.g., its attempt count.
|
|
3005
|
+
*/
|
|
3006
|
+
const QUEUED_MESSAGE_HEADROOM = 64;
|
|
3007
|
+
/**
|
|
3008
|
+
* Computes the idempotency key of an incoming activity.
|
|
3009
|
+
* @returns The key, or `null` if the activity is not deduplicated.
|
|
3010
|
+
* @internal
|
|
3011
|
+
*/
|
|
3012
|
+
async function getInboxIdempotencyKey(strategy, inboxContext, activity, origin, recipient, prefix) {
|
|
3013
|
+
if (activity.id == null) return null;
|
|
3014
|
+
strategy ??= "per-inbox";
|
|
3015
|
+
let keyString;
|
|
3016
|
+
if (typeof strategy === "function") keyString = await strategy(inboxContext(), activity);
|
|
3017
|
+
else switch (strategy) {
|
|
3018
|
+
case "global":
|
|
3019
|
+
keyString = activity.id.href;
|
|
3020
|
+
break;
|
|
3021
|
+
case "per-origin":
|
|
3022
|
+
keyString = `${origin}\n${activity.id.href}`;
|
|
3023
|
+
break;
|
|
3024
|
+
case "per-inbox":
|
|
3025
|
+
keyString = `${origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
|
|
3026
|
+
break;
|
|
3027
|
+
default: keyString = `${origin}\n${activity.id.href}`;
|
|
3028
|
+
}
|
|
3029
|
+
return keyString == null ? null : [...prefix, keyString];
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Checks whether an incoming activity has already been processed, by its
|
|
3033
|
+
* idempotency key, or by the key that queue workers of older versions of
|
|
3034
|
+
* Fedify recorded.
|
|
3035
|
+
* @internal
|
|
3036
|
+
*/
|
|
3037
|
+
async function isInboxActivityProcessed(kv, cacheKey, activity, origin, prefix) {
|
|
3038
|
+
if (cacheKey == null) return false;
|
|
3039
|
+
if (await kv.get(cacheKey) === true) return true;
|
|
3040
|
+
if (activity.id == null) return false;
|
|
3041
|
+
return await kv.get([
|
|
3042
|
+
...prefix,
|
|
3043
|
+
origin,
|
|
3044
|
+
activity.id.href
|
|
3045
|
+
]) === true;
|
|
3046
|
+
}
|
|
3047
|
+
/**
|
|
3048
|
+
* The bytes added to the UTF-8 JSON length of a message for each of its JSON
|
|
3049
|
+
* values, to bound the size of serialization formats other than JSON, such
|
|
3050
|
+
* as the structured serialization of Deno KV, which spends more bytes than
|
|
3051
|
+
* JSON on type tags and lengths of small values, e.g., empty arrays, and up
|
|
3052
|
+
* to nine bytes on a fractional number that JSON writes in three.
|
|
3053
|
+
*/
|
|
3054
|
+
const PER_VALUE_SERIALIZATION_OVERHEAD = 8;
|
|
3055
|
+
/**
|
|
3056
|
+
* Estimates, conservatively, how large a queued message is once a queue
|
|
3057
|
+
* backend serializes it: the UTF-8 length of its JSON serialization, plus
|
|
3058
|
+
* a fixed overhead for each JSON value in it, plus the bytes that encoding
|
|
3059
|
+
* strings beyond Latin-1 as UTF-16 adds.
|
|
3060
|
+
* @internal
|
|
3061
|
+
*/
|
|
3062
|
+
function estimateQueuedMessageSize(message) {
|
|
3063
|
+
const encoder = new TextEncoder();
|
|
3064
|
+
let values = 0;
|
|
3065
|
+
let stringExpansion = 0;
|
|
3066
|
+
const countString = (value) => {
|
|
3067
|
+
values++;
|
|
3068
|
+
if (!/[\u0100-\uffff]/.test(value)) return;
|
|
3069
|
+
const expansion = value.length * 2 - encoder.encode(value).byteLength;
|
|
3070
|
+
if (expansion > 0) stringExpansion += expansion;
|
|
3071
|
+
};
|
|
3072
|
+
const count = (value) => {
|
|
3073
|
+
if (typeof value === "string") {
|
|
3074
|
+
countString(value);
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
values++;
|
|
3078
|
+
if (Array.isArray(value)) for (const item of value) count(item);
|
|
3079
|
+
else if (value != null && typeof value === "object") for (const [key, item] of Object.entries(value)) {
|
|
3080
|
+
if (item === void 0) continue;
|
|
3081
|
+
countString(key);
|
|
3082
|
+
count(item);
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
count(message);
|
|
3086
|
+
return encoder.encode(JSON.stringify(message)).byteLength + stringExpansion + values * PER_VALUE_SERIALIZATION_OVERHEAD;
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* Checks whether an activity that declares no actor is authenticated: at
|
|
3090
|
+
* least one principal must be authenticated for it, and its ID, if any, must
|
|
3091
|
+
* have the same [FEP-fe34] origin as one of those principals.
|
|
3092
|
+
*
|
|
3093
|
+
* [FEP-fe34]: https://w3id.org/fep/fe34
|
|
3094
|
+
* @returns `"ok"` if it is authenticated, or why it is not.
|
|
3095
|
+
* @internal
|
|
3096
|
+
*/
|
|
3097
|
+
function checkActorlessActivity(activity, authentication) {
|
|
3098
|
+
const principals = getEffectivePrincipals(authentication);
|
|
3099
|
+
if (principals.length < 1) return {
|
|
3100
|
+
status: "noAuthenticatedPrincipal",
|
|
3101
|
+
principals
|
|
3102
|
+
};
|
|
3103
|
+
if (activity.id == null) return {
|
|
3104
|
+
status: "ok",
|
|
3105
|
+
principals
|
|
3106
|
+
};
|
|
3107
|
+
const origin = require_http.getAuthenticationOrigin(activity.id);
|
|
3108
|
+
if (origin == null || !principals.some((principal) => require_http.getAuthenticationOrigin(principal.id) === origin)) return {
|
|
3109
|
+
status: "activityOriginMismatch",
|
|
3110
|
+
principals
|
|
3111
|
+
};
|
|
3112
|
+
return {
|
|
3113
|
+
status: "ok",
|
|
3114
|
+
principals
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Same as {@link checkActorlessActivity}, but returns a boolean.
|
|
3119
|
+
* @internal
|
|
3120
|
+
*/
|
|
3121
|
+
function isActorlessActivityAuthenticated(activity, authentication) {
|
|
3122
|
+
return checkActorlessActivity(activity, authentication).status === "ok";
|
|
3123
|
+
}
|
|
3124
|
+
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, authentication, verifiedView, replayable, actorless, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
2831
3125
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
2832
3126
|
"fedify",
|
|
2833
3127
|
"federation",
|
|
2834
3128
|
"inbox"
|
|
2835
3129
|
]);
|
|
2836
|
-
if (activity.actorId == null) {
|
|
3130
|
+
if (activity.actorId == null && !actorless) {
|
|
2837
3131
|
logger.error("Missing actor.", { activity: json });
|
|
2838
3132
|
span.setStatus({
|
|
2839
3133
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
@@ -2842,58 +3136,59 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
2842
3136
|
require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
|
|
2843
3137
|
return "missingActor";
|
|
2844
3138
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
|
|
2860
|
-
break;
|
|
2861
|
-
default: keyString = `${ctx.origin}\n${activity.id.href}`;
|
|
2862
|
-
}
|
|
2863
|
-
if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
|
|
3139
|
+
const contextMetadata = { authentication };
|
|
3140
|
+
const cacheKey = await getInboxIdempotencyKey(idempotencyStrategy, () => inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href, contextMetadata), activity, ctx.origin, recipient, kvPrefixes.activityIdempotence);
|
|
3141
|
+
if (await isInboxActivityProcessed(kv, cacheKey, activity, ctx.origin, kvPrefixes.activityIdempotence)) {
|
|
3142
|
+
logger.debug("Activity {activityId} has already been processed.", {
|
|
3143
|
+
activityId: activity.id?.href,
|
|
3144
|
+
activity: json,
|
|
3145
|
+
recipient
|
|
3146
|
+
});
|
|
3147
|
+
span.setStatus({
|
|
3148
|
+
code: _opentelemetry_api.SpanStatusCode.UNSET,
|
|
3149
|
+
message: `Activity ${activity.id?.href} has already been processed.`
|
|
3150
|
+
});
|
|
3151
|
+
require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
|
|
3152
|
+
return "alreadyProcessed";
|
|
2864
3153
|
}
|
|
2865
|
-
if (
|
|
2866
|
-
|
|
2867
|
-
|
|
3154
|
+
if (activity.actorId != null) span.setAttribute("activitypub.actor.id", activity.actorId.href);
|
|
3155
|
+
let message;
|
|
3156
|
+
if (queue != null) {
|
|
3157
|
+
const carrier = {};
|
|
3158
|
+
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
3159
|
+
message = {
|
|
3160
|
+
type: "inbox",
|
|
3161
|
+
id: crypto.randomUUID(),
|
|
3162
|
+
baseUrl: ctx.origin,
|
|
3163
|
+
activity: originalJson ?? json,
|
|
3164
|
+
...normalizedActivity == null ? {} : { normalizedActivity },
|
|
3165
|
+
...ldSignatureVerified == null ? {} : { ldSignatureVerified },
|
|
3166
|
+
...verifiedView == null ? {} : { verifiedView },
|
|
3167
|
+
...authentication == null ? {} : { authentication: serializeAuthentication(authentication) },
|
|
3168
|
+
identifier: recipient,
|
|
3169
|
+
attempt: 0,
|
|
3170
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3171
|
+
traceContext: carrier
|
|
3172
|
+
};
|
|
3173
|
+
const maxMessageSize = queue.maxMessageSize;
|
|
3174
|
+
if (replayable === false) {
|
|
3175
|
+
logger.warn("The incoming activity {activityId} uses a JSON-LD context that cannot be kept for the queue; processing it immediately instead.", {
|
|
2868
3176
|
activityId: activity.id?.href,
|
|
2869
|
-
activity: json,
|
|
2870
3177
|
recipient
|
|
2871
3178
|
});
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
3179
|
+
message = void 0;
|
|
3180
|
+
} else if (maxMessageSize != null && estimateQueuedMessageSize(message) > maxMessageSize - QUEUED_MESSAGE_HEADROOM) {
|
|
3181
|
+
logger.warn("The incoming activity {activityId} is too large to enqueue; processing it immediately instead.", {
|
|
3182
|
+
activityId: activity.id?.href,
|
|
3183
|
+
recipient,
|
|
3184
|
+
maxMessageSize
|
|
2875
3185
|
});
|
|
2876
|
-
|
|
2877
|
-
return "alreadyProcessed";
|
|
3186
|
+
message = void 0;
|
|
2878
3187
|
}
|
|
2879
3188
|
}
|
|
2880
|
-
|
|
2881
|
-
if (queue != null) {
|
|
2882
|
-
const carrier = {};
|
|
2883
|
-
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
3189
|
+
if (queue != null && message != null) {
|
|
2884
3190
|
try {
|
|
2885
|
-
await queue.enqueue(
|
|
2886
|
-
type: "inbox",
|
|
2887
|
-
id: crypto.randomUUID(),
|
|
2888
|
-
baseUrl: ctx.origin,
|
|
2889
|
-
activity: originalJson ?? json,
|
|
2890
|
-
...normalizedActivity == null ? {} : { normalizedActivity },
|
|
2891
|
-
...ldSignatureVerified == null ? {} : { ldSignatureVerified },
|
|
2892
|
-
identifier: recipient,
|
|
2893
|
-
attempt: 0,
|
|
2894
|
-
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2895
|
-
traceContext: carrier
|
|
2896
|
-
});
|
|
3191
|
+
await queue.enqueue(message);
|
|
2897
3192
|
} catch (error) {
|
|
2898
3193
|
logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
|
|
2899
3194
|
error,
|
|
@@ -2943,7 +3238,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
2943
3238
|
const started = performance.now();
|
|
2944
3239
|
try {
|
|
2945
3240
|
const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
|
|
2946
|
-
await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
|
|
3241
|
+
await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType, contextMetadata), activity);
|
|
2947
3242
|
} finally {
|
|
2948
3243
|
require_http.getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
|
|
2949
3244
|
}
|
|
@@ -2984,6 +3279,382 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
2984
3279
|
});
|
|
2985
3280
|
}
|
|
2986
3281
|
//#endregion
|
|
3282
|
+
//#region src/federation/inbox-view.ts
|
|
3283
|
+
/**
|
|
3284
|
+
* The interpretation of an incoming activity that Fedify authenticated, kept
|
|
3285
|
+
* so that listeners, authorization helpers, and queue workers observe the
|
|
3286
|
+
* same JSON-LD meaning that signature verification observed.
|
|
3287
|
+
*
|
|
3288
|
+
* Every JSON-LD processing step of one delivery goes through a request-scoped
|
|
3289
|
+
* *context snapshot*, which memoizes the remote context responses so that
|
|
3290
|
+
* a nondeterministic or mutable remote context cannot give verification and
|
|
3291
|
+
* dispatch different meanings. The snapshot is serialized into queued
|
|
3292
|
+
* messages, so that workers and retries replay the very same responses
|
|
3293
|
+
* without loading contexts again.
|
|
3294
|
+
* @module
|
|
3295
|
+
*/
|
|
3296
|
+
/**
|
|
3297
|
+
* Thrown when replaying an interpretation needs a context response that its
|
|
3298
|
+
* snapshot does not have, or has with a different content.
|
|
3299
|
+
* @internal
|
|
3300
|
+
*/
|
|
3301
|
+
var ContextSnapshotMissError = class extends Error {
|
|
3302
|
+
url;
|
|
3303
|
+
constructor(url, reason) {
|
|
3304
|
+
super(`Context ${url} cannot be replayed: ${reason}.`);
|
|
3305
|
+
this.name = "ContextSnapshotMissError";
|
|
3306
|
+
this.url = url;
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
function normalizeUrl(url) {
|
|
3310
|
+
return URL.canParse(url) ? new URL(url).href : url;
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* Creates the context snapshot of a delivery.
|
|
3314
|
+
* @param base The caller's context loader.
|
|
3315
|
+
* @returns The snapshot.
|
|
3316
|
+
* @internal
|
|
3317
|
+
*/
|
|
3318
|
+
function createContextSnapshot(base) {
|
|
3319
|
+
const underlying = {
|
|
3320
|
+
standard: require_http.wrapContextLoaderForJsonLd(base),
|
|
3321
|
+
normalization: require_http.getNormalizationContextLoader(base)
|
|
3322
|
+
};
|
|
3323
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3324
|
+
const recorded = /* @__PURE__ */ new Map();
|
|
3325
|
+
const loaders = /* @__PURE__ */ new Map();
|
|
3326
|
+
let released = false;
|
|
3327
|
+
return {
|
|
3328
|
+
loader(policy) {
|
|
3329
|
+
let loader = loaders.get(policy);
|
|
3330
|
+
if (loader != null) return loader;
|
|
3331
|
+
const effective = underlying[policy];
|
|
3332
|
+
loader = require_http.markEffectiveContextLoader(async (url, options) => {
|
|
3333
|
+
if (released) return await effective(url, options);
|
|
3334
|
+
const normalizedUrl = normalizeUrl(url);
|
|
3335
|
+
const key = `${policy}\n${normalizedUrl}`;
|
|
3336
|
+
let promise = cache.get(key);
|
|
3337
|
+
if (promise == null) {
|
|
3338
|
+
promise = Promise.resolve(effective(url, options)).then((response) => {
|
|
3339
|
+
const cloned = normalizeResponse(structuredClone(response));
|
|
3340
|
+
recorded.set(key, {
|
|
3341
|
+
policy,
|
|
3342
|
+
url: normalizedUrl,
|
|
3343
|
+
response: structuredClone(cloned)
|
|
3344
|
+
});
|
|
3345
|
+
return cloned;
|
|
3346
|
+
});
|
|
3347
|
+
promise.catch(() => {});
|
|
3348
|
+
cache.set(key, promise);
|
|
3349
|
+
}
|
|
3350
|
+
return structuredClone(await promise);
|
|
3351
|
+
}, policy);
|
|
3352
|
+
loaders.set(policy, loader);
|
|
3353
|
+
return loader;
|
|
3354
|
+
},
|
|
3355
|
+
entries() {
|
|
3356
|
+
return [...recorded.values()];
|
|
3357
|
+
},
|
|
3358
|
+
release() {
|
|
3359
|
+
released = true;
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3363
|
+
function getBundledResponse(url) {
|
|
3364
|
+
if (!(url in _fedify_vocab_runtime.preloadedContexts)) return null;
|
|
3365
|
+
return {
|
|
3366
|
+
contextUrl: null,
|
|
3367
|
+
document: _fedify_vocab_runtime.preloadedContexts[url],
|
|
3368
|
+
documentUrl: url
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
async function sha256(value) {
|
|
3372
|
+
return (0, byte_encodings_hex.encodeHex)(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, json_canon.default)(value))));
|
|
3373
|
+
}
|
|
3374
|
+
function normalizeResponse(response) {
|
|
3375
|
+
return {
|
|
3376
|
+
contextUrl: response.contextUrl ?? null,
|
|
3377
|
+
document: response.document,
|
|
3378
|
+
documentUrl: response.documentUrl
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Serializes the entries of a context snapshot for a queued message.
|
|
3383
|
+
* @param entries The entries.
|
|
3384
|
+
* @returns The serialized entries, which are plain JSON values.
|
|
3385
|
+
* @internal
|
|
3386
|
+
*/
|
|
3387
|
+
async function serializeContextSnapshot(entries) {
|
|
3388
|
+
const result = [];
|
|
3389
|
+
for (const entry of entries) {
|
|
3390
|
+
const response = normalizeResponse(entry.response);
|
|
3391
|
+
const hash = await sha256(response);
|
|
3392
|
+
const bundled = getBundledResponse(entry.url);
|
|
3393
|
+
if (bundled != null && await sha256(bundled) === hash) result.push({
|
|
3394
|
+
policy: entry.policy,
|
|
3395
|
+
url: entry.url,
|
|
3396
|
+
sha256: hash,
|
|
3397
|
+
bundled: true
|
|
3398
|
+
});
|
|
3399
|
+
else result.push({
|
|
3400
|
+
policy: entry.policy,
|
|
3401
|
+
url: entry.url,
|
|
3402
|
+
sha256: hash,
|
|
3403
|
+
response: structuredClone(response)
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
return result;
|
|
3407
|
+
}
|
|
3408
|
+
function isRemoteDocument(value) {
|
|
3409
|
+
if (value == null || typeof value !== "object") return false;
|
|
3410
|
+
const record = value;
|
|
3411
|
+
return typeof record.documentUrl === "string" && "document" in record && (record.contextUrl == null || typeof record.contextUrl === "string");
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Restores the entries of a serialized context snapshot, verifying the
|
|
3415
|
+
* hashes of stored responses. Bundled entries are reconstructed from the
|
|
3416
|
+
* contexts this version of Fedify bundles.
|
|
3417
|
+
* @param entries The serialized entries.
|
|
3418
|
+
* @returns The entries.
|
|
3419
|
+
* @throws {ContextSnapshotMissError} If an entry is malformed, or its
|
|
3420
|
+
* content does not match its hash.
|
|
3421
|
+
* @internal
|
|
3422
|
+
*/
|
|
3423
|
+
async function deserializeContextSnapshot(entries) {
|
|
3424
|
+
if (!Array.isArray(entries)) throw new ContextSnapshotMissError("(snapshot)", "malformed snapshot");
|
|
3425
|
+
const result = [];
|
|
3426
|
+
for (const entry of entries) {
|
|
3427
|
+
if (entry == null || typeof entry !== "object" || entry.policy !== "standard" && entry.policy !== "normalization" || typeof entry.url !== "string" || typeof entry.sha256 !== "string") throw new ContextSnapshotMissError("(snapshot)", "malformed entry");
|
|
3428
|
+
let response;
|
|
3429
|
+
if (entry.bundled === true) {
|
|
3430
|
+
response = getBundledResponse(entry.url);
|
|
3431
|
+
if (response == null) throw new ContextSnapshotMissError(entry.url, "not bundled");
|
|
3432
|
+
} else if (isRemoteDocument(entry.response)) response = normalizeResponse(entry.response);
|
|
3433
|
+
else throw new ContextSnapshotMissError(entry.url, "malformed entry");
|
|
3434
|
+
if (entry.bundled !== true && await sha256(response) !== entry.sha256) throw new ContextSnapshotMissError(entry.url, "content mismatch");
|
|
3435
|
+
result.push({
|
|
3436
|
+
policy: entry.policy,
|
|
3437
|
+
url: entry.url,
|
|
3438
|
+
response: structuredClone(response)
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
return result;
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Creates a context loader of a policy that serves only the responses of
|
|
3445
|
+
* a snapshot. It is marked as the effective loader of the policy.
|
|
3446
|
+
* @param entries The snapshot's entries.
|
|
3447
|
+
* @param policy The policy.
|
|
3448
|
+
* @returns The loader, which throws {@link ContextSnapshotMissError} for
|
|
3449
|
+
* anything else.
|
|
3450
|
+
* @internal
|
|
3451
|
+
*/
|
|
3452
|
+
function createSnapshotOnlyLoader(entries, policy) {
|
|
3453
|
+
const responses = /* @__PURE__ */ new Map();
|
|
3454
|
+
for (const entry of entries) if (entry.policy === policy) responses.set(entry.url, entry.response);
|
|
3455
|
+
return require_http.markEffectiveContextLoader(async (url) => {
|
|
3456
|
+
const response = responses.get(normalizeUrl(url));
|
|
3457
|
+
if (response == null) throw new ContextSnapshotMissError(url, "not in the snapshot");
|
|
3458
|
+
return structuredClone(response);
|
|
3459
|
+
}, policy);
|
|
3460
|
+
}
|
|
3461
|
+
/**
|
|
3462
|
+
* Creates a {@link ReleasableLoader}.
|
|
3463
|
+
* @param strict The loader to use until release.
|
|
3464
|
+
* @param fallback The loader to use after release.
|
|
3465
|
+
* @param policy The policy both loaders apply.
|
|
3466
|
+
* @returns The releasable loader.
|
|
3467
|
+
* @internal
|
|
3468
|
+
*/
|
|
3469
|
+
function createReleasableLoader(strict, fallback, policy) {
|
|
3470
|
+
let released = false;
|
|
3471
|
+
return {
|
|
3472
|
+
loader: require_http.markEffectiveContextLoader((url, options) => released ? fallback(url, options) : strict(url, options), policy),
|
|
3473
|
+
release() {
|
|
3474
|
+
released = true;
|
|
3475
|
+
}
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Expands a JSON-LD document with the given context loader.
|
|
3480
|
+
* @param document The document.
|
|
3481
|
+
* @param contextLoader The context loader.
|
|
3482
|
+
* @returns The expanded document.
|
|
3483
|
+
* @internal
|
|
3484
|
+
*/
|
|
3485
|
+
async function expandDocument(document, contextLoader) {
|
|
3486
|
+
return await _fedify_vocab_runtime_jsonld.default.expand(document, { documentLoader: contextLoader });
|
|
3487
|
+
}
|
|
3488
|
+
/**
|
|
3489
|
+
* Computes the digest of an expanded document.
|
|
3490
|
+
* @param expanded The expanded document.
|
|
3491
|
+
* @returns The hex-encoded SHA-256 of its canonical JSON.
|
|
3492
|
+
* @internal
|
|
3493
|
+
*/
|
|
3494
|
+
function digestExpandedDocument(expanded) {
|
|
3495
|
+
return sha256(expanded);
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Deeply freezes a JSON value.
|
|
3499
|
+
* @param value The value.
|
|
3500
|
+
* @returns The same value, frozen.
|
|
3501
|
+
* @internal
|
|
3502
|
+
*/
|
|
3503
|
+
function deepFreeze(value) {
|
|
3504
|
+
if (value != null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
3505
|
+
Object.freeze(value);
|
|
3506
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
3507
|
+
}
|
|
3508
|
+
return value;
|
|
3509
|
+
}
|
|
3510
|
+
const ACTOR_IRI = "https://www.w3.org/ns/activitystreams#actor";
|
|
3511
|
+
/**
|
|
3512
|
+
* Gets the single root node of an expanded document.
|
|
3513
|
+
* @param expanded The expanded document.
|
|
3514
|
+
* @returns The root node, or `null` if there is not exactly one.
|
|
3515
|
+
* @internal
|
|
3516
|
+
*/
|
|
3517
|
+
function getRootNode(expanded) {
|
|
3518
|
+
if (expanded.length !== 1) return null;
|
|
3519
|
+
const node = expanded[0];
|
|
3520
|
+
if (node == null || typeof node !== "object" || Array.isArray(node)) return null;
|
|
3521
|
+
return node;
|
|
3522
|
+
}
|
|
3523
|
+
/**
|
|
3524
|
+
* Gets the values of a property of an expanded node, unwrapping a single
|
|
3525
|
+
* `@list` container the way generated vocabulary decoders do.
|
|
3526
|
+
* @param node The expanded node.
|
|
3527
|
+
* @param iri The property IRI.
|
|
3528
|
+
* @returns The values, or an empty array if the property is absent.
|
|
3529
|
+
* @internal
|
|
3530
|
+
*/
|
|
3531
|
+
function getPropertyValues(node, iri) {
|
|
3532
|
+
const values = node[iri];
|
|
3533
|
+
if (values == null) return [];
|
|
3534
|
+
if (!Array.isArray(values)) return [values];
|
|
3535
|
+
if (values.length === 1 && values[0] != null && typeof values[0] === "object" && "@list" in values[0]) {
|
|
3536
|
+
const list = values[0]["@list"];
|
|
3537
|
+
return Array.isArray(list) ? list : [list];
|
|
3538
|
+
}
|
|
3539
|
+
return values;
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Tells whether the root node of an expanded activity declares any `actor`
|
|
3543
|
+
* value at all, including blank nodes, anonymous or untyped nodes, and
|
|
3544
|
+
* literals, which ID accessors and typed parsing may drop.
|
|
3545
|
+
* @param expanded The expanded activity.
|
|
3546
|
+
* @returns `"absent"` if the root node declares no actor; `"present"`
|
|
3547
|
+
* otherwise, including when there is no single root node.
|
|
3548
|
+
* @internal
|
|
3549
|
+
*/
|
|
3550
|
+
function getActorPresence(expanded) {
|
|
3551
|
+
const root = getRootNode(expanded);
|
|
3552
|
+
if (root == null) return "present";
|
|
3553
|
+
return getPropertyValues(root, ACTOR_IRI).length < 1 ? "absent" : "present";
|
|
3554
|
+
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Builds the serialized view of an authenticated activity. The document is
|
|
3557
|
+
* expanded through the snapshot, so that the snapshot holds every context
|
|
3558
|
+
* response the interpretation needs.
|
|
3559
|
+
* @param snapshot The delivery's context snapshot.
|
|
3560
|
+
* @param source Which document the activity was parsed from.
|
|
3561
|
+
* @returns The serialized view and the expanded activity.
|
|
3562
|
+
* @internal
|
|
3563
|
+
*/
|
|
3564
|
+
async function buildVerifiedActivityView(snapshot, source) {
|
|
3565
|
+
const loader = snapshot.loader(source.policy);
|
|
3566
|
+
const used = /* @__PURE__ */ new Set();
|
|
3567
|
+
const expanded = await expandDocument(source.document, require_http.markEffectiveContextLoader((url, options) => {
|
|
3568
|
+
used.add(normalizeUrl(url));
|
|
3569
|
+
return loader(url, options);
|
|
3570
|
+
}, source.policy));
|
|
3571
|
+
const entries = snapshot.entries().filter((entry) => entry.policy === source.policy && used.has(entry.url));
|
|
3572
|
+
const contexts = await serializeContextSnapshot(entries);
|
|
3573
|
+
const references = /* @__PURE__ */ new Set();
|
|
3574
|
+
collectContextReferences(source.document, source.baseUrl?.href, references);
|
|
3575
|
+
for (const entry of entries) {
|
|
3576
|
+
const { contextUrl, document } = entry.response;
|
|
3577
|
+
collectContextReferences(document, entry.url, references);
|
|
3578
|
+
if (contextUrl != null) collectContextReferences({ "@context": contextUrl }, entry.url, references);
|
|
3579
|
+
}
|
|
3580
|
+
return {
|
|
3581
|
+
replayable: [...references].every((url) => used.has(url)),
|
|
3582
|
+
view: {
|
|
3583
|
+
version: 1,
|
|
3584
|
+
policy: source.policy,
|
|
3585
|
+
source: source.source,
|
|
3586
|
+
baseUrl: source.baseUrl?.href ?? null,
|
|
3587
|
+
contexts,
|
|
3588
|
+
expandedDigest: await digestExpandedDocument(expanded)
|
|
3589
|
+
},
|
|
3590
|
+
expanded
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
/**
|
|
3594
|
+
* Collects the remote context references of a JSON-LD document or context
|
|
3595
|
+
* document: every IRI given as a value of `@context` at any depth, including
|
|
3596
|
+
* scoped contexts of term definitions, and of `@import`.
|
|
3597
|
+
* @param value The document.
|
|
3598
|
+
* @param base The URL to resolve relative references against.
|
|
3599
|
+
* @param references The set to add the normalized references to.
|
|
3600
|
+
*/
|
|
3601
|
+
function collectContextReferences(value, base, references) {
|
|
3602
|
+
const add = (reference) => {
|
|
3603
|
+
let url = reference;
|
|
3604
|
+
if (base != null && URL.canParse(reference, base)) url = new URL(reference, base).href;
|
|
3605
|
+
references.add(normalizeUrl(url));
|
|
3606
|
+
};
|
|
3607
|
+
const collectContext = (context) => {
|
|
3608
|
+
if (typeof context === "string") add(context);
|
|
3609
|
+
else if (Array.isArray(context)) for (const item of context) collectContext(item);
|
|
3610
|
+
else if (context != null && typeof context === "object") {
|
|
3611
|
+
const imported = context["@import"];
|
|
3612
|
+
if (typeof imported === "string") add(imported);
|
|
3613
|
+
walk(context);
|
|
3614
|
+
}
|
|
3615
|
+
};
|
|
3616
|
+
const walk = (node) => {
|
|
3617
|
+
if (Array.isArray(node)) for (const item of node) walk(item);
|
|
3618
|
+
else if (node != null && typeof node === "object") for (const [key, item] of Object.entries(node)) if (key === "@context") collectContext(item);
|
|
3619
|
+
else walk(item);
|
|
3620
|
+
};
|
|
3621
|
+
walk(value);
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Validates the shape of a serialized view taken from a queued message.
|
|
3625
|
+
* @param value The value of the message's `verifiedView` field.
|
|
3626
|
+
* @returns The view, or `null` if it is malformed or of an unsupported
|
|
3627
|
+
* version.
|
|
3628
|
+
* @internal
|
|
3629
|
+
*/
|
|
3630
|
+
function parseSerializedVerifiedActivityView(value) {
|
|
3631
|
+
if (value == null || typeof value !== "object") return null;
|
|
3632
|
+
const view = value;
|
|
3633
|
+
if (view.version !== 1 || view.policy !== "standard" && view.policy !== "normalization" || view.source !== "activity" && view.source !== "normalizedActivity" || view.baseUrl !== null && (typeof view.baseUrl !== "string" || !URL.canParse(view.baseUrl)) || !Array.isArray(view.contexts) || typeof view.expandedDigest !== "string") return null;
|
|
3634
|
+
return view;
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Restores a view from a queued message without loading any context.
|
|
3638
|
+
* @param view The serialized view.
|
|
3639
|
+
* @param message The fields of the message the view refers to.
|
|
3640
|
+
* @returns The restored view.
|
|
3641
|
+
* @throws {ContextSnapshotMissError} If the view cannot be replayed exactly.
|
|
3642
|
+
* @internal
|
|
3643
|
+
*/
|
|
3644
|
+
async function restoreVerifiedActivityView(view, message) {
|
|
3645
|
+
const loader = createSnapshotOnlyLoader(await deserializeContextSnapshot(view.contexts), view.policy);
|
|
3646
|
+
if (view.source === "normalizedActivity" && message.normalizedActivity == null) throw new ContextSnapshotMissError("(normalizedActivity)", "the normalized activity is missing");
|
|
3647
|
+
const document = require_http.detachSignature(view.source === "activity" ? message.activity : message.normalizedActivity);
|
|
3648
|
+
const expanded = await expandDocument(document, loader);
|
|
3649
|
+
if (await digestExpandedDocument(expanded) !== view.expandedDigest) throw new ContextSnapshotMissError("(activity)", "the interpretation does not match the authenticated one");
|
|
3650
|
+
return {
|
|
3651
|
+
document,
|
|
3652
|
+
loader,
|
|
3653
|
+
expanded,
|
|
3654
|
+
baseUrl: view.baseUrl == null ? null : new URL(view.baseUrl)
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
//#endregion
|
|
2987
3658
|
//#region src/federation/keycache.ts
|
|
2988
3659
|
const Temporal$9 = require_http.Temporal;
|
|
2989
3660
|
const KEY_CACHE_GENERATION = "3";
|
|
@@ -5405,11 +6076,12 @@ async function handleInbox(request, options) {
|
|
|
5405
6076
|
}, async (span) => {
|
|
5406
6077
|
if (options.recipient != null) span.setAttribute("fedify.inbox.recipient", options.recipient);
|
|
5407
6078
|
const spanContext = span.spanContext();
|
|
6079
|
+
const snapshot = createContextSnapshot(options.context.contextLoader);
|
|
5408
6080
|
try {
|
|
5409
6081
|
return await (0, _logtape_logtape.withContext)({
|
|
5410
6082
|
traceId: spanContext.traceId,
|
|
5411
6083
|
spanId: spanContext.spanId
|
|
5412
|
-
}, () => handleInboxInternal(request, options, span));
|
|
6084
|
+
}, () => handleInboxInternal(request, options, span, snapshot));
|
|
5413
6085
|
} catch (e) {
|
|
5414
6086
|
options.observation.hasException = true;
|
|
5415
6087
|
span.setStatus({
|
|
@@ -5418,6 +6090,7 @@ async function handleInbox(request, options) {
|
|
|
5418
6090
|
});
|
|
5419
6091
|
throw e;
|
|
5420
6092
|
} finally {
|
|
6093
|
+
snapshot.release();
|
|
5421
6094
|
options.observation.project(span);
|
|
5422
6095
|
span.end();
|
|
5423
6096
|
}
|
|
@@ -5429,9 +6102,10 @@ async function handleInbox(request, options) {
|
|
|
5429
6102
|
* @param request The HTTP request.
|
|
5430
6103
|
* @param options The parameters for handling the inbox.
|
|
5431
6104
|
* @param span The OpenTelemetry span for tracing.
|
|
6105
|
+
* @param snapshot The context snapshot of the delivery.
|
|
5432
6106
|
* @returns A promise that resolves to an HTTP response.
|
|
5433
6107
|
*/
|
|
5434
|
-
async function handleInboxInternal(request, parameters, span) {
|
|
6108
|
+
async function handleInboxInternal(request, parameters, span, snapshot) {
|
|
5435
6109
|
const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, maxHttpSignatures, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider, portableInbox } = parameters;
|
|
5436
6110
|
const observation = parameters.observation;
|
|
5437
6111
|
const signatureObservation = { [require_http.verificationObservation]: observation.verification };
|
|
@@ -5545,6 +6219,8 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5545
6219
|
tracerProvider,
|
|
5546
6220
|
keyTtl: publicKeyTtl
|
|
5547
6221
|
});
|
|
6222
|
+
const standardLoader = snapshot.loader("standard");
|
|
6223
|
+
const normalizationLoader = snapshot.loader("normalization");
|
|
5548
6224
|
const jsonWithoutSig = require_http.detachSignature(json);
|
|
5549
6225
|
const hasLdSignature = require_http.hasSignature(json);
|
|
5550
6226
|
const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
|
|
@@ -5580,9 +6256,10 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5580
6256
|
let compactedJson = json;
|
|
5581
6257
|
let compactedJsonWithoutSig = jsonWithoutSig;
|
|
5582
6258
|
let ldSigVerified = false;
|
|
6259
|
+
let ldSigKey = null;
|
|
5583
6260
|
if (hasLdSignature) {
|
|
5584
6261
|
try {
|
|
5585
|
-
compactedJson = await require_http.compactJsonLd(json,
|
|
6262
|
+
compactedJson = await require_http.compactJsonLd(json, normalizationLoader);
|
|
5586
6263
|
} catch (error) {
|
|
5587
6264
|
if (isInvalidJsonLdError(error)) {
|
|
5588
6265
|
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
@@ -5618,16 +6295,18 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5618
6295
|
if (compactedJson !== json) {
|
|
5619
6296
|
compactedJsonWithoutSig = require_http.detachSignature(compactedJson);
|
|
5620
6297
|
try {
|
|
5621
|
-
|
|
6298
|
+
const ldResult = await require_http.verifyCompactJsonLdDetailed(compactedJson, {
|
|
5622
6299
|
...signatureObservation,
|
|
5623
|
-
contextLoader:
|
|
6300
|
+
contextLoader: normalizationLoader,
|
|
5624
6301
|
documentLoader: ctx.documentLoader,
|
|
5625
6302
|
keyCache,
|
|
5626
6303
|
meterProvider,
|
|
5627
6304
|
tracerProvider
|
|
5628
6305
|
});
|
|
6306
|
+
ldSigVerified = ldResult.verified;
|
|
6307
|
+
ldSigKey = ldResult.key;
|
|
5629
6308
|
} catch (error) {
|
|
5630
|
-
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig,
|
|
6309
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(error);
|
|
5631
6310
|
if (isInvalidJsonLdError(error)) {
|
|
5632
6311
|
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
5633
6312
|
recipient,
|
|
@@ -5645,12 +6324,12 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5645
6324
|
if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
|
|
5646
6325
|
if (!skipSignatureVerification) try {
|
|
5647
6326
|
await _fedify_vocab.Object.fromJsonLd(compactedJson, {
|
|
5648
|
-
contextLoader:
|
|
6327
|
+
contextLoader: normalizationLoader,
|
|
5649
6328
|
documentLoader: ctx.documentLoader,
|
|
5650
6329
|
tracerProvider
|
|
5651
6330
|
});
|
|
5652
6331
|
} catch (parseError) {
|
|
5653
|
-
if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig,
|
|
6332
|
+
if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(parseError);
|
|
5654
6333
|
if (isInvalidJsonLdError(parseError)) {
|
|
5655
6334
|
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
5656
6335
|
recipient,
|
|
@@ -5674,6 +6353,33 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5674
6353
|
let activity = null;
|
|
5675
6354
|
let activityVerified = false;
|
|
5676
6355
|
let proofVerified = false;
|
|
6356
|
+
let documentSigners = [];
|
|
6357
|
+
let requestSigners = [];
|
|
6358
|
+
let viewSource;
|
|
6359
|
+
const rawViewSource = {
|
|
6360
|
+
policy: "standard",
|
|
6361
|
+
source: "activity",
|
|
6362
|
+
document: jsonWithoutSig,
|
|
6363
|
+
baseUrl: null
|
|
6364
|
+
};
|
|
6365
|
+
const isActorlessCandidate = async (candidate, source) => {
|
|
6366
|
+
if (skipSignatureVerification) return false;
|
|
6367
|
+
if (inboxListeners?.dispatchWithClass(candidate)?.options.allowActorless !== true) return false;
|
|
6368
|
+
try {
|
|
6369
|
+
return getActorPresence(await expandDocument(source.document, snapshot.loader(source.policy))) === "absent";
|
|
6370
|
+
} catch {
|
|
6371
|
+
return false;
|
|
6372
|
+
}
|
|
6373
|
+
};
|
|
6374
|
+
const verifyProofs = (contextLoader = standardLoader) => require_http.verifyObjectDetailed(_fedify_vocab.Activity, jsonWithoutSig, {
|
|
6375
|
+
...signatureObservation,
|
|
6376
|
+
contextLoader,
|
|
6377
|
+
documentLoader: ctx.documentLoader,
|
|
6378
|
+
keyCache,
|
|
6379
|
+
meterProvider,
|
|
6380
|
+
tracerProvider,
|
|
6381
|
+
verifyPortableObject: ctx.verifyPortableObject
|
|
6382
|
+
});
|
|
5677
6383
|
if (ldSigVerified) {
|
|
5678
6384
|
logger.debug("Linked Data Signatures are verified.", {
|
|
5679
6385
|
recipient,
|
|
@@ -5682,18 +6388,49 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5682
6388
|
try {
|
|
5683
6389
|
activity = observation.activity = await _fedify_vocab.Activity.fromJsonLd(compactedJsonWithoutSig, {
|
|
5684
6390
|
...ctx,
|
|
5685
|
-
contextLoader:
|
|
6391
|
+
contextLoader: normalizationLoader
|
|
5686
6392
|
});
|
|
5687
6393
|
} catch (error) {
|
|
5688
|
-
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig,
|
|
6394
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(error);
|
|
5689
6395
|
if (!isPermanentActivityParseError(error)) throw error;
|
|
5690
6396
|
return await respondInvalidActivity(error);
|
|
5691
6397
|
}
|
|
5692
6398
|
activityVerified = true;
|
|
6399
|
+
viewSource = {
|
|
6400
|
+
policy: "normalization",
|
|
6401
|
+
source: "normalizedActivity",
|
|
6402
|
+
document: compactedJsonWithoutSig,
|
|
6403
|
+
baseUrl: null
|
|
6404
|
+
};
|
|
6405
|
+
if (ldSigKey?.ownerId != null) documentSigners = [{
|
|
6406
|
+
kind: "actor",
|
|
6407
|
+
id: ldSigKey.ownerId,
|
|
6408
|
+
mechanism: "linkedDataSignatures",
|
|
6409
|
+
keyId: ldSigKey.id
|
|
6410
|
+
}];
|
|
6411
|
+
if (await isActorlessCandidate(activity, viewSource)) {
|
|
6412
|
+
let result = null;
|
|
6413
|
+
try {
|
|
6414
|
+
result = await verifyProofs();
|
|
6415
|
+
} catch (error) {
|
|
6416
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
6417
|
+
logger.debug("Failed to verify the Object Integrity Proofs of the activity without an actor:\n{error}", {
|
|
6418
|
+
recipient,
|
|
6419
|
+
error
|
|
6420
|
+
});
|
|
6421
|
+
}
|
|
6422
|
+
if (result?.status === "unavailable") throw result.error;
|
|
6423
|
+
if (result?.status === "verified" && result.signers.length > 0) {
|
|
6424
|
+
activity = observation.activity = result.object;
|
|
6425
|
+
documentSigners = toProofPrincipals(result.signers);
|
|
6426
|
+
viewSource = rawViewSource;
|
|
6427
|
+
proofVerified = true;
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
5693
6430
|
if (!skipSignatureVerification && isPortableActivity(activity)) try {
|
|
5694
6431
|
proofVerified = await require_http.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
|
|
5695
6432
|
...signatureObservation,
|
|
5696
|
-
contextLoader:
|
|
6433
|
+
contextLoader: standardLoader,
|
|
5697
6434
|
documentLoader: ctx.documentLoader,
|
|
5698
6435
|
keyCache,
|
|
5699
6436
|
meterProvider,
|
|
@@ -5711,18 +6448,11 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5711
6448
|
recipient,
|
|
5712
6449
|
json
|
|
5713
6450
|
});
|
|
6451
|
+
let proofResult;
|
|
5714
6452
|
try {
|
|
5715
|
-
|
|
5716
|
-
...signatureObservation,
|
|
5717
|
-
contextLoader: require_http.wrapContextLoaderForJsonLd(ctx.contextLoader),
|
|
5718
|
-
documentLoader: ctx.documentLoader,
|
|
5719
|
-
keyCache,
|
|
5720
|
-
meterProvider,
|
|
5721
|
-
tracerProvider,
|
|
5722
|
-
verifyPortableObject: ctx.verifyPortableObject
|
|
5723
|
-
});
|
|
6453
|
+
proofResult = await verifyProofs();
|
|
5724
6454
|
} catch (error) {
|
|
5725
|
-
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig,
|
|
6455
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, standardLoader)) return await respondInvalidActivity(error);
|
|
5726
6456
|
if (deferredLdSignatureError != null) {
|
|
5727
6457
|
logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
|
|
5728
6458
|
recipient,
|
|
@@ -5758,6 +6488,22 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5758
6488
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
5759
6489
|
});
|
|
5760
6490
|
}
|
|
6491
|
+
if (proofResult.status === "verified") if (proofResult.signers.length < 1 && await isActorlessCandidate(proofResult.object, rawViewSource)) activity = null;
|
|
6492
|
+
else {
|
|
6493
|
+
activity = proofResult.object;
|
|
6494
|
+
documentSigners = toProofPrincipals(proofResult.signers);
|
|
6495
|
+
viewSource = rawViewSource;
|
|
6496
|
+
}
|
|
6497
|
+
else if (proofResult.status === "unavailable") {
|
|
6498
|
+
let parsed = null;
|
|
6499
|
+
try {
|
|
6500
|
+
parsed = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
|
|
6501
|
+
...ctx,
|
|
6502
|
+
contextLoader: standardLoader
|
|
6503
|
+
});
|
|
6504
|
+
} catch {}
|
|
6505
|
+
if (parsed != null && await isActorlessCandidate(parsed, rawViewSource)) throw proofResult.error;
|
|
6506
|
+
}
|
|
5761
6507
|
if (activity == null) logger.debug("Object Integrity Proofs are not verified.", {
|
|
5762
6508
|
recipient,
|
|
5763
6509
|
activity: json
|
|
@@ -5887,14 +6633,28 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5887
6633
|
try {
|
|
5888
6634
|
activity = observation.activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
|
|
5889
6635
|
...ctx,
|
|
5890
|
-
contextLoader:
|
|
6636
|
+
contextLoader: standardLoader
|
|
5891
6637
|
});
|
|
5892
6638
|
} catch (error) {
|
|
5893
6639
|
if (!isPermanentActivityParseError(error)) throw error;
|
|
5894
6640
|
return await respondInvalidActivity(error);
|
|
5895
6641
|
}
|
|
6642
|
+
viewSource = rawViewSource;
|
|
5896
6643
|
}
|
|
5897
6644
|
observation.activity = activity;
|
|
6645
|
+
let view;
|
|
6646
|
+
try {
|
|
6647
|
+
view = await buildVerifiedActivityView(snapshot, viewSource ?? rawViewSource);
|
|
6648
|
+
} catch (error) {
|
|
6649
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
6650
|
+
return await respondInvalidActivity(error);
|
|
6651
|
+
}
|
|
6652
|
+
const actorless = !skipSignatureVerification && getActorPresence(view.expanded) === "absent" && inboxListeners?.dispatchWithClass(activity)?.options.allowActorless === true;
|
|
6653
|
+
span.setAttribute("activitypub.activity.actorless", actorless);
|
|
6654
|
+
if (actorless && httpSigKey != null) {
|
|
6655
|
+
const principal = getHttpKeyPrincipal(httpSigKey);
|
|
6656
|
+
if (principal != null) requestSigners = [principal];
|
|
6657
|
+
}
|
|
5898
6658
|
observation.stage = "policy";
|
|
5899
6659
|
observation.result = {
|
|
5900
6660
|
disposition: "rejected",
|
|
@@ -5938,7 +6698,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5938
6698
|
const rejection = await require_http.observeAttempt(signatureObservation, "objectIntegrity", async (verification) => {
|
|
5939
6699
|
const rejection = await verifyPortableActivityId(activity.id, jsonWithoutSig, {
|
|
5940
6700
|
[require_http.verificationObservation]: verification,
|
|
5941
|
-
contextLoader:
|
|
6701
|
+
contextLoader: standardLoader,
|
|
5942
6702
|
documentLoader: ctx.documentLoader,
|
|
5943
6703
|
keyCache,
|
|
5944
6704
|
meterProvider,
|
|
@@ -5976,7 +6736,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
5976
6736
|
});
|
|
5977
6737
|
}
|
|
5978
6738
|
}
|
|
5979
|
-
if (httpSigKey != null && !await require_portable_collection.doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
6739
|
+
if (httpSigKey != null && !actorless && !await require_portable_collection.doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
5980
6740
|
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
5981
6741
|
observation.authentication = {
|
|
5982
6742
|
status: "rejected",
|
|
@@ -6002,6 +6762,66 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
6002
6762
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
6003
6763
|
});
|
|
6004
6764
|
}
|
|
6765
|
+
if (httpSigKey != null && !actorless && activity.actorId != null) requestSigners = [{
|
|
6766
|
+
kind: "actor",
|
|
6767
|
+
id: activity.actorId,
|
|
6768
|
+
mechanism: "httpSignatures",
|
|
6769
|
+
keyId: httpSigKey.id
|
|
6770
|
+
}];
|
|
6771
|
+
if (actorless) {
|
|
6772
|
+
const check = checkActorlessActivity(activity, {
|
|
6773
|
+
verified: true,
|
|
6774
|
+
requestSigners,
|
|
6775
|
+
documentSigners,
|
|
6776
|
+
originAuthenticated: [],
|
|
6777
|
+
expandedActivity: [],
|
|
6778
|
+
baseUrl: null
|
|
6779
|
+
});
|
|
6780
|
+
if (check.status === "noAuthenticatedPrincipal") {
|
|
6781
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
6782
|
+
observation.authentication = {
|
|
6783
|
+
status: "rejected",
|
|
6784
|
+
reason: { type: "noAuthenticatedPrincipal" }
|
|
6785
|
+
};
|
|
6786
|
+
logger.error("The activity {activityId} declares no actor, and no principal is authenticated for it.", {
|
|
6787
|
+
activity: json,
|
|
6788
|
+
recipient,
|
|
6789
|
+
activityId: activity.id?.href
|
|
6790
|
+
});
|
|
6791
|
+
span.setStatus({
|
|
6792
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
6793
|
+
message: "No principal is authenticated for the activity."
|
|
6794
|
+
});
|
|
6795
|
+
return new Response("No principal is authenticated for the activity.", {
|
|
6796
|
+
status: 401,
|
|
6797
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
6798
|
+
});
|
|
6799
|
+
} else if (check.status === "activityOriginMismatch") {
|
|
6800
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
6801
|
+
observation.authentication = {
|
|
6802
|
+
status: "rejected",
|
|
6803
|
+
reason: {
|
|
6804
|
+
type: "activityOriginMismatch",
|
|
6805
|
+
activityId: new URL(activity.id.href),
|
|
6806
|
+
actorIds: check.principals.map((principal) => principal.id)
|
|
6807
|
+
}
|
|
6808
|
+
};
|
|
6809
|
+
logger.error("The activity ({activityId}) does not have the same origin as the principals that authenticated it.", {
|
|
6810
|
+
activity: json,
|
|
6811
|
+
recipient,
|
|
6812
|
+
activityId: activity.id?.href,
|
|
6813
|
+
principals: check.principals.map((principal) => principal.id.href)
|
|
6814
|
+
});
|
|
6815
|
+
span.setStatus({
|
|
6816
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
6817
|
+
message: `The activity (${activity.id?.href}) does not have the same origin as the principals that authenticated it.`
|
|
6818
|
+
});
|
|
6819
|
+
return new Response("The activity and its signers do not have the same origin.", {
|
|
6820
|
+
status: 401,
|
|
6821
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
6822
|
+
});
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6005
6825
|
if (!skipSignatureVerification) {
|
|
6006
6826
|
const compoundJson = Array.isArray(json) ? json : jsonWithoutSig;
|
|
6007
6827
|
if (inspectCompoundPortableObjectApplicability(compoundJson, INBOX_COMPOUND_PROOF_LIMITS) !== "absent") {
|
|
@@ -6061,7 +6881,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
6061
6881
|
return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
|
|
6062
6882
|
}
|
|
6063
6883
|
}
|
|
6064
|
-
if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
|
|
6884
|
+
if (!skipSignatureVerification && !actorless && activity.id != null && !actorsHaveSameOrigin(activity)) {
|
|
6065
6885
|
observation.authentication = {
|
|
6066
6886
|
status: "rejected",
|
|
6067
6887
|
reason: {
|
|
@@ -6090,7 +6910,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
6090
6910
|
attempts: observation.verification.attempts.filter((attempt) => attempt.status === "verified")
|
|
6091
6911
|
};
|
|
6092
6912
|
observation.stage = "dispatch";
|
|
6093
|
-
if (activity.actorId == null) {
|
|
6913
|
+
if (!actorless && activity.actorId == null) {
|
|
6094
6914
|
observation.result = {
|
|
6095
6915
|
disposition: "rejected",
|
|
6096
6916
|
reason: "missingActor"
|
|
@@ -6114,6 +6934,17 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
6114
6934
|
originalJson: json,
|
|
6115
6935
|
normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
|
|
6116
6936
|
ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
|
|
6937
|
+
authentication: createAuthenticationEvidence({
|
|
6938
|
+
verified: !skipSignatureVerification,
|
|
6939
|
+
requestSigners,
|
|
6940
|
+
documentSigners,
|
|
6941
|
+
originAuthenticated: [],
|
|
6942
|
+
expandedActivity: deepFreeze(view.expanded),
|
|
6943
|
+
baseUrl: null
|
|
6944
|
+
}),
|
|
6945
|
+
verifiedView: view.view,
|
|
6946
|
+
replayable: view.replayable,
|
|
6947
|
+
actorless,
|
|
6117
6948
|
activity,
|
|
6118
6949
|
recipient,
|
|
6119
6950
|
inboxListeners,
|
|
@@ -6838,6 +7669,42 @@ const MIN_COMPONENTS = [
|
|
|
6838
7669
|
"@authority"
|
|
6839
7670
|
];
|
|
6840
7671
|
/**
|
|
7672
|
+
* Turns the signers of verified Object Integrity Proofs into principals.
|
|
7673
|
+
*/
|
|
7674
|
+
function toProofPrincipals(signers) {
|
|
7675
|
+
return signers.map((signer) => ({
|
|
7676
|
+
kind: signer.kind,
|
|
7677
|
+
id: signer.id,
|
|
7678
|
+
mechanism: "objectIntegrityProofs",
|
|
7679
|
+
keyId: signer.keyId
|
|
7680
|
+
}));
|
|
7681
|
+
}
|
|
7682
|
+
/**
|
|
7683
|
+
* Gets the principal of an HTTP Signature key, whose owner was confirmed by
|
|
7684
|
+
* the owner's own document, or by the signed document of a portable actor,
|
|
7685
|
+
* when the key was fetched.
|
|
7686
|
+
*/
|
|
7687
|
+
function getHttpKeyPrincipal(key) {
|
|
7688
|
+
const evidence = require_http.getVerifiedKeyOwnerEvidence(key);
|
|
7689
|
+
if (evidence != null) try {
|
|
7690
|
+
return {
|
|
7691
|
+
kind: "actor",
|
|
7692
|
+
id: (0, _fedify_vocab_runtime.parseIri)(evidence.ownerId),
|
|
7693
|
+
mechanism: "httpSignatures",
|
|
7694
|
+
keyId: key.id
|
|
7695
|
+
};
|
|
7696
|
+
} catch {
|
|
7697
|
+
return null;
|
|
7698
|
+
}
|
|
7699
|
+
if (key.ownerId == null || require_http.isPortableId(key.ownerId)) return null;
|
|
7700
|
+
return {
|
|
7701
|
+
kind: "actor",
|
|
7702
|
+
id: key.ownerId,
|
|
7703
|
+
mechanism: "httpSignatures",
|
|
7704
|
+
keyId: key.id
|
|
7705
|
+
};
|
|
7706
|
+
}
|
|
7707
|
+
/**
|
|
6841
7708
|
* Checks whether an HTTP Signature key is a key of an FEP-ef61 portable
|
|
6842
7709
|
* actor, i.e., its ID is a compatible identifier or an `ap:` or `ap+ef61:`
|
|
6843
7710
|
* URI, and it is owned by a portable actor, which only the portable actor's
|
|
@@ -9429,24 +10296,102 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9429
10296
|
let dispatched;
|
|
9430
10297
|
let parseInput = void 0;
|
|
9431
10298
|
let parseContextLoader = context.contextLoader;
|
|
10299
|
+
let authentication = getEmptyAuthenticationEvidence();
|
|
10300
|
+
let actorPresence = "present";
|
|
10301
|
+
if (message.verifiedView !== void 0) {
|
|
10302
|
+
const view = parseSerializedVerifiedActivityView(message.verifiedView);
|
|
10303
|
+
let restored;
|
|
10304
|
+
let restoredAuthentication = null;
|
|
10305
|
+
let failure;
|
|
10306
|
+
if (view == null) failure = /* @__PURE__ */ new TypeError("The queued activity has a malformed verified view.");
|
|
10307
|
+
else try {
|
|
10308
|
+
if (view.source === "normalizedActivity") require_http.assertSafeJsonLd(message.normalizedActivity);
|
|
10309
|
+
restored = await restoreVerifiedActivityView(view, message);
|
|
10310
|
+
} catch (error) {
|
|
10311
|
+
if (!(error instanceof ContextSnapshotMissError) && !isPermanentInboxParseError(error)) {
|
|
10312
|
+
await handleRetriableFailure(error);
|
|
10313
|
+
return;
|
|
10314
|
+
}
|
|
10315
|
+
failure = error;
|
|
10316
|
+
}
|
|
10317
|
+
if (restored != null) {
|
|
10318
|
+
const expandedActivity = deepFreeze(restored.expanded);
|
|
10319
|
+
restoredAuthentication = message.authentication === void 0 ? createAuthenticationEvidence({
|
|
10320
|
+
...getEmptyAuthenticationEvidence(),
|
|
10321
|
+
expandedActivity,
|
|
10322
|
+
baseUrl: restored.baseUrl
|
|
10323
|
+
}) : deserializeAuthentication(message.authentication, expandedActivity, restored.baseUrl);
|
|
10324
|
+
if (restoredAuthentication == null) failure = /* @__PURE__ */ new TypeError("The queued activity has malformed authentication evidence.");
|
|
10325
|
+
}
|
|
10326
|
+
if (failure != null || restored == null || restoredAuthentication == null) {
|
|
10327
|
+
await reportInboxError(failure);
|
|
10328
|
+
logger.error("Failed to replay the queued incoming activity:\n{error}", {
|
|
10329
|
+
error: failure,
|
|
10330
|
+
trial: message.attempt,
|
|
10331
|
+
activity: message.activity,
|
|
10332
|
+
recipient: message.identifier
|
|
10333
|
+
});
|
|
10334
|
+
listenerSpan.setStatus({
|
|
10335
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
10336
|
+
message: String(failure)
|
|
10337
|
+
});
|
|
10338
|
+
listenerSpan.end();
|
|
10339
|
+
return;
|
|
10340
|
+
}
|
|
10341
|
+
authentication = restoredAuthentication;
|
|
10342
|
+
actorPresence = getActorPresence(restored.expanded);
|
|
10343
|
+
const policy = view.policy;
|
|
10344
|
+
const replayLoader = createReleasableLoader(restored.loader, policy === "normalization" ? require_http.getNormalizationContextLoader(context.contextLoader) : require_http.wrapContextLoaderForJsonLd(context.contextLoader), policy);
|
|
10345
|
+
parseInput = restored.document;
|
|
10346
|
+
parseContextLoader = restored.loader;
|
|
10347
|
+
try {
|
|
10348
|
+
activity = await _fedify_vocab.Activity.fromJsonLd(restored.document, {
|
|
10349
|
+
...context,
|
|
10350
|
+
contextLoader: replayLoader.loader,
|
|
10351
|
+
...restored.baseUrl == null ? {} : { baseUrl: restored.baseUrl }
|
|
10352
|
+
});
|
|
10353
|
+
} catch (error) {
|
|
10354
|
+
if (isPermanentInboxParseError(error)) {
|
|
10355
|
+
await reportInboxError(error);
|
|
10356
|
+
logger.error("Failed to parse the queued incoming activity:\n{error}", {
|
|
10357
|
+
error,
|
|
10358
|
+
trial: message.attempt,
|
|
10359
|
+
activity: message.activity,
|
|
10360
|
+
recipient: message.identifier
|
|
10361
|
+
});
|
|
10362
|
+
listenerSpan.setStatus({
|
|
10363
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
10364
|
+
message: String(error)
|
|
10365
|
+
});
|
|
10366
|
+
listenerSpan.end();
|
|
10367
|
+
return;
|
|
10368
|
+
}
|
|
10369
|
+
await handleRetriableFailure(error);
|
|
10370
|
+
return;
|
|
10371
|
+
} finally {
|
|
10372
|
+
replayLoader.release();
|
|
10373
|
+
}
|
|
10374
|
+
}
|
|
9432
10375
|
try {
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
10376
|
+
if (activity == null) {
|
|
10377
|
+
const hasSignatureField = require_http.hasSignature(message.activity);
|
|
10378
|
+
const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
|
|
10379
|
+
const parseContext = hasSignatureField ? {
|
|
10380
|
+
...context,
|
|
10381
|
+
contextLoader: require_http.getNormalizationContextLoader(context.contextLoader)
|
|
10382
|
+
} : {
|
|
10383
|
+
...context,
|
|
10384
|
+
contextLoader: require_http.wrapContextLoaderForJsonLd(context.contextLoader)
|
|
10385
|
+
};
|
|
10386
|
+
parseContextLoader = parseContext.contextLoader;
|
|
10387
|
+
let normalizedActivity;
|
|
10388
|
+
if (shouldParseFromNormalizedSignedPayload) {
|
|
10389
|
+
normalizedActivity = message.normalizedActivity ?? await require_http.compactJsonLd(message.activity, context.contextLoader);
|
|
10390
|
+
require_http.assertSafeJsonLd(normalizedActivity);
|
|
10391
|
+
}
|
|
10392
|
+
parseInput = shouldParseFromNormalizedSignedPayload ? require_http.detachSignature(normalizedActivity) : hasSignatureField ? require_http.detachSignature(message.activity) : message.activity;
|
|
10393
|
+
activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
|
|
9447
10394
|
}
|
|
9448
|
-
parseInput = shouldParseFromNormalizedSignedPayload ? require_http.detachSignature(normalizedActivity) : hasSignatureField ? require_http.detachSignature(message.activity) : message.activity;
|
|
9449
|
-
activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
|
|
9450
10395
|
activityType = (0, _fedify_vocab.getTypeId)(activity).href;
|
|
9451
10396
|
span.setAttribute("activitypub.activity.type", activityType);
|
|
9452
10397
|
listenerSpan.setAttribute("activitypub.activity.type", activityType);
|
|
@@ -9455,7 +10400,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9455
10400
|
span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
9456
10401
|
listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
|
|
9457
10402
|
}
|
|
9458
|
-
|
|
10403
|
+
const actorless = actorPresence === "absent" && !this.skipSignatureVerification && authentication.verified && this.inboxListeners?.dispatchWithClass(activity)?.options.allowActorless === true && isActorlessActivityAuthenticated(activity, authentication);
|
|
10404
|
+
if (activity.actorId == null && !actorless) {
|
|
9459
10405
|
logger.error("Missing actor in queued incoming activity.", {
|
|
9460
10406
|
activityId: activity.id?.href,
|
|
9461
10407
|
activity: message.activity,
|
|
@@ -9469,7 +10415,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9469
10415
|
listenerSpan.end();
|
|
9470
10416
|
return;
|
|
9471
10417
|
}
|
|
9472
|
-
if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
|
|
10418
|
+
if (!this.skipSignatureVerification && !actorless && activity.id != null && !actorsHaveSameOrigin(activity)) {
|
|
9473
10419
|
logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
|
|
9474
10420
|
activityId: activity.id.href,
|
|
9475
10421
|
actorIds: activity.actorIds.map((actor) => actor.href),
|
|
@@ -9484,22 +10430,18 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9484
10430
|
listenerSpan.end();
|
|
9485
10431
|
return;
|
|
9486
10432
|
}
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
9500
|
-
listenerSpan.end();
|
|
9501
|
-
return;
|
|
9502
|
-
}
|
|
10433
|
+
const parsedActivity = activity;
|
|
10434
|
+
const parsedActivityType = activityType;
|
|
10435
|
+
cacheKey = await getInboxIdempotencyKey(this.idempotencyStrategy, () => context.toInboxContext(message.identifier, message.activity, parsedActivity.id?.href, parsedActivityType, { authentication }), activity, context.origin, message.identifier, this.kvPrefixes.activityIdempotence);
|
|
10436
|
+
if (await isInboxActivityProcessed(this.kv, cacheKey, activity, context.origin, this.kvPrefixes.activityIdempotence)) {
|
|
10437
|
+
logger.debug("Activity {activityId} has already been processed.", {
|
|
10438
|
+
activityId: activity.id?.href,
|
|
10439
|
+
activity: message.activity,
|
|
10440
|
+
recipient: message.identifier
|
|
10441
|
+
});
|
|
10442
|
+
require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
10443
|
+
listenerSpan.end();
|
|
10444
|
+
return;
|
|
9503
10445
|
}
|
|
9504
10446
|
dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
|
9505
10447
|
} catch (error) {
|
|
@@ -9558,7 +10500,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9558
10500
|
try {
|
|
9559
10501
|
const started = performance.now();
|
|
9560
10502
|
try {
|
|
9561
|
-
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
|
|
10503
|
+
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType, { authentication }), activity);
|
|
9562
10504
|
} finally {
|
|
9563
10505
|
this.metrics.recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
|
|
9564
10506
|
}
|
|
@@ -10865,7 +11807,7 @@ var ContextImpl = class ContextImpl {
|
|
|
10865
11807
|
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
10866
11808
|
});
|
|
10867
11809
|
}
|
|
10868
|
-
toInboxContext(recipient, activity, activityId, activityType) {
|
|
11810
|
+
toInboxContext(recipient, activity, activityId, activityType, metadata) {
|
|
10869
11811
|
return new InboxContextImpl(recipient, activity, activityId, activityType, {
|
|
10870
11812
|
url: this.url,
|
|
10871
11813
|
federation: this.federation,
|
|
@@ -10873,7 +11815,7 @@ var ContextImpl = class ContextImpl {
|
|
|
10873
11815
|
documentLoader: this.documentLoader,
|
|
10874
11816
|
contextLoader: this.contextLoader,
|
|
10875
11817
|
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
10876
|
-
});
|
|
11818
|
+
}, metadata?.authentication);
|
|
10877
11819
|
}
|
|
10878
11820
|
toOutboxContext(identifier, activity, activityId, activityType) {
|
|
10879
11821
|
return new OutboxContextImpl(identifier, activity, activityId, activityType, {
|
|
@@ -11488,12 +12430,20 @@ var ContextImpl = class ContextImpl {
|
|
|
11488
12430
|
});
|
|
11489
12431
|
}
|
|
11490
12432
|
async routeActivityInternal(recipient, activity, options = {}, span) {
|
|
12433
|
+
const snapshot = createContextSnapshot(options.contextLoader ?? this.contextLoader);
|
|
12434
|
+
try {
|
|
12435
|
+
return await this.#routeActivityWithSnapshot(recipient, activity, options, span, snapshot);
|
|
12436
|
+
} finally {
|
|
12437
|
+
snapshot.release();
|
|
12438
|
+
}
|
|
12439
|
+
}
|
|
12440
|
+
async #routeActivityWithSnapshot(recipient, activity, options, span, snapshot) {
|
|
11491
12441
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
11492
12442
|
"fedify",
|
|
11493
12443
|
"federation",
|
|
11494
12444
|
"inbox"
|
|
11495
12445
|
]);
|
|
11496
|
-
const contextLoader =
|
|
12446
|
+
const contextLoader = snapshot.loader("standard");
|
|
11497
12447
|
let json = await activity.toJsonLd({ contextLoader });
|
|
11498
12448
|
const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
|
|
11499
12449
|
documentLoader: this.documentLoader,
|
|
@@ -11501,13 +12451,19 @@ var ContextImpl = class ContextImpl {
|
|
|
11501
12451
|
tracerProvider: this.tracerProvider,
|
|
11502
12452
|
keyTtl: this.federation.publicKeyTtl
|
|
11503
12453
|
});
|
|
11504
|
-
|
|
12454
|
+
let documentSigners = [];
|
|
12455
|
+
let originAuthenticated = [];
|
|
12456
|
+
let viewSource;
|
|
12457
|
+
let fetchedView;
|
|
12458
|
+
const document = require_http.detachSignature(json);
|
|
12459
|
+
const verified = await require_http.verifyObjectDetailed(_fedify_vocab.Activity, document, {
|
|
11505
12460
|
contextLoader,
|
|
11506
12461
|
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
11507
12462
|
meterProvider: this.meterProvider,
|
|
11508
12463
|
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
11509
12464
|
keyCache
|
|
11510
|
-
})
|
|
12465
|
+
});
|
|
12466
|
+
if (verified.status !== "verified") {
|
|
11511
12467
|
logger.debug("Object Integrity Proofs are not verified.", {
|
|
11512
12468
|
recipient,
|
|
11513
12469
|
activity: json
|
|
@@ -11519,8 +12475,29 @@ var ContextImpl = class ContextImpl {
|
|
|
11519
12475
|
});
|
|
11520
12476
|
return false;
|
|
11521
12477
|
}
|
|
11522
|
-
|
|
11523
|
-
|
|
12478
|
+
let candidate;
|
|
12479
|
+
let candidateView;
|
|
12480
|
+
const verifications = /* @__PURE__ */ new WeakMap();
|
|
12481
|
+
const fetched = await this.lookupObject(activity.id, {
|
|
12482
|
+
...options,
|
|
12483
|
+
contextLoader,
|
|
12484
|
+
verifyPortableObject: async (document, verifierOptions) => {
|
|
12485
|
+
const result = await this.verifyPortableObject(document, verifierOptions);
|
|
12486
|
+
if (document != null && typeof document === "object") verifications.set(document, result);
|
|
12487
|
+
return result;
|
|
12488
|
+
},
|
|
12489
|
+
[_fedify_vocab.lookupObserver]: { accept: async (accepted) => {
|
|
12490
|
+
candidate = accepted;
|
|
12491
|
+
candidateView = await buildVerifiedActivityView(createContextSnapshot(accepted.contextLoader), {
|
|
12492
|
+
policy: "standard",
|
|
12493
|
+
source: "activity",
|
|
12494
|
+
document: require_http.detachSignature(accepted.document),
|
|
12495
|
+
baseUrl: accepted.baseUrl
|
|
12496
|
+
});
|
|
12497
|
+
return true;
|
|
12498
|
+
} }
|
|
12499
|
+
});
|
|
12500
|
+
if (fetched == null || candidate == null || candidateView == null) {
|
|
11524
12501
|
logger.debug("Failed to fetch the remote activity object {activityId}.", {
|
|
11525
12502
|
recipient,
|
|
11526
12503
|
activity: json,
|
|
@@ -11556,11 +12533,50 @@ var ContextImpl = class ContextImpl {
|
|
|
11556
12533
|
}
|
|
11557
12534
|
logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
|
|
11558
12535
|
activity = fetched;
|
|
11559
|
-
json =
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
12536
|
+
json = candidate.document;
|
|
12537
|
+
fetchedView = candidateView;
|
|
12538
|
+
viewSource = {
|
|
12539
|
+
policy: "standard",
|
|
12540
|
+
source: "activity",
|
|
12541
|
+
document: require_http.detachSignature(candidate.document),
|
|
12542
|
+
baseUrl: candidate.baseUrl
|
|
12543
|
+
};
|
|
12544
|
+
if (require_http.isPortableId(fetched.id)) {
|
|
12545
|
+
const result = verifications.get(candidate.document);
|
|
12546
|
+
if (result?.verified === true && result.method === "proof") documentSigners = result.keys.flatMap((key) => {
|
|
12547
|
+
const signer = require_http.getProofSigner(key);
|
|
12548
|
+
return signer == null ? [] : [{
|
|
12549
|
+
kind: signer.kind,
|
|
12550
|
+
id: signer.id,
|
|
12551
|
+
mechanism: "objectIntegrityProofs",
|
|
12552
|
+
keyId: signer.keyId
|
|
12553
|
+
}];
|
|
12554
|
+
});
|
|
12555
|
+
} else originAuthenticated = fetched.actorIds.map((actorId) => ({
|
|
12556
|
+
kind: "actor",
|
|
12557
|
+
id: actorId,
|
|
12558
|
+
mechanism: "origin",
|
|
12559
|
+
keyId: null
|
|
12560
|
+
}));
|
|
12561
|
+
} else {
|
|
12562
|
+
logger.debug("Object Integrity Proofs are verified.", {
|
|
12563
|
+
recipient,
|
|
12564
|
+
activity: json
|
|
12565
|
+
});
|
|
12566
|
+
activity = verified.object;
|
|
12567
|
+
documentSigners = verified.signers.map((signer) => ({
|
|
12568
|
+
kind: signer.kind,
|
|
12569
|
+
id: signer.id,
|
|
12570
|
+
mechanism: "objectIntegrityProofs",
|
|
12571
|
+
keyId: signer.keyId
|
|
12572
|
+
}));
|
|
12573
|
+
viewSource = {
|
|
12574
|
+
policy: "standard",
|
|
12575
|
+
source: "activity",
|
|
12576
|
+
document,
|
|
12577
|
+
baseUrl: null
|
|
12578
|
+
};
|
|
12579
|
+
}
|
|
11564
12580
|
if (activity.actorId == null) {
|
|
11565
12581
|
logger.debug("Activity is missing an actor; unable to verify.", {
|
|
11566
12582
|
recipient,
|
|
@@ -11575,10 +12591,21 @@ var ContextImpl = class ContextImpl {
|
|
|
11575
12591
|
});
|
|
11576
12592
|
return false;
|
|
11577
12593
|
}
|
|
12594
|
+
const view = fetchedView ?? await buildVerifiedActivityView(snapshot, viewSource);
|
|
11578
12595
|
const routeResult = await routeActivity({
|
|
11579
12596
|
context: this,
|
|
11580
12597
|
json,
|
|
11581
12598
|
ldSignatureVerified: false,
|
|
12599
|
+
authentication: createAuthenticationEvidence({
|
|
12600
|
+
verified: true,
|
|
12601
|
+
requestSigners: [],
|
|
12602
|
+
documentSigners,
|
|
12603
|
+
originAuthenticated,
|
|
12604
|
+
expandedActivity: deepFreeze(view.expanded),
|
|
12605
|
+
baseUrl: viewSource.baseUrl
|
|
12606
|
+
}),
|
|
12607
|
+
verifiedView: view.view,
|
|
12608
|
+
replayable: view.replayable,
|
|
11582
12609
|
activity,
|
|
11583
12610
|
recipient,
|
|
11584
12611
|
inboxListeners: this.federation.inboxListeners,
|
|
@@ -11929,12 +12956,14 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
|
11929
12956
|
activity;
|
|
11930
12957
|
activityId;
|
|
11931
12958
|
activityType;
|
|
11932
|
-
|
|
12959
|
+
authentication;
|
|
12960
|
+
constructor(recipient, activity, activityId, activityType, options, authentication) {
|
|
11933
12961
|
super(options);
|
|
11934
12962
|
this.recipient = recipient;
|
|
11935
12963
|
this.activity = activity;
|
|
11936
12964
|
this.activityId = activityId;
|
|
11937
12965
|
this.activityType = activityType;
|
|
12966
|
+
this.authentication = authentication ?? getEmptyAuthenticationEvidence();
|
|
11938
12967
|
}
|
|
11939
12968
|
clone(data) {
|
|
11940
12969
|
return new InboxContextImpl(this.recipient, this.activity, this.activityId, this.activityType, {
|
|
@@ -11944,7 +12973,7 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
|
11944
12973
|
documentLoader: this.documentLoader,
|
|
11945
12974
|
contextLoader: this.contextLoader,
|
|
11946
12975
|
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
11947
|
-
});
|
|
12976
|
+
}, this.authentication);
|
|
11948
12977
|
}
|
|
11949
12978
|
forwardActivity(forwarder, recipients, options) {
|
|
11950
12979
|
return forwardActivity(this, "inbox", forwarder, recipients, options).then(() => void 0);
|
|
@@ -12237,6 +13266,11 @@ var ParallelMessageQueue = class ParallelMessageQueue {
|
|
|
12237
13266
|
* @since 2.4.0
|
|
12238
13267
|
*/
|
|
12239
13268
|
atomicEnqueueMany;
|
|
13269
|
+
/**
|
|
13270
|
+
* Inherits the message size limit from the wrapped queue.
|
|
13271
|
+
* @since 2.5.0
|
|
13272
|
+
*/
|
|
13273
|
+
maxMessageSize;
|
|
12240
13274
|
getDepth;
|
|
12241
13275
|
/**
|
|
12242
13276
|
* Tracks which ordering keys are currently being processed to ensure
|
|
@@ -12262,6 +13296,7 @@ var ParallelMessageQueue = class ParallelMessageQueue {
|
|
|
12262
13296
|
this.workers = workers;
|
|
12263
13297
|
this.nativeRetrial = queue.nativeRetrial;
|
|
12264
13298
|
this.nativeDeduplication = queue.nativeDeduplication;
|
|
13299
|
+
this.maxMessageSize = queue.maxMessageSize;
|
|
12265
13300
|
this.atomicEnqueueMany = queue.enqueueMany == null ? false : queue.atomicEnqueueMany;
|
|
12266
13301
|
if (queue.getDepth != null) this.getDepth = () => queue.getDepth();
|
|
12267
13302
|
}
|
|
@@ -12567,6 +13602,12 @@ Object.defineProperty(exports, "handleWebFinger", {
|
|
|
12567
13602
|
return handleWebFinger;
|
|
12568
13603
|
}
|
|
12569
13604
|
});
|
|
13605
|
+
Object.defineProperty(exports, "isActorAuthenticated", {
|
|
13606
|
+
enumerable: true,
|
|
13607
|
+
get: function() {
|
|
13608
|
+
return isActorAuthenticated;
|
|
13609
|
+
}
|
|
13610
|
+
});
|
|
12570
13611
|
Object.defineProperty(exports, "normalizeCircuitBreakerOptions", {
|
|
12571
13612
|
enumerable: true,
|
|
12572
13613
|
get: function() {
|