@fedify/fedify 2.0.36-dev.2530 → 2.0.36-dev.2598

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/{builder-BBdPdHYx.mjs → builder-B5hI5Xsn.mjs} +3 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-CtG6TwTB.mjs → deno--OYa_lxj.mjs} +1 -1
  4. package/dist/{docloader-BxW_P_Rq.mjs → docloader-BXR2f83H.mjs} +2 -2
  5. package/dist/federation/builder.test.mjs +1 -1
  6. package/dist/federation/handler.test.mjs +8 -8
  7. package/dist/federation/idempotency.test.mjs +2 -2
  8. package/dist/federation/inbox.test.mjs +1 -1
  9. package/dist/federation/middleware.test.mjs +381 -8
  10. package/dist/federation/mod.cjs +1 -1
  11. package/dist/federation/mod.js +1 -1
  12. package/dist/federation/send.test.mjs +3 -3
  13. package/dist/federation/temporal.test.mjs +2 -1
  14. package/dist/federation/webfinger.test.mjs +1 -1
  15. package/dist/{http-CUOxcDgn.mjs → http-C3d2DXwF.mjs} +2 -2
  16. package/dist/{http-DMRapH1d.cjs → http-CO_IVq7x.cjs} +1 -1
  17. package/dist/{http-YAfhvSad.js → http-CfyVeGgF.js} +1 -1
  18. package/dist/{inbox-DtThTl1D.mjs → inbox-huUE3IP8.mjs} +1 -1
  19. package/dist/{key-QamJxksu.mjs → key-D5e9c_yV.mjs} +1 -1
  20. package/dist/{kv-cache-Di_SbH_3.cjs → kv-cache-BMlo_kyv.cjs} +1 -1
  21. package/dist/{kv-cache-C0MyXzft.js → kv-cache-Zw0soD-7.js} +1 -1
  22. package/dist/{ld-B0eNy8WK.mjs → ld-9qfz9rP_.mjs} +99 -10
  23. package/dist/{middleware-D3omYPtg.cjs → middleware-Ad6kTSBf.cjs} +1 -1
  24. package/dist/{middleware-D1zogq17.js → middleware-BKs_a9P1.js} +111 -15
  25. package/dist/{middleware-II_yrP7N.mjs → middleware-BhYpLGA_.mjs} +119 -23
  26. package/dist/{middleware-BKECCXIs.mjs → middleware-CT1Lbohd.mjs} +1 -1
  27. package/dist/{middleware-CUFw4tT0.cjs → middleware-CXoG7ZAW.cjs} +112 -16
  28. package/dist/{mod-DVPS4yam.d.cts → mod-BA6vqC2S.d.cts} +19 -7
  29. package/dist/{mod-BS-jg3Wx.d.ts → mod-CL9Ep0uo.d.ts} +19 -7
  30. package/dist/mod.cjs +4 -4
  31. package/dist/mod.d.cts +1 -1
  32. package/dist/mod.d.ts +1 -1
  33. package/dist/mod.js +4 -4
  34. package/dist/nodeinfo/handler.test.mjs +1 -1
  35. package/dist/{owner-lqq4KXYV.mjs → owner-R4ffU9Ht.mjs} +2 -2
  36. package/dist/{proof-CqOINd75.cjs → proof-BrreJTRF.cjs} +131 -18
  37. package/dist/{proof-DMYwiD7d.js → proof-CE8nwTF6.js} +132 -19
  38. package/dist/{proof-Bf8rYD7r.mjs → proof-CiBs9Q4l.mjs} +36 -12
  39. package/dist/{send-1tVKmxvT.mjs → send-DpvZgVSA.mjs} +2 -2
  40. package/dist/sig/http.test.mjs +2 -2
  41. package/dist/sig/key.test.mjs +2 -2
  42. package/dist/sig/ld.test.mjs +198 -4
  43. package/dist/sig/mod.cjs +2 -2
  44. package/dist/sig/mod.d.cts +1 -1
  45. package/dist/sig/mod.d.ts +1 -1
  46. package/dist/sig/mod.js +2 -2
  47. package/dist/sig/owner.test.mjs +1 -1
  48. package/dist/sig/proof.test.mjs +71 -2
  49. package/dist/{temporal-D6JQXNOJ.mjs → temporal-Dd-J410Z.mjs} +1 -1
  50. package/dist/utils/docloader.test.mjs +2 -2
  51. package/dist/utils/mod.cjs +1 -1
  52. package/dist/utils/mod.js +1 -1
  53. package/package.json +5 -5
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-DMRapH1d.cjs");
5
+ const require_http = require("./http-CO_IVq7x.cjs");
6
6
  const require_types = require("./types-DOvyymM4.cjs");
7
- const require_proof = require("./proof-CqOINd75.cjs");
8
- const require_kv_cache = require("./kv-cache-Di_SbH_3.cjs");
7
+ const require_proof = require("./proof-BrreJTRF.cjs");
8
+ const require_kv_cache = require("./kv-cache-BMlo_kyv.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -351,7 +351,7 @@ var FederationBuilderImpl = class {
351
351
  this.collectionTypeIds = {};
352
352
  }
353
353
  async build(options) {
354
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-D3omYPtg.cjs"));
354
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-Ad6kTSBf.cjs"));
355
355
  const f = new FederationImpl(options);
356
356
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
357
357
  f.router = this.router.clone();
@@ -2996,7 +2996,8 @@ var FederationImpl = class extends FederationBuilderImpl {
2996
2996
  await this.sendActivity(keys, message.inboxes, activity, {
2997
2997
  collectionSync: message.collectionSync,
2998
2998
  orderingKey: message.orderingKey,
2999
- context
2999
+ context,
3000
+ activityJsonLd: hasTopLevelProof(message.activity) ? message.activity : void 0
3000
3001
  });
3001
3002
  }
3002
3003
  async #listenOutboxMessage(_, message, span) {
@@ -3403,7 +3404,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3403
3404
  proofCreated = true;
3404
3405
  }
3405
3406
  }
3406
- let jsonLd = await activity.toJsonLd({
3407
+ let jsonLd = options.activityJsonLd != null ? require_proof.detachSignature(options.activityJsonLd) : await activity.toJsonLd({
3407
3408
  format: "compact",
3408
3409
  contextLoader
3409
3410
  });
@@ -4120,7 +4121,17 @@ var ContextImpl = class ContextImpl {
4120
4121
  allowPrivateAddress: this.federation.allowPrivateAddress
4121
4122
  });
4122
4123
  }
4123
- sendActivity(sender, recipients, activity, options = {}) {
4124
+ async sendActivity(sender, recipients, activity, options = {}) {
4125
+ let activityJsonLd = void 0;
4126
+ let hasProof = activity.proofIds.length > 0;
4127
+ if (!hasProof) for await (const _ of activity.getProofs({ contextLoader: this.contextLoader })) {
4128
+ hasProof = true;
4129
+ break;
4130
+ }
4131
+ if (hasProof) {
4132
+ activityJsonLd = await activity.toJsonLd({ contextLoader: this.contextLoader });
4133
+ if (!hasTopLevelProof(activityJsonLd)) activityJsonLd = void 0;
4134
+ }
4124
4135
  return this.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
4125
4136
  kind: this.federation.outboxQueue == null || options.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
4126
4137
  attributes: {
@@ -4133,7 +4144,7 @@ var ContextImpl = class ContextImpl {
4133
4144
  }, async (span) => {
4134
4145
  try {
4135
4146
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
4136
- await this.sendActivityInternal(sender, recipients, activity, options, span);
4147
+ await this.sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd);
4137
4148
  } catch (e) {
4138
4149
  span.setStatus({
4139
4150
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -4145,7 +4156,7 @@ var ContextImpl = class ContextImpl {
4145
4156
  }
4146
4157
  });
4147
4158
  }
4148
- async sendActivityInternal(sender, recipients, activity, options, span) {
4159
+ async sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd) {
4149
4160
  const logger = (0, _logtape_logtape.getLogger)([
4150
4161
  "fedify",
4151
4162
  "federation",
@@ -4192,14 +4203,23 @@ var ContextImpl = class ContextImpl {
4192
4203
  if (!(error instanceof RouterError)) throw error;
4193
4204
  }
4194
4205
  } else expandedRecipients = [recipients];
4195
- const opts = {
4196
- context: this,
4197
- orderingKey: options.orderingKey,
4198
- collectionSync,
4199
- immediate: options.immediate
4200
- };
4201
4206
  span.setAttribute("activitypub.inboxes", expandedRecipients.length);
4202
4207
  for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
4208
+ let contextMatches = false;
4209
+ if (activityJsonLd != null) {
4210
+ const capturedJsonLd = activityJsonLd;
4211
+ const transformedJsonLd = await activity.toJsonLd({
4212
+ format: "compact",
4213
+ contextLoader: this.contextLoader
4214
+ });
4215
+ const contentChanged = getContentFields(transformedJsonLd) !== getContentFields(capturedJsonLd);
4216
+ contextMatches = getContextFields(transformedJsonLd) === getContextFields(capturedJsonLd);
4217
+ const proofsChanged = getProofFields(transformedJsonLd) !== getProofFields(capturedJsonLd);
4218
+ if (contentChanged || !contextMatches && proofsChanged) {
4219
+ activity = activity.clone({ proofs: await getUncapturedProofs(activity, capturedJsonLd, this.contextLoader) });
4220
+ activityJsonLd = void 0;
4221
+ } else if (proofsChanged) activityJsonLd = void 0;
4222
+ }
4203
4223
  span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
4204
4224
  if (activity.actorId == null) {
4205
4225
  logger.error("Activity {activityId} to send does not have an actor.", {
@@ -4210,14 +4230,27 @@ var ContextImpl = class ContextImpl {
4210
4230
  }
4211
4231
  if (actorKeyPairs != null) {
4212
4232
  const contextLoader = this.contextLoader;
4233
+ if (actorKeyPairs.some((kp) => kp.privateKey.algorithm.name === "Ed25519" && kp.multikey.id != null)) {
4234
+ if (activityJsonLd != null && !contextMatches) activity = activity.clone({ proofs: [] });
4235
+ }
4236
+ let proofAdded = false;
4213
4237
  for (const kp of actorKeyPairs) {
4214
4238
  if (kp.privateKey.algorithm.name !== "Ed25519" || kp.multikey.id == null) continue;
4215
4239
  activity = await require_proof.signObject(activity, kp.privateKey, kp.multikey.id, {
4216
4240
  contextLoader,
4217
4241
  tracerProvider: this.tracerProvider
4218
4242
  });
4243
+ proofAdded = true;
4219
4244
  }
4245
+ if (proofAdded) activityJsonLd = void 0;
4220
4246
  }
4247
+ const opts = {
4248
+ context: this,
4249
+ orderingKey: options.orderingKey,
4250
+ collectionSync,
4251
+ immediate: options.immediate,
4252
+ activityJsonLd
4253
+ };
4221
4254
  const inboxes = extractInboxes({
4222
4255
  recipients: expandedRecipients,
4223
4256
  preferSharedInbox: options.preferSharedInbox,
@@ -4247,7 +4280,7 @@ var ContextImpl = class ContextImpl {
4247
4280
  actorIds: [...actorIds],
4248
4281
  sharedInbox
4249
4282
  }])),
4250
- activity: await activity.toJsonLd({
4283
+ activity: activityJsonLd != null ? require_proof.detachSignature(activityJsonLd) : await activity.toJsonLd({
4251
4284
  format: "compact",
4252
4285
  contextLoader: this.contextLoader
4253
4286
  }),
@@ -4733,6 +4766,69 @@ var KvSpecDeterminer = class {
4733
4766
  function notFound(_request) {
4734
4767
  return new Response("Not Found", { status: 404 });
4735
4768
  }
4769
+ function hasTopLevelProof(value) {
4770
+ return typeof value === "object" && value != null && !Array.isArray(value) && globalThis.Object.prototype.hasOwnProperty.call(value, "proof");
4771
+ }
4772
+ function getContentFields(value) {
4773
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4774
+ const record = { ...value };
4775
+ delete record["@context"];
4776
+ delete record.proof;
4777
+ delete record.signature;
4778
+ return JSON.stringify(sortJsonKeys(record));
4779
+ }
4780
+ function getContextFields(value) {
4781
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4782
+ const context = value["@context"];
4783
+ return JSON.stringify(sortJsonKeys(context ?? null));
4784
+ }
4785
+ function getProofEntries(value) {
4786
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return [];
4787
+ const proof = value.proof;
4788
+ if (proof == null) return [];
4789
+ return Array.isArray(proof) ? proof : [proof];
4790
+ }
4791
+ function normalizeProofEntry(proof) {
4792
+ const stripped = stripContexts(proof);
4793
+ if (typeof stripped !== "object" || stripped == null || Array.isArray(stripped)) return stripped;
4794
+ const record = { ...stripped };
4795
+ if (typeof record.created === "string") try {
4796
+ record.created = Temporal.Instant.from(record.created).toString();
4797
+ } catch {}
4798
+ return record;
4799
+ }
4800
+ function getProofEntryFields(proof) {
4801
+ return JSON.stringify(sortJsonKeys(normalizeProofEntry(proof)));
4802
+ }
4803
+ function getProofFields(value) {
4804
+ return JSON.stringify(getProofEntries(value).map(getProofEntryFields).sort());
4805
+ }
4806
+ async function getUncapturedProofs(activity, capturedJsonLd, contextLoader) {
4807
+ const capturedProofs = new Set(getProofEntries(capturedJsonLd).map(getProofEntryFields));
4808
+ const retainedProofs = [];
4809
+ for await (const proof of activity.getProofs({ contextLoader })) {
4810
+ const proofJsonLd = await proof.toJsonLd({
4811
+ format: "compact",
4812
+ contextLoader
4813
+ });
4814
+ if (!capturedProofs.has(getProofEntryFields(proofJsonLd))) retainedProofs.push(proof);
4815
+ }
4816
+ return retainedProofs;
4817
+ }
4818
+ function stripContexts(value) {
4819
+ if (Array.isArray(value)) return value.map(stripContexts);
4820
+ if (typeof value !== "object" || value == null) return value;
4821
+ const record = { ...value };
4822
+ delete record["@context"];
4823
+ delete record.signature;
4824
+ for (const key of globalThis.Object.keys(record)) record[key] = stripContexts(record[key]);
4825
+ return record;
4826
+ }
4827
+ function sortJsonKeys(value) {
4828
+ if (Array.isArray(value)) return value.map(sortJsonKeys);
4829
+ if (typeof value !== "object" || value == null) return value;
4830
+ return globalThis.Object.fromEntries(globalThis.Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sortJsonKeys(item)]));
4831
+ }
4736
4832
  function notAcceptable(_request) {
4737
4833
  return new Response("Not Acceptable", {
4738
4834
  status: 406,
@@ -10,7 +10,7 @@ import { TracerProvider } from "@opentelemetry/api";
10
10
  * @since 1.0.0
11
11
  */
12
12
  interface Signature {
13
- "@context"?: "https://w3id.org/identity/v1";
13
+ "@context"?: "https://w3id.org/identity/v1" | "https://w3id.org/security/v1";
14
14
  type: "RsaSignature2017";
15
15
  id?: string;
16
16
  creator: string;
@@ -19,16 +19,26 @@ interface Signature {
19
19
  }
20
20
  /**
21
21
  * Attaches a LD signature to the given JSON-LD document.
22
+ *
23
+ * When the document's context does not already define the `signature`
24
+ * property, the returned document adds a minimal inline context for it.
25
+ * Without that definition, expanding the attached signature would produce a
26
+ * blank node predicate, which is not valid RDF. Documents that already carry
27
+ * a `proof` are left unchanged, since a new context definition would invalidate
28
+ * the proof. Documents whose context includes an unrecognized remote context
29
+ * are also left unchanged, since that context may already define `signature`.
22
30
  * @param jsonLd The JSON-LD document to attach the signature to. It is not
23
31
  * modified.
24
32
  * @param signature The signature to attach.
25
33
  * @returns The JSON-LD document with the attached signature.
26
34
  * @throws {TypeError} If the input document is not a valid JSON-LD document.
35
+ * Top-level arrays are rejected, since a Linked Data
36
+ * Signature is attached to a single node.
27
37
  * @since 1.0.0
28
38
  */
29
39
  declare function attachSignature(jsonLd: unknown, signature: Signature): {
30
40
  signature: Signature;
31
- };
41
+ } & Record<string, unknown>;
32
42
  /**
33
43
  * Options for creating Linked Data Signatures.
34
44
  * @since 1.0.0
@@ -46,6 +56,12 @@ interface CreateSignatureOptions {
46
56
  }
47
57
  /**
48
58
  * Creates a LD signature for the given JSON-LD document.
59
+ *
60
+ * If the document's context does not define the `signature` property, the
61
+ * signature is computed over the document with a minimal inline definition
62
+ * added. Use {@link attachSignature} to attach the signature to that exact
63
+ * document. Documents that already carry a `proof`, or whose context includes
64
+ * an unrecognized remote context, are hashed unchanged.
49
65
  * @param jsonLd The JSON-LD document to sign.
50
66
  * @param privateKey The private key to sign the document.
51
67
  * @param keyId The ID of the public key that corresponds to the private key.
@@ -173,11 +189,7 @@ interface CreateProofOptions {
173
189
  * @throws {TypeError} If the private key is invalid or unsupported.
174
190
  * @since 0.10.0
175
191
  */
176
- declare function createProof(object: Object$1, privateKey: CryptoKey, keyId: URL, {
177
- contextLoader,
178
- context,
179
- created
180
- }?: CreateProofOptions): Promise<DataIntegrityProof>;
192
+ declare function createProof(object: Object$1, privateKey: CryptoKey, keyId: URL, options?: CreateProofOptions): Promise<DataIntegrityProof>;
181
193
  /**
182
194
  * Options for {@link signObject}.
183
195
  * @since 0.10.0
@@ -10,7 +10,7 @@ import { DocumentLoader } from "@fedify/vocab-runtime";
10
10
  * @since 1.0.0
11
11
  */
12
12
  interface Signature {
13
- "@context"?: "https://w3id.org/identity/v1";
13
+ "@context"?: "https://w3id.org/identity/v1" | "https://w3id.org/security/v1";
14
14
  type: "RsaSignature2017";
15
15
  id?: string;
16
16
  creator: string;
@@ -19,16 +19,26 @@ interface Signature {
19
19
  }
20
20
  /**
21
21
  * Attaches a LD signature to the given JSON-LD document.
22
+ *
23
+ * When the document's context does not already define the `signature`
24
+ * property, the returned document adds a minimal inline context for it.
25
+ * Without that definition, expanding the attached signature would produce a
26
+ * blank node predicate, which is not valid RDF. Documents that already carry
27
+ * a `proof` are left unchanged, since a new context definition would invalidate
28
+ * the proof. Documents whose context includes an unrecognized remote context
29
+ * are also left unchanged, since that context may already define `signature`.
22
30
  * @param jsonLd The JSON-LD document to attach the signature to. It is not
23
31
  * modified.
24
32
  * @param signature The signature to attach.
25
33
  * @returns The JSON-LD document with the attached signature.
26
34
  * @throws {TypeError} If the input document is not a valid JSON-LD document.
35
+ * Top-level arrays are rejected, since a Linked Data
36
+ * Signature is attached to a single node.
27
37
  * @since 1.0.0
28
38
  */
29
39
  declare function attachSignature(jsonLd: unknown, signature: Signature): {
30
40
  signature: Signature;
31
- };
41
+ } & Record<string, unknown>;
32
42
  /**
33
43
  * Options for creating Linked Data Signatures.
34
44
  * @since 1.0.0
@@ -46,6 +56,12 @@ interface CreateSignatureOptions {
46
56
  }
47
57
  /**
48
58
  * Creates a LD signature for the given JSON-LD document.
59
+ *
60
+ * If the document's context does not define the `signature` property, the
61
+ * signature is computed over the document with a minimal inline definition
62
+ * added. Use {@link attachSignature} to attach the signature to that exact
63
+ * document. Documents that already carry a `proof`, or whose context includes
64
+ * an unrecognized remote context, are hashed unchanged.
49
65
  * @param jsonLd The JSON-LD document to sign.
50
66
  * @param privateKey The private key to sign the document.
51
67
  * @param keyId The ID of the public key that corresponds to the private key.
@@ -173,11 +189,7 @@ interface CreateProofOptions {
173
189
  * @throws {TypeError} If the private key is invalid or unsupported.
174
190
  * @since 0.10.0
175
191
  */
176
- declare function createProof(object: Object$1, privateKey: CryptoKey, keyId: URL, {
177
- contextLoader,
178
- context,
179
- created
180
- }?: CreateProofOptions): Promise<DataIntegrityProof>;
192
+ declare function createProof(object: Object$1, privateKey: CryptoKey, keyId: URL, options?: CreateProofOptions): Promise<DataIntegrityProof>;
181
193
  /**
182
194
  * Options for {@link signObject}.
183
195
  * @since 0.10.0
package/dist/mod.cjs CHANGED
@@ -4,11 +4,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("./chunk-DDcVe30Y.cjs");
5
5
  const require_transformers = require("./transformers-NeAONrAq.cjs");
6
6
  require("./compat/mod.cjs");
7
- const require_http = require("./http-DMRapH1d.cjs");
8
- const require_middleware = require("./middleware-CUFw4tT0.cjs");
7
+ const require_http = require("./http-CO_IVq7x.cjs");
8
+ const require_middleware = require("./middleware-CXoG7ZAW.cjs");
9
9
  const require_types = require("./types-DOvyymM4.cjs");
10
- const require_proof = require("./proof-CqOINd75.cjs");
11
- const require_kv_cache = require("./kv-cache-Di_SbH_3.cjs");
10
+ const require_proof = require("./proof-BrreJTRF.cjs");
11
+ const require_kv_cache = require("./kv-cache-BMlo_kyv.cjs");
12
12
  const require_federation_mod = require("./federation/mod.cjs");
13
13
  require("./nodeinfo/mod.cjs");
14
14
  require("./runtime/mod.cjs");
package/dist/mod.d.cts CHANGED
@@ -6,7 +6,7 @@ import { $ as AuthorizePredicate, A as CreateExponentialBackoffPolicyOptions, B
6
6
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "./kv-CZKl1IUS.cjs";
7
7
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.cjs";
8
8
  import { n as handleWebFinger, t as WebFingerHandlerParameters } from "./mod-w1oyHseQ.cjs";
9
- import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "./mod-DVPS4yam.cjs";
9
+ import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "./mod-BA6vqC2S.cjs";
10
10
  import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-BPDfu4Ey.cjs";
11
11
  export * from "@fedify/vocab-runtime";
12
12
 
package/dist/mod.d.ts CHANGED
@@ -6,7 +6,7 @@ import { $ as AuthorizePredicate, A as CreateExponentialBackoffPolicyOptions, B
6
6
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "./kv-CxaI4eGo.js";
7
7
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.js";
8
8
  import { n as handleWebFinger, t as WebFingerHandlerParameters } from "./mod-I-34OMN3.js";
9
- import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "./mod-BS-jg3Wx.js";
9
+ import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "./mod-CL9Ep0uo.js";
10
10
  import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-DGP89q0u.js";
11
11
  export * from "@fedify/vocab-runtime";
12
12
 
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "./chunk-CRNNMoPX.js";
4
4
  import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "./transformers-BGMIq1cs.js";
5
5
  import "./compat/mod.js";
6
- import { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "./http-YAfhvSad.js";
7
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-D1zogq17.js";
6
+ import { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "./http-CfyVeGgF.js";
7
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-BKs_a9P1.js";
8
8
  import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-B1_pRFE_.js";
9
- import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "./proof-DMYwiD7d.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-C0MyXzft.js";
9
+ import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "./proof-CE8nwTF6.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-Zw0soD-7.js";
11
11
  import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue } from "./federation/mod.js";
12
12
  import "./nodeinfo/mod.js";
13
13
  import "./runtime/mod.js";
@@ -5,7 +5,7 @@ import { n as createRequestContext } from "../context-Juj6bdHC.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-DflMZha5.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
8
- import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-II_yrP7N.mjs";
8
+ import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-BhYpLGA_.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/nodeinfo/handler.test.ts
11
11
  test("handleNodeInfo()", async () => {
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CtG6TwTB.mjs";
5
- import { n as fetchActorDocument, s as verifyKeyOwnership, t as exportJwk } from "./key-QamJxksu.mjs";
4
+ import { n as version, t as name } from "./deno--OYa_lxj.mjs";
5
+ import { n as fetchActorDocument, s as verifyKeyOwnership, t as exportJwk } from "./key-D5e9c_yV.mjs";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-DMRapH1d.cjs");
4
+ const require_http = require("./http-CO_IVq7x.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let _fedify_vocab = require("@fedify/vocab");
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -300,24 +300,110 @@ function assertSafeJsonLd(jsonLd) {
300
300
  }
301
301
  }
302
302
  }
303
+ const signatureTermDefinition = { "signature": "https://w3id.org/security#signature" };
304
+ const signatureDefiningContexts = new Set(["https://w3id.org/identity/v1", "https://w3id.org/security/v1"]);
305
+ const signatureFreeContexts = getSignatureFreeContexts();
306
+ function getSignatureFreeContexts() {
307
+ const urls = /* @__PURE__ */ new Set();
308
+ for (const [url, document] of globalThis.Object.entries(_fedify_vocab_runtime.preloadedContexts)) {
309
+ if (typeof document !== "object" || document == null) continue;
310
+ const context = document["@context"];
311
+ if (typeof context !== "object" || context == null || Array.isArray(context)) continue;
312
+ const record = context;
313
+ if (!globalThis.Object.prototype.hasOwnProperty.call(record, "signature")) urls.add(url);
314
+ }
315
+ return urls;
316
+ }
317
+ function getContextEntryEffect(entry) {
318
+ if (entry == null) return "resets";
319
+ if (typeof entry === "string") {
320
+ if (signatureDefiningContexts.has(entry)) return "defines";
321
+ if (signatureFreeContexts.has(entry)) return "absent";
322
+ return "unknown";
323
+ }
324
+ if (typeof entry !== "object") return "absent";
325
+ const record = entry;
326
+ if (globalThis.Object.prototype.hasOwnProperty.call(record, "signature")) {
327
+ const definition = record["signature"];
328
+ const contextProtected = record["@protected"] === true;
329
+ const imported = globalThis.Object.prototype.hasOwnProperty.call(record, "@import");
330
+ if (definition == null) {
331
+ if (contextProtected) return "defines";
332
+ return imported ? "unknown" : "resets";
333
+ }
334
+ if (typeof definition === "object" && !Array.isArray(definition)) {
335
+ const term = definition;
336
+ const protectedTerm = contextProtected || term["@protected"] === true;
337
+ if (globalThis.Object.prototype.hasOwnProperty.call(term, "@id") && term["@id"] == null) {
338
+ if (protectedTerm) return "defines";
339
+ return imported ? "unknown" : "resets";
340
+ }
341
+ }
342
+ return "defines";
343
+ }
344
+ if (globalThis.Object.prototype.hasOwnProperty.call(record, "@import")) return "unknown";
345
+ return "absent";
346
+ }
347
+ function getSignatureTermState(context) {
348
+ if (context == null) return "absent";
349
+ if (!Array.isArray(context)) {
350
+ const effect = getContextEntryEffect(context);
351
+ return effect === "defines" ? "defined" : effect === "unknown" ? "unknown" : "absent";
352
+ }
353
+ let state = "absent";
354
+ for (const entry of context) {
355
+ const effect = getContextEntryEffect(entry);
356
+ if (effect === "defines") state = "defined";
357
+ else if (effect === "resets") state = "absent";
358
+ else if (effect === "unknown" && state !== "defined") state = "unknown";
359
+ }
360
+ return state;
361
+ }
362
+ function prepareSignatureDocument(jsonLd) {
363
+ if (typeof jsonLd !== "object" || jsonLd == null || Array.isArray(jsonLd)) return jsonLd;
364
+ const document = jsonLd;
365
+ if (document["proof"] != null) return { ...document };
366
+ const context = document["@context"];
367
+ if (getSignatureTermState(context) !== "absent") return { ...document };
368
+ const definition = { ...signatureTermDefinition };
369
+ return {
370
+ ...document,
371
+ "@context": context == null ? [definition] : Array.isArray(context) ? [...context, definition] : [context, definition]
372
+ };
373
+ }
303
374
  /**
304
375
  * Attaches a LD signature to the given JSON-LD document.
376
+ *
377
+ * When the document's context does not already define the `signature`
378
+ * property, the returned document adds a minimal inline context for it.
379
+ * Without that definition, expanding the attached signature would produce a
380
+ * blank node predicate, which is not valid RDF. Documents that already carry
381
+ * a `proof` are left unchanged, since a new context definition would invalidate
382
+ * the proof. Documents whose context includes an unrecognized remote context
383
+ * are also left unchanged, since that context may already define `signature`.
305
384
  * @param jsonLd The JSON-LD document to attach the signature to. It is not
306
385
  * modified.
307
386
  * @param signature The signature to attach.
308
387
  * @returns The JSON-LD document with the attached signature.
309
388
  * @throws {TypeError} If the input document is not a valid JSON-LD document.
389
+ * Top-level arrays are rejected, since a Linked Data
390
+ * Signature is attached to a single node.
310
391
  * @since 1.0.0
311
392
  */
312
393
  function attachSignature(jsonLd, signature) {
313
- if (typeof jsonLd !== "object" || jsonLd == null) throw new TypeError("Failed to attach signature; invalid JSON-LD document.");
314
- return {
315
- ...jsonLd,
316
- signature
317
- };
394
+ if (typeof jsonLd !== "object" || jsonLd == null || Array.isArray(jsonLd)) throw new TypeError("Failed to attach signature; invalid JSON-LD document.");
395
+ const output = prepareSignatureDocument(jsonLd);
396
+ output.signature = signature;
397
+ return output;
318
398
  }
319
399
  /**
320
400
  * Creates a LD signature for the given JSON-LD document.
401
+ *
402
+ * If the document's context does not define the `signature` property, the
403
+ * signature is computed over the document with a minimal inline definition
404
+ * added. Use {@link attachSignature} to attach the signature to that exact
405
+ * document. Documents that already carry a `proof`, or whose context includes
406
+ * an unrecognized remote context, are hashed unchanged.
321
407
  * @param jsonLd The JSON-LD document to sign.
322
408
  * @param privateKey The private key to sign the document.
323
409
  * @param keyId The ID of the public key that corresponds to the private key.
@@ -331,14 +417,17 @@ async function createSignature(jsonLd, privateKey, keyId, { contextLoader, creat
331
417
  require_http.validateCryptoKey(privateKey, "private");
332
418
  if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
333
419
  const options = {
334
- "@context": "https://w3id.org/identity/v1",
335
420
  creator: keyId.href,
336
421
  created: created?.toString() ?? (/* @__PURE__ */ new Date()).toISOString()
337
422
  };
338
- const message = await hashJsonLd(options, contextLoader) + await hashJsonLd(jsonLd, contextLoader);
423
+ const message = await hashJsonLd({
424
+ "@context": "https://w3id.org/identity/v1",
425
+ ...options
426
+ }, contextLoader) + await hashJsonLd(prepareSignatureDocument(jsonLd), contextLoader);
339
427
  const messageBytes = new TextEncoder().encode(message);
340
428
  const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, messageBytes);
341
429
  return {
430
+ "@context": "https://w3id.org/security/v1",
342
431
  ...options,
343
432
  type: "RsaSignature2017",
344
433
  signatureValue: (0, byte_encodings_base64.encodeBase64)(signature)
@@ -730,7 +819,10 @@ const logger = (0, _logtape_logtape.getLogger)([
730
819
  * @throws {TypeError} If the private key is invalid or unsupported.
731
820
  * @since 0.10.0
732
821
  */
733
- async function createProof(object, privateKey, keyId, { contextLoader, context, created } = {}) {
822
+ async function createProof(object, privateKey, keyId, options = {}) {
823
+ return (await createProofInternal(object, privateKey, keyId, options)).proof;
824
+ }
825
+ async function createProofInternal(object, privateKey, keyId, { contextLoader, context, created } = {}) {
734
826
  require_http.validateCryptoKey(privateKey, "private");
735
827
  if (privateKey.algorithm.name !== "Ed25519") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
736
828
  const compactMsg = await object.clone({ proofs: [] }).toJsonLd({
@@ -757,13 +849,25 @@ async function createProof(object, privateKey, keyId, { contextLoader, context,
757
849
  digest.set(new Uint8Array(proofDigest), 0);
758
850
  digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
759
851
  const sig = await crypto.subtle.sign("Ed25519", privateKey, digest);
760
- return new _fedify_vocab.DataIntegrityProof({
761
- cryptosuite: "eddsa-jcs-2022",
762
- verificationMethod: keyId,
763
- proofPurpose: "assertionMethod",
764
- created: created ?? Temporal.Now.instant(),
765
- proofValue: new Uint8Array(sig)
766
- });
852
+ return {
853
+ proof: new _fedify_vocab.DataIntegrityProof({
854
+ cryptosuite: "eddsa-jcs-2022",
855
+ verificationMethod: keyId,
856
+ proofPurpose: "assertionMethod",
857
+ created: created ?? Temporal.Now.instant(),
858
+ proofValue: new Uint8Array(sig)
859
+ }),
860
+ jsonLd: compactMsg
861
+ };
862
+ }
863
+ function getJsonLdContextKey(value) {
864
+ const context = typeof value === "object" && value != null && !Array.isArray(value) ? value["@context"] ?? null : null;
865
+ return JSON.stringify(sortJsonKeys(context));
866
+ }
867
+ function sortJsonKeys(value) {
868
+ if (Array.isArray(value)) return value.map(sortJsonKeys);
869
+ if (typeof value !== "object" || value == null) return value;
870
+ return globalThis.Object.fromEntries(globalThis.Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sortJsonKeys(item)]));
767
871
  }
768
872
  /**
769
873
  * Signs the given object with the private key and returns the signed object.
@@ -779,9 +883,18 @@ async function signObject(object, privateKey, keyId, options = {}) {
779
883
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(require_http.name, require_http.version).startActiveSpan("object_integrity_proofs.sign", { attributes: { "activitypub.object.type": (0, _fedify_vocab.getTypeId)(object).href } }, async (span) => {
780
884
  try {
781
885
  if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
886
+ const { proof, jsonLd } = await createProofInternal(object, privateKey, keyId, options);
782
887
  const existingProofs = [];
783
- for await (const proof of object.getProofs(options)) existingProofs.push(proof);
784
- const proof = await createProof(object, privateKey, keyId, options);
888
+ let hasProofs = object.proofIds.length > 0;
889
+ if (!hasProofs) for await (const _ of object.getProofs(options)) {
890
+ hasProofs = true;
891
+ break;
892
+ }
893
+ if (hasProofs) {
894
+ let sameContext = true;
895
+ if (options.context == null) sameContext = getJsonLdContextKey(await object.toJsonLd({ contextLoader: options.contextLoader })) === getJsonLdContextKey(jsonLd);
896
+ if (sameContext) for await (const existingProof of object.getProofs(options)) existingProofs.push(existingProof);
897
+ }
785
898
  if (span.isRecording()) {
786
899
  if (proof.cryptosuite != null) span.setAttribute("object_integrity_proofs.cryptosuite", proof.cryptosuite);
787
900
  if (proof.verificationMethodId != null) span.setAttribute("object_integrity_proofs.key_id", proof.verificationMethodId.href);