@fedify/vocab-tools 2.4.0-dev.2056 → 2.4.0-dev.2065

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/mod.js CHANGED
@@ -7,7 +7,7 @@ import { parse } from "yaml";
7
7
  import { pascalCase } from "es-toolkit";
8
8
  //#region deno.json
9
9
  var name = "@fedify/vocab-tools";
10
- var version = "2.4.0-dev.2056+e6394f3b";
10
+ var version = "2.4.0-dev.2065+b09fdd33";
11
11
  //#endregion
12
12
  //#region src/type.ts
13
13
  const HEURISTICS_CONTEXTS = [
@@ -1424,6 +1424,7 @@ async function* generateCloner(typeUri, types) {
1424
1424
  { id: values.id ?? this.id },
1425
1425
  options
1426
1426
  );
1427
+ copyPortableProvenance(this, clone);
1427
1428
  `;
1428
1429
  else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
1429
1430
  for (const property of type.properties) {
@@ -1671,14 +1672,27 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1671
1672
  ${JSON.stringify(version)},
1672
1673
  );
1673
1674
  return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
1674
- if (isPortableIri(url)) {
1675
+ const dereferencePortable = async (
1676
+ portableUrl: URL,
1677
+ extra: {
1678
+ inferredGateways?: readonly URL[];
1679
+ response?: RemoteDocument;
1680
+ contextLoader?: DocumentLoader;
1681
+ } = {},
1682
+ ) => {
1675
1683
  try {
1676
- const obj = await dereferencePortableIri(url, {
1684
+ const obj = await dereferencePortableIri(portableUrl, {
1677
1685
  documentLoader,
1678
- contextLoader,
1686
+ contextLoader: extra.contextLoader ?? contextLoader,
1679
1687
  tracerProvider,
1680
1688
  gateways: options.gateways,
1689
+ inferredGateways: extra.inferredGateways,
1690
+ response: extra.response,
1681
1691
  verifyPortableObject: options.verifyPortableObject,
1692
+ referrer: {
1693
+ object: this,
1694
+ property: ${JSON.stringify(property.uri)},
1695
+ },
1682
1696
  suppressError: options.suppressError,
1683
1697
  crossOrigin: options.crossOrigin,
1684
1698
  span,
@@ -1691,7 +1705,10 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1691
1705
  }),
1692
1706
  });
1693
1707
  if (obj != null) {
1694
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1708
+ span.setAttribute(
1709
+ "activitypub.object.id",
1710
+ (obj.id ?? portableUrl).href,
1711
+ );
1695
1712
  span.setAttribute(
1696
1713
  "activitypub.object.type",
1697
1714
  // @ts-ignore: obj.constructor always has a typeId.
@@ -1708,8 +1725,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1708
1725
  } finally {
1709
1726
  span.end();
1710
1727
  }
1711
- }
1728
+ };
1729
+ if (isPortableIri(url)) return await dereferencePortable(url);
1712
1730
  const lookupUrl = formatIri(url);
1731
+ const portableMode = isPortableMode(this, options);
1732
+ if (portableMode) {
1733
+ const compatible = parseCompatibleEf61Reference(url);
1734
+ if (compatible === null) {
1735
+ span.end();
1736
+ return rejectMalformedCompatibleReference(lookupUrl, options);
1737
+ } else if (compatible != null) {
1738
+ return await dereferencePortable(compatible.id, {
1739
+ inferredGateways: [compatible.gateway],
1740
+ });
1741
+ }
1742
+ }
1713
1743
  let fetchResult: RemoteDocument;
1714
1744
  try {
1715
1745
  fetchResult = await documentLoader(lookupUrl);
@@ -1729,52 +1759,90 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1729
1759
  throw error;
1730
1760
  }
1731
1761
  const { document, documentUrl } = fetchResult;
1732
- const baseUrl = parseIri(documentUrl);
1762
+ // In portable mode, the document may turn out to be a portable object,
1763
+ // which has to be verified with the same context documents:
1764
+ const snapshot = portableMode
1765
+ ? createSnapshotContextLoader(contextLoader)
1766
+ : null;
1733
1767
  try {
1734
- const obj = await this.#${property.singularName}_fromJsonLd(
1735
- document,
1736
- { documentLoader, contextLoader, tracerProvider, baseUrl }
1737
- );
1738
- if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
1739
- if (options.crossOrigin === "throw") {
1740
- throw new Error(
1741
- "The object's @id (" + obj.id.href + ") has a different origin " +
1742
- "than the document URL (" + baseUrl.href + "); refusing to return " +
1743
- "the object. If you want to bypass this check and are aware of" +
1744
- 'the security implications, set the crossOrigin option to "trust".'
1768
+ let claim: PortableResponseClaim | null | undefined;
1769
+ try {
1770
+ const baseUrl = parseIri(documentUrl);
1771
+ const obj = await this.#${property.singularName}_fromJsonLd(
1772
+ document,
1773
+ {
1774
+ documentLoader,
1775
+ contextLoader: snapshot?.loader ?? contextLoader,
1776
+ tracerProvider,
1777
+ baseUrl,
1778
+ }
1779
+ );
1780
+ if (snapshot == null) {
1781
+ markUnverifiedPortableClaim(obj, url, documentUrl);
1782
+ } else {
1783
+ claim = getPortableResponseClaim(documentUrl, obj.id);
1784
+ }
1785
+ if (claim === undefined) {
1786
+ if (
1787
+ obj?.id != null &&
1788
+ !isTrustedIriOrigin(options, obj.id, baseUrl)
1789
+ ) {
1790
+ if (options.crossOrigin === "throw") {
1791
+ throw new Error(
1792
+ "The object's @id (" + obj.id.href + ") has a different origin " +
1793
+ "than the document URL (" + baseUrl.href + "); refusing to return " +
1794
+ "the object. If you want to bypass this check and are aware of" +
1795
+ 'the security implications, set the crossOrigin option to "trust".'
1796
+ );
1797
+ }
1798
+ getLogger(["fedify", "vocab"]).warn(
1799
+ "The object's @id ({objectId}) has a different origin than the document " +
1800
+ "URL ({documentUrl}); refusing to return the object. If you want to " +
1801
+ "bypass this check and are aware of the security implications, " +
1802
+ 'set the crossOrigin option to "trust".',
1803
+ { ...fetchResult, objectId: obj.id.href },
1804
+ );
1805
+ span.end();
1806
+ return null;
1807
+ }
1808
+ span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1809
+ span.setAttribute(
1810
+ "activitypub.object.type",
1811
+ // @ts-ignore: obj.constructor always has a typeId.
1812
+ obj.constructor.typeId.href
1745
1813
  );
1814
+ span.end();
1815
+ return obj;
1746
1816
  }
1747
- getLogger(["fedify", "vocab"]).warn(
1748
- "The object's @id ({objectId}) has a different origin than the document " +
1749
- "URL ({documentUrl}); refusing to return the object. If you want to " +
1750
- "bypass this check and are aware of the security implications, " +
1751
- 'set the crossOrigin option to "trust".',
1752
- { ...fetchResult, objectId: obj.id.href },
1753
- );
1754
- return null;
1817
+ } catch (e) {
1818
+ if (options.suppressError) {
1819
+ getLogger(["fedify", "vocab"]).error(
1820
+ "Failed to parse {url}: {error}",
1821
+ { error: e, url: lookupUrl }
1822
+ );
1823
+ span.end();
1824
+ return null;
1825
+ }
1826
+ span.setStatus({
1827
+ code: SpanStatusCode.ERROR,
1828
+ message: String(e),
1829
+ });
1830
+ span.end();
1831
+ throw e;
1755
1832
  }
1756
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1757
- span.setAttribute(
1758
- "activitypub.object.type",
1759
- // @ts-ignore: obj.constructor always has a typeId.
1760
- obj.constructor.typeId.href
1761
- );
1762
- return obj;
1763
- } catch (e) {
1764
- if (options.suppressError) {
1765
- getLogger(["fedify", "vocab"]).error(
1766
- "Failed to parse {url}: {error}",
1767
- { error: e, url: lookupUrl }
1768
- );
1769
- return null;
1833
+ // The document stands for a portable object, so it is verified as
1834
+ // one instead of being trusted because of where it came from:
1835
+ if (claim === null) {
1836
+ span.end();
1837
+ return rejectMalformedCompatibleReference(documentUrl, options);
1770
1838
  }
1771
- span.setStatus({
1772
- code: SpanStatusCode.ERROR,
1773
- message: String(e),
1839
+ return await dereferencePortable(claim.id, {
1840
+ inferredGateways: claim.inferredGateways,
1841
+ response: fetchResult,
1842
+ contextLoader: snapshot?.loader,
1774
1843
  });
1775
- throw e;
1776
1844
  } finally {
1777
- span.end();
1845
+ snapshot?.release();
1778
1846
  }
1779
1847
  });
1780
1848
  }
@@ -1887,18 +1955,31 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1887
1955
  if (this.${await getFieldName(property.uri)}.length < 1) return null;
1888
1956
  let v = this.${await getFieldName(property.uri)}[0];
1889
1957
  if (!(v instanceof URL) &&
1890
- v.id != null &&
1891
- !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1892
1958
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1893
- v = v.id;
1959
+ if (v.id == null) {
1960
+ if (isUnsecuredPortableObject(this)) {
1961
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
1962
+ return null;
1963
+ }
1964
+ } else if (
1965
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
1966
+ isUnsecuredPortableObject(this) ||
1967
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
1968
+ ) {
1969
+ v = v.id;
1970
+ }
1894
1971
  }
1895
1972
  if (v instanceof URL) {
1896
1973
  const fetched =
1897
1974
  await this.#fetch${pascalCase(property.singularName)}(v, options);
1898
1975
  if (fetched == null) return null;
1976
+ // Not cached, so that it is verified when the portable object
1977
+ // policy applies later:
1978
+ if (isUnverifiedPortableClaim(fetched)) return fetched;
1899
1979
  this.${await getFieldName(property.uri)}[0] = fetched;
1900
1980
  this.${await getFieldName(property.uri, "#_trust")}.add(0);
1901
1981
  this._cachedJsonLd = undefined;
1982
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
1902
1983
  return fetched;
1903
1984
  }
1904
1985
  `;
@@ -1928,12 +2009,16 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1928
2009
  }
1929
2010
  yield `
1930
2011
  if (v?.id != null &&
1931
- ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
2012
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2013
+ isUnsecuredPortableObject(this) ||
2014
+ mustDereferencePortableObject(
2015
+ this, ${trustOwner}, v.id, options,
2016
+ )) &&
1932
2017
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1933
2018
  if (options.crossOrigin === "throw") {
1934
2019
  throw new Error(
1935
2020
  "The property object's @id (" + v.id.href + ") has a different " +
1936
- "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
2021
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
1937
2022
  "refusing to return the object. If you want to bypass this " +
1938
2023
  "check and are aware of the security implications, set the " +
1939
2024
  'crossOrigin option to "trust".'
@@ -1945,10 +2030,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1945
2030
  "return the object. If you want to bypass this check and are " +
1946
2031
  "aware of the security implications, set the crossOrigin option " +
1947
2032
  'to "trust".',
1948
- { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
2033
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
1949
2034
  );
1950
2035
  return null;
1951
2036
  }
2037
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
1952
2038
  return v;
1953
2039
  }
1954
2040
  `;
@@ -1995,18 +2081,34 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1995
2081
  for (let i = 0; i < vs.length; i++) {
1996
2082
  let v = vs[i];
1997
2083
  if (!(v instanceof URL) &&
1998
- v.id != null &&
1999
- !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
2000
2084
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
2001
- v = v.id;
2085
+ if (v.id == null) {
2086
+ if (isUnsecuredPortableObject(this)) {
2087
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
2088
+ continue;
2089
+ }
2090
+ } else if (
2091
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
2092
+ isUnsecuredPortableObject(this) ||
2093
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
2094
+ ) {
2095
+ v = v.id;
2096
+ }
2002
2097
  }
2003
2098
  if (v instanceof URL) {
2004
2099
  const fetched =
2005
2100
  await this.#fetch${pascalCase(property.singularName)}(v, options);
2006
2101
  if (fetched == null) continue;
2102
+ // Not cached, so that it is verified when the portable object
2103
+ // policy applies later:
2104
+ if (isUnverifiedPortableClaim(fetched)) {
2105
+ yield fetched;
2106
+ continue;
2107
+ }
2007
2108
  vs[i] = fetched;
2008
2109
  this.${await getFieldName(property.uri, "#_trust")}.add(i);
2009
2110
  this._cachedJsonLd = undefined;
2111
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
2010
2112
  yield fetched;
2011
2113
  continue;
2012
2114
  }
@@ -2037,12 +2139,16 @@ async function* generateProperty(type, property, types, moduleVarNames) {
2037
2139
  }
2038
2140
  yield `
2039
2141
  if (v?.id != null &&
2040
- ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
2142
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2143
+ isUnsecuredPortableObject(this) ||
2144
+ mustDereferencePortableObject(
2145
+ this, ${trustOwner}, v.id, options,
2146
+ )) &&
2041
2147
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
2042
2148
  if (options.crossOrigin === "throw") {
2043
2149
  throw new Error(
2044
2150
  "The property object's @id (" + v.id.href + ") has a different " +
2045
- "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
2151
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
2046
2152
  "refusing to return the object. If you want to bypass this " +
2047
2153
  "check and are aware of the security implications, set the " +
2048
2154
  'crossOrigin option to "trust".'
@@ -2054,10 +2160,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
2054
2160
  "return the object. If you want to bypass this check and are " +
2055
2161
  "aware of the security implications, set the crossOrigin " +
2056
2162
  'option to "trust".',
2057
- { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
2163
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
2058
2164
  );
2059
2165
  continue;
2060
2166
  }
2167
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
2061
2168
  yield v;
2062
2169
  }
2063
2170
  }
@@ -2079,7 +2186,23 @@ const INTERNAL_RUNTIME_IMPORTS = [
2079
2186
  "isTrustedIriOrigin",
2080
2187
  "normalizeJsonLdIris"
2081
2188
  ].join(",\n ");
2082
- const PORTABLE_DEREFERENCE_IMPORTS = ["dereferencePortableIri", "isPortableIri"].join(",\n ");
2189
+ const PORTABLE_DEREFERENCE_IMPORTS = [
2190
+ "copyPortableProvenance",
2191
+ "createSnapshotContextLoader",
2192
+ "dereferencePortableIri",
2193
+ "getPortableResponseClaim",
2194
+ "isPortableIri",
2195
+ "isPortableMode",
2196
+ "isUnsecuredPortableObject",
2197
+ "isUnverifiedPortableClaim",
2198
+ "markUnverifiedPortableClaim",
2199
+ "mustDereferencePortableObject",
2200
+ "parseCompatibleEf61Reference",
2201
+ "type PortableResponseClaim",
2202
+ "recordPortableReferrer",
2203
+ "rejectMalformedCompatibleReference",
2204
+ "warnUnverifiableEmbeddedObject"
2205
+ ].join(",\n ");
2083
2206
  const SIGNED_REPRESENTATION_IMPORTS = [
2084
2207
  "enterSignedValueScope",
2085
2208
  "resolveSignedValues",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.4.0-dev.2056+e6394f3b",
3
+ "version": "2.4.0-dev.2065+b09fdd33",
4
4
  "description": "Code generator for Activity Vocabulary APIs",
5
5
  "homepage": "https://fedify.dev/",
6
6
  "repository": {