@particle-academy/fancy-connector-core 0.11.0 → 0.13.0

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/index.js CHANGED
@@ -1232,6 +1232,88 @@ function fnv1a(value) {
1232
1232
  return hash.toString(16).padStart(8, "0");
1233
1233
  }
1234
1234
 
1235
+ // src/jwt.ts
1236
+ var JWT_ALGORITHMS = ["RS256"];
1237
+ var fail = (reason) => ({ ok: false, reason });
1238
+ async function verifyJwt(token, options) {
1239
+ const parts = typeof token === "string" ? token.split(".") : [];
1240
+ if (parts.length !== 3 || parts.some((p2, i) => p2 === "" && i < 2)) {
1241
+ return fail("token is not a JWS compact serialization");
1242
+ }
1243
+ const [h, p, s] = parts;
1244
+ const header3 = jsonSegment(h);
1245
+ if (!header3) return fail("token header is not base64url JSON");
1246
+ if (!JWT_ALGORITHMS.includes(header3.alg)) {
1247
+ return fail(`alg ${JSON.stringify(header3.alg)} is not accepted`);
1248
+ }
1249
+ const key = selectKey(options.keys, header3.kid);
1250
+ if ("reason" in key) return fail(key.reason);
1251
+ const signature = base64urlBytes(s);
1252
+ if (!signature) return fail("signature is not base64url");
1253
+ const subtle = globalThis.crypto?.subtle;
1254
+ if (!subtle) return fail("no WebCrypto in this runtime");
1255
+ let valid;
1256
+ try {
1257
+ const imported = await subtle.importKey(
1258
+ "jwk",
1259
+ { kty: "RSA", n: key.jwk.n, e: key.jwk.e, alg: "RS256", ext: true },
1260
+ { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
1261
+ false,
1262
+ ["verify"]
1263
+ );
1264
+ valid = await subtle.verify("RSASSA-PKCS1-v1_5", imported, signature, new TextEncoder().encode(`${h}.${p}`));
1265
+ } catch {
1266
+ return fail("the key could not be used to verify");
1267
+ }
1268
+ if (!valid) return fail("signature did not match");
1269
+ const claims = jsonSegment(p);
1270
+ if (!claims) return fail("token payload is not base64url JSON");
1271
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
1272
+ const leeway = options.leeway ?? 60;
1273
+ if (typeof claims.exp !== "number") return fail("token has no exp");
1274
+ if (now > claims.exp + leeway) return fail("token has expired");
1275
+ if (typeof claims.nbf === "number" && now + leeway < claims.nbf) return fail("token is not valid yet");
1276
+ if (options.issuer !== void 0) {
1277
+ const issuers = typeof options.issuer === "string" ? [options.issuer] : options.issuer;
1278
+ if (typeof claims.iss !== "string" || !issuers.includes(claims.iss)) return fail("token issuer is not accepted");
1279
+ }
1280
+ if (options.audience !== null) {
1281
+ const wanted = typeof options.audience === "string" ? [options.audience] : options.audience;
1282
+ const offered = typeof claims.aud === "string" ? [claims.aud] : Array.isArray(claims.aud) ? claims.aud : [];
1283
+ if (!offered.some((a) => typeof a === "string" && wanted.includes(a))) return fail("token audience is not accepted");
1284
+ }
1285
+ return { ok: true, header: header3, claims };
1286
+ }
1287
+ function selectKey(keys, kid) {
1288
+ const rsa = keys.filter((k) => k.kty === "RSA" && typeof k.n === "string" && typeof k.e === "string");
1289
+ if (typeof kid === "string") {
1290
+ const match = rsa.find((k) => k.kid === kid);
1291
+ return match ? { jwk: match } : { reason: `no key for kid ${JSON.stringify(kid)}` };
1292
+ }
1293
+ const [only] = rsa;
1294
+ return rsa.length === 1 && only ? { jwk: only } : { reason: "token names no kid and the key is ambiguous" };
1295
+ }
1296
+ function jsonSegment(segment) {
1297
+ const bytes = base64urlBytes(segment);
1298
+ if (!bytes) return null;
1299
+ try {
1300
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1301
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1302
+ } catch {
1303
+ return null;
1304
+ }
1305
+ }
1306
+ function base64urlBytes(segment) {
1307
+ if (!/^[A-Za-z0-9_-]*$/.test(segment) || segment.length % 4 === 1) return null;
1308
+ const standard = segment.replace(/-/g, "+").replace(/_/g, "/");
1309
+ const padded = standard + "=".repeat((4 - standard.length % 4) % 4);
1310
+ try {
1311
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
1312
+ } catch {
1313
+ return null;
1314
+ }
1315
+ }
1316
+
1235
1317
  // src/lease.ts
1236
1318
  var EXPIRES_AT_UNITS = ["rfc3339", "epoch-ms"];
1237
1319
  var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
@@ -1666,6 +1748,65 @@ function parseMime(raw) {
1666
1748
  };
1667
1749
  }
1668
1750
 
1751
+ // src/pubsub-push.ts
1752
+ var PUBSUB_PUSH_REASONS = ["not-json", "missing-message", "missing-data", "bad-encoding", "data-not-json"];
1753
+ var PubSubPushError = class extends Error {
1754
+ constructor(reason, message) {
1755
+ super(message);
1756
+ this.reason = reason;
1757
+ this.name = "PubSubPushError";
1758
+ }
1759
+ reason;
1760
+ };
1761
+ var UTF8 = new TextDecoder("utf-8", { fatal: true });
1762
+ function decodePubSubPush(raw) {
1763
+ let body;
1764
+ try {
1765
+ body = JSON.parse(typeof raw === "string" ? raw : UTF8.decode(raw));
1766
+ } catch {
1767
+ throw new PubSubPushError("not-json", "the push body is not JSON");
1768
+ }
1769
+ if (!isObject(body)) throw new PubSubPushError("not-json", "the push body is not a JSON object");
1770
+ const message = body.message;
1771
+ if (!isObject(message)) throw new PubSubPushError("missing-message", "the push body has no message");
1772
+ const data = message.data;
1773
+ if (typeof data !== "string" || data === "") {
1774
+ throw new PubSubPushError("missing-data", "the message carries no data");
1775
+ }
1776
+ let text;
1777
+ try {
1778
+ text = UTF8.decode(base64Bytes2(data));
1779
+ } catch {
1780
+ throw new PubSubPushError("bad-encoding", "the message data is not base64 of UTF-8 text");
1781
+ }
1782
+ let parsed;
1783
+ try {
1784
+ parsed = JSON.parse(text);
1785
+ } catch (error) {
1786
+ throw new PubSubPushError("data-not-json", `the message data is not JSON: ${error.message}`);
1787
+ }
1788
+ return {
1789
+ subscription: stringOrNull(body.subscription),
1790
+ messageId: stringOrNull(message.messageId) ?? stringOrNull(message.message_id),
1791
+ publishTime: stringOrNull(message.publishTime) ?? stringOrNull(message.publish_time),
1792
+ attributes: isObject(message.attributes) ? message.attributes : {},
1793
+ deliveryAttempt: Number.isInteger(body.deliveryAttempt) ? body.deliveryAttempt : null,
1794
+ data: parsed
1795
+ };
1796
+ }
1797
+ function base64Bytes2(data) {
1798
+ const unpadded = data.replace(/={1,2}$/, "");
1799
+ if (!/^[A-Za-z0-9+/_-]*$/.test(unpadded) || unpadded.length % 4 === 1) throw new Error("not base64");
1800
+ const standard = unpadded.replace(/-/g, "+").replace(/_/g, "/");
1801
+ return Uint8Array.from(atob(standard + "=".repeat((4 - standard.length % 4) % 4)), (c) => c.charCodeAt(0));
1802
+ }
1803
+ function isObject(value) {
1804
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1805
+ }
1806
+ function stringOrNull(value) {
1807
+ return typeof value === "string" ? value : null;
1808
+ }
1809
+
1669
1810
  // src/form-field-json.ts
1670
1811
  var FORM_FIELD_JSON_REASONS = ["missing-field", "duplicate-field", "bad-encoding", "not-json"];
1671
1812
  var FormFieldJsonError = class extends Error {
@@ -1678,7 +1819,7 @@ var FormFieldJsonError = class extends Error {
1678
1819
  reason;
1679
1820
  field;
1680
1821
  };
1681
- var UTF8 = new TextDecoder("utf-8", { fatal: true });
1822
+ var UTF82 = new TextDecoder("utf-8", { fatal: true });
1682
1823
  var HEX = /^[0-9A-Fa-f]{2}$/;
1683
1824
  function decodeFormFieldJson(raw, field) {
1684
1825
  const text = typeof raw === "string" ? raw : latin12(raw);
@@ -1721,14 +1862,15 @@ function component(encoded, field) {
1721
1862
  }
1722
1863
  }
1723
1864
  try {
1724
- return UTF8.decode(new Uint8Array(bytes));
1865
+ return UTF82.decode(new Uint8Array(bytes));
1725
1866
  } catch {
1726
1867
  throw new FormFieldJsonError("bad-encoding", field, "the decoded bytes are not valid UTF-8");
1727
1868
  }
1728
1869
  }
1729
- var DELIVERY_DECODE_KINDS = ["form-field-json"];
1870
+ var DELIVERY_DECODE_KINDS = ["form-field-json", "pubsub-push"];
1730
1871
  function decodeDelivery(decode, raw) {
1731
1872
  if (decode.kind === "form-field-json") return decodeFormFieldJson(raw, decode.field);
1873
+ if (decode.kind === "pubsub-push") return decodePubSubPush(raw);
1732
1874
  throw new Error(`unknown decode kind ${JSON.stringify(decode.kind)}`);
1733
1875
  }
1734
1876
  function latin12(bytes) {
@@ -1737,6 +1879,202 @@ function latin12(bytes) {
1737
1879
  return out;
1738
1880
  }
1739
1881
 
1882
+ // src/webhook.ts
1883
+ var SIGNED_URI_RULES = ["hubspot-v3"];
1884
+ var HUBSPOT_V3_DECODE = {
1885
+ "%3A": ":",
1886
+ "%2F": "/",
1887
+ "%3F": "?",
1888
+ "%40": "@",
1889
+ "%21": "!",
1890
+ "%24": "$",
1891
+ "%27": "'",
1892
+ "%28": "(",
1893
+ "%29": ")",
1894
+ "%2A": "*",
1895
+ "%2C": ",",
1896
+ "%3B": ";"
1897
+ };
1898
+ function normaliseSignedUri(url, rule) {
1899
+ if (rule !== "hubspot-v3") throw new Error(`unknown signed-uri rule ${JSON.stringify(rule)}`);
1900
+ const withoutFragment = url.split("#", 1)[0] ?? "";
1901
+ const q = withoutFragment.indexOf("?");
1902
+ if (q === -1) return withoutFragment;
1903
+ const query = withoutFragment.slice(q + 1).replace(/%[0-9A-Fa-f]{2}/g, (e) => HUBSPOT_V3_DECODE[e.toUpperCase()] ?? e);
1904
+ return `${withoutFragment.slice(0, q)}?${query}`;
1905
+ }
1906
+ function secretKeyBytes(secret, options = {}) {
1907
+ let material = secret;
1908
+ if (options.secretPrefix !== void 0) {
1909
+ if (!material.startsWith(options.secretPrefix)) {
1910
+ return { reason: `signing secret does not start with ${JSON.stringify(options.secretPrefix)}` };
1911
+ }
1912
+ material = material.slice(options.secretPrefix.length);
1913
+ }
1914
+ if ((options.secretEncoding ?? "utf8") === "base64") {
1915
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(material) || material.length % 4 !== 0) {
1916
+ return { reason: "signing secret is not valid base64" };
1917
+ }
1918
+ try {
1919
+ return { bytes: Uint8Array.from(atob(material), (c) => c.charCodeAt(0)) };
1920
+ } catch {
1921
+ return { reason: "signing secret is not valid base64" };
1922
+ }
1923
+ }
1924
+ return { bytes: new TextEncoder().encode(material) };
1925
+ }
1926
+ async function verifyHmac(options) {
1927
+ const { raw, secret, scheme, timestamp, id } = options;
1928
+ const signatures = (Array.isArray(options.signature) ? options.signature : [options.signature]).filter(
1929
+ (s) => typeof s === "string" && s !== ""
1930
+ );
1931
+ if (!secret) {
1932
+ return { ok: false, reason: "no signing secret configured for this trigger" };
1933
+ }
1934
+ if (signatures.length === 0) return { ok: false, reason: "delivery carried no signature header" };
1935
+ if (scheme.tolerance !== void 0) {
1936
+ if (!timestamp) return { ok: false, reason: "delivery carried no timestamp header" };
1937
+ const sent = Number(timestamp);
1938
+ if (!Number.isFinite(sent)) return { ok: false, reason: "timestamp header is not a number" };
1939
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
1940
+ const sentSeconds = scheme.timestampUnit === "ms" ? sent / 1e3 : sent;
1941
+ if (Math.abs(now - sentSeconds) > scheme.tolerance) {
1942
+ return { ok: false, reason: `delivery is outside the ${scheme.tolerance}s replay window` };
1943
+ }
1944
+ }
1945
+ const key = secretKeyBytes(secret, scheme);
1946
+ if ("reason" in key) return { ok: false, reason: key.reason };
1947
+ let line;
1948
+ if (scheme.signsRequestLine) {
1949
+ const { method, url } = options.request ?? {};
1950
+ if (!method || !url) return { ok: false, reason: "delivery carried no request method or url" };
1951
+ line = { method, uri: scheme.uri ? normaliseSignedUri(url, scheme.uri) : url };
1952
+ }
1953
+ const expected = await hmac(
1954
+ secret,
1955
+ scheme.payload(raw, timestamp, id, line),
1956
+ scheme.algorithm,
1957
+ scheme.encoding ?? "hex",
1958
+ scheme
1959
+ );
1960
+ return signatures.some((candidate) => constantTimeEquals(expected, candidate)) ? { ok: true } : { ok: false, reason: "signature did not match" };
1961
+ }
1962
+ async function hmac(secret, payload, algorithm, encoding = "hex", options = {}) {
1963
+ const subtle = globalThis.crypto?.subtle;
1964
+ if (!subtle) {
1965
+ throw new Error(
1966
+ "WebCrypto is not available in this runtime, so webhook signatures cannot be verified. Refusing to accept deliveries unverified."
1967
+ );
1968
+ }
1969
+ const material = secretKeyBytes(secret, options);
1970
+ if ("reason" in material) throw new Error(material.reason);
1971
+ const encoder2 = new TextEncoder();
1972
+ const key = await subtle.importKey(
1973
+ "raw",
1974
+ material.bytes,
1975
+ { name: "HMAC", hash: algorithm },
1976
+ false,
1977
+ ["sign"]
1978
+ );
1979
+ const bytes = new Uint8Array(await subtle.sign("HMAC", key, encoder2.encode(payload)));
1980
+ if (encoding === "base64") {
1981
+ let binary = "";
1982
+ bytes.forEach((byte) => {
1983
+ binary += String.fromCharCode(byte);
1984
+ });
1985
+ return btoa(binary);
1986
+ }
1987
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1988
+ }
1989
+ function constantTimeEquals(a, b) {
1990
+ if (a.length !== b.length) return false;
1991
+ let diff = 0;
1992
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1993
+ return diff === 0;
1994
+ }
1995
+ function isSharedTokenScheme(scheme) {
1996
+ return "kind" in scheme && scheme.kind === "shared-token";
1997
+ }
1998
+ function verifySharedToken(options) {
1999
+ const { raw, headers, secret, scheme } = options;
2000
+ if (!secret) {
2001
+ return { ok: false, reason: "no shared token configured for this trigger" };
2002
+ }
2003
+ let tokens;
2004
+ if (scheme.in === "header") {
2005
+ tokens = [header2(headers, scheme.name)];
2006
+ } else {
2007
+ let parsed;
2008
+ try {
2009
+ parsed = JSON.parse(raw);
2010
+ } catch {
2011
+ return { ok: false, reason: "delivery body is not JSON" };
2012
+ }
2013
+ tokens = readTokens(parsed, scheme.path.split("."));
2014
+ }
2015
+ const present = tokens.filter((t) => t !== void 0 && t !== null && t !== "");
2016
+ if (present.length === 0) return { ok: false, reason: "delivery carried no token" };
2017
+ let matched = present.length === tokens.length;
2018
+ for (const token of present) {
2019
+ matched = typeof token === "string" && constantTimeEquals(secret, token) && matched;
2020
+ }
2021
+ return matched ? { ok: true } : { ok: false, reason: "token did not match" };
2022
+ }
2023
+ function readTokens(value, segments) {
2024
+ if (segments.length === 0) return [value];
2025
+ const [head, ...rest] = segments;
2026
+ const eachElement = head.endsWith("[]");
2027
+ const key = eachElement ? head.slice(0, -2) : head;
2028
+ if (value === null || typeof value !== "object") return [void 0];
2029
+ const next = value[key];
2030
+ if (!eachElement) return readTokens(next, rest);
2031
+ if (!Array.isArray(next)) return [void 0];
2032
+ return next.flatMap((element) => readTokens(element, rest));
2033
+ }
2034
+ function handshakeResponse(handshake, query) {
2035
+ if (!handshake) return void 0;
2036
+ const raw = query[handshake.param];
2037
+ const token = Array.isArray(raw) ? raw[0] : raw;
2038
+ if (!token) return void 0;
2039
+ return { status: 200, contentType: handshake.contentType ?? "text/plain", body: token };
2040
+ }
2041
+ function header2(headers, name) {
2042
+ const wanted = name.toLowerCase();
2043
+ for (const [key, value] of Object.entries(headers)) {
2044
+ if (key.toLowerCase() !== wanted) continue;
2045
+ return Array.isArray(value) ? value[0] : value;
2046
+ }
2047
+ return void 0;
2048
+ }
2049
+
2050
+ // src/oidc-delivery.ts
2051
+ var OIDC_JWT_SCHEME_KIND = "oidc-jwt";
2052
+ function isOidcJwtScheme(scheme) {
2053
+ return typeof scheme === "object" && scheme !== null && scheme.kind === OIDC_JWT_SCHEME_KIND;
2054
+ }
2055
+ var fail2 = (reason) => ({ ok: false, reason });
2056
+ var BEARER = /^bearer +(\S+)$/i;
2057
+ async function verifyOidcDelivery(options) {
2058
+ const { headers, scheme, expected } = options;
2059
+ if (!expected || expected.keys.length === 0) return fail2("no signing keys supplied for this trigger");
2060
+ if (!expected.audience) return fail2("no audience configured for this trigger");
2061
+ if (!expected.principal) return fail2("no principal configured for this trigger");
2062
+ const token = BEARER.exec(header2(headers, scheme.header) ?? "")?.[1];
2063
+ if (!token) return fail2("delivery carried no bearer token");
2064
+ const verified = await verifyJwt(token, {
2065
+ keys: expected.keys,
2066
+ audience: expected.audience,
2067
+ issuer: scheme.issuers,
2068
+ ...options.now === void 0 ? {} : { now: options.now }
2069
+ });
2070
+ if (!verified.ok) return fail2(verified.reason);
2071
+ if (verified.claims[scheme.principalClaim] !== expected.principal) return fail2("token principal is not accepted");
2072
+ if (scheme.verifiedClaim !== void 0 && verified.claims[scheme.verifiedClaim] !== true) {
2073
+ return fail2("token principal is not verified");
2074
+ }
2075
+ return { ok: true };
2076
+ }
2077
+
1740
2078
  // src/probe.ts
1741
2079
  async function probe(spec) {
1742
2080
  let response;
@@ -1952,143 +2290,14 @@ var CREDENTIAL_SCOPES = ["provider", "account"];
1952
2290
  var PROBLEM_SEVERITIES = ["block", "warn"];
1953
2291
  var CANONICAL_METRICS = ["like", "share", "reply", "quote", "view"];
1954
2292
 
1955
- // src/webhook.ts
1956
- function secretKeyBytes(secret, options = {}) {
1957
- let material = secret;
1958
- if (options.secretPrefix !== void 0) {
1959
- if (!material.startsWith(options.secretPrefix)) {
1960
- return { reason: `signing secret does not start with ${JSON.stringify(options.secretPrefix)}` };
1961
- }
1962
- material = material.slice(options.secretPrefix.length);
1963
- }
1964
- if ((options.secretEncoding ?? "utf8") === "base64") {
1965
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(material) || material.length % 4 !== 0) {
1966
- return { reason: "signing secret is not valid base64" };
1967
- }
1968
- try {
1969
- return { bytes: Uint8Array.from(atob(material), (c) => c.charCodeAt(0)) };
1970
- } catch {
1971
- return { reason: "signing secret is not valid base64" };
1972
- }
1973
- }
1974
- return { bytes: new TextEncoder().encode(material) };
1975
- }
1976
- async function verifyHmac(options) {
1977
- const { raw, secret, scheme, timestamp, id } = options;
1978
- const signatures = (Array.isArray(options.signature) ? options.signature : [options.signature]).filter(
1979
- (s) => typeof s === "string" && s !== ""
1980
- );
1981
- if (!secret) {
1982
- return { ok: false, reason: "no signing secret configured for this trigger" };
1983
- }
1984
- if (signatures.length === 0) return { ok: false, reason: "delivery carried no signature header" };
1985
- if (scheme.tolerance !== void 0) {
1986
- if (!timestamp) return { ok: false, reason: "delivery carried no timestamp header" };
1987
- const sent = Number(timestamp);
1988
- if (!Number.isFinite(sent)) return { ok: false, reason: "timestamp header is not a number" };
1989
- const now = options.now ?? Math.floor(Date.now() / 1e3);
1990
- if (Math.abs(now - sent) > scheme.tolerance) {
1991
- return { ok: false, reason: `delivery is outside the ${scheme.tolerance}s replay window` };
1992
- }
1993
- }
1994
- const key = secretKeyBytes(secret, scheme);
1995
- if ("reason" in key) return { ok: false, reason: key.reason };
1996
- const expected = await hmac(secret, scheme.payload(raw, timestamp, id), scheme.algorithm, scheme.encoding ?? "hex", scheme);
1997
- return signatures.some((candidate) => constantTimeEquals(expected, candidate)) ? { ok: true } : { ok: false, reason: "signature did not match" };
1998
- }
1999
- async function hmac(secret, payload, algorithm, encoding = "hex", options = {}) {
2000
- const subtle = globalThis.crypto?.subtle;
2001
- if (!subtle) {
2002
- throw new Error(
2003
- "WebCrypto is not available in this runtime, so webhook signatures cannot be verified. Refusing to accept deliveries unverified."
2004
- );
2005
- }
2006
- const material = secretKeyBytes(secret, options);
2007
- if ("reason" in material) throw new Error(material.reason);
2008
- const encoder2 = new TextEncoder();
2009
- const key = await subtle.importKey(
2010
- "raw",
2011
- material.bytes,
2012
- { name: "HMAC", hash: algorithm },
2013
- false,
2014
- ["sign"]
2015
- );
2016
- const bytes = new Uint8Array(await subtle.sign("HMAC", key, encoder2.encode(payload)));
2017
- if (encoding === "base64") {
2018
- let binary = "";
2019
- bytes.forEach((byte) => {
2020
- binary += String.fromCharCode(byte);
2021
- });
2022
- return btoa(binary);
2023
- }
2024
- return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
2025
- }
2026
- function constantTimeEquals(a, b) {
2027
- if (a.length !== b.length) return false;
2028
- let diff = 0;
2029
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
2030
- return diff === 0;
2031
- }
2032
- function isSharedTokenScheme(scheme) {
2033
- return "kind" in scheme && scheme.kind === "shared-token";
2034
- }
2035
- function verifySharedToken(options) {
2036
- const { raw, headers, secret, scheme } = options;
2037
- if (!secret) {
2038
- return { ok: false, reason: "no shared token configured for this trigger" };
2039
- }
2040
- let tokens;
2041
- if (scheme.in === "header") {
2042
- tokens = [header2(headers, scheme.name)];
2043
- } else {
2044
- let parsed;
2045
- try {
2046
- parsed = JSON.parse(raw);
2047
- } catch {
2048
- return { ok: false, reason: "delivery body is not JSON" };
2049
- }
2050
- tokens = readTokens(parsed, scheme.path.split("."));
2051
- }
2052
- const present = tokens.filter((t) => t !== void 0 && t !== null && t !== "");
2053
- if (present.length === 0) return { ok: false, reason: "delivery carried no token" };
2054
- let matched = present.length === tokens.length;
2055
- for (const token of present) {
2056
- matched = typeof token === "string" && constantTimeEquals(secret, token) && matched;
2057
- }
2058
- return matched ? { ok: true } : { ok: false, reason: "token did not match" };
2059
- }
2060
- function readTokens(value, segments) {
2061
- if (segments.length === 0) return [value];
2062
- const [head, ...rest] = segments;
2063
- const eachElement = head.endsWith("[]");
2064
- const key = eachElement ? head.slice(0, -2) : head;
2065
- if (value === null || typeof value !== "object") return [void 0];
2066
- const next = value[key];
2067
- if (!eachElement) return readTokens(next, rest);
2068
- if (!Array.isArray(next)) return [void 0];
2069
- return next.flatMap((element) => readTokens(element, rest));
2070
- }
2071
- function handshakeResponse(handshake, query) {
2072
- if (!handshake) return void 0;
2073
- const raw = query[handshake.param];
2074
- const token = Array.isArray(raw) ? raw[0] : raw;
2075
- if (!token) return void 0;
2076
- return { status: 200, contentType: handshake.contentType ?? "text/plain", body: token };
2077
- }
2078
- function header2(headers, name) {
2079
- const wanted = name.toLowerCase();
2080
- for (const [key, value] of Object.entries(headers)) {
2081
- if (key.toLowerCase() !== wanted) continue;
2082
- return Array.isArray(value) ? value[0] : value;
2083
- }
2084
- return void 0;
2085
- }
2086
-
2087
2293
  // src/trigger.ts
2294
+ function isOidcJwtSpec(spec) {
2295
+ return isOidcJwtScheme(spec.scheme);
2296
+ }
2088
2297
  function isSharedTokenSpec(spec) {
2089
2298
  return isSharedTokenScheme(spec.scheme);
2090
2299
  }
2091
- async function verifyDelivery(trigger, delivery, secret, now) {
2300
+ async function verifyDelivery(trigger, delivery, secret, now, oidc) {
2092
2301
  const spec = trigger.verification;
2093
2302
  if (!spec) {
2094
2303
  return {
@@ -2099,6 +2308,14 @@ async function verifyDelivery(trigger, delivery, secret, now) {
2099
2308
  if (isSharedTokenSpec(spec)) {
2100
2309
  return verifySharedToken({ raw: delivery.raw, headers: delivery.headers, secret, scheme: spec.scheme });
2101
2310
  }
2311
+ if (isOidcJwtSpec(spec)) {
2312
+ return verifyOidcDelivery({
2313
+ headers: delivery.headers,
2314
+ scheme: spec.scheme,
2315
+ expected: oidc,
2316
+ ...now === void 0 ? {} : { now }
2317
+ });
2318
+ }
2102
2319
  const kind = spec.scheme.kind ?? "hmac";
2103
2320
  if (kind !== "hmac") {
2104
2321
  return {
@@ -2118,6 +2335,7 @@ async function verifyDelivery(trigger, delivery, secret, now) {
2118
2335
  scheme: spec.scheme,
2119
2336
  timestamp,
2120
2337
  id,
2338
+ ...delivery.method === void 0 && delivery.url === void 0 ? {} : { request: { method: delivery.method ?? "", url: delivery.url ?? "" } },
2121
2339
  ...now === void 0 ? {} : { now }
2122
2340
  });
2123
2341
  }
@@ -2163,12 +2381,17 @@ export {
2163
2381
  FAKE_EPOCH,
2164
2382
  FORM_FIELD_JSON_REASONS,
2165
2383
  FormFieldJsonError,
2384
+ JWT_ALGORITHMS,
2166
2385
  MAX_IDEMPOTENCY_KEY_LENGTH,
2167
2386
  NO_IDEMPOTENCY_KEY_WARNING,
2387
+ OIDC_JWT_SCHEME_KIND,
2168
2388
  PROBLEM_SEVERITIES,
2389
+ PUBSUB_PUSH_REASONS,
2390
+ PubSubPushError,
2169
2391
  RENDERER_VERSION,
2170
2392
  REVIEW_STALE_DAYS,
2171
2393
  SANDBOX_KINDS,
2394
+ SIGNED_URI_RULES,
2172
2395
  SUPPORTED_CONNECTOR_API,
2173
2396
  assertConnectorApi,
2174
2397
  byteRangeOf,
@@ -2185,6 +2408,7 @@ export {
2185
2408
  constantTimeEquals,
2186
2409
  decodeDelivery,
2187
2410
  decodeFormFieldJson,
2411
+ decodePubSubPush,
2188
2412
  deliver,
2189
2413
  fakeRequest,
2190
2414
  fakeValues,
@@ -2196,6 +2420,8 @@ export {
2196
2420
  httpFailure,
2197
2421
  idempotencyKeyFor,
2198
2422
  isEmptyPayload,
2423
+ isOidcJwtScheme,
2424
+ isOidcJwtSpec,
2199
2425
  isRemote,
2200
2426
  isSharedTokenScheme,
2201
2427
  isSharedTokenSpec,
@@ -2208,6 +2434,7 @@ export {
2208
2434
  linkRanges,
2209
2435
  measure,
2210
2436
  nextOccurrence,
2437
+ normaliseSignedUri,
2211
2438
  parseInvite,
2212
2439
  parseMime,
2213
2440
  payloadHash,
@@ -2241,6 +2468,8 @@ export {
2241
2468
  utf8Length,
2242
2469
  verifyDelivery,
2243
2470
  verifyHmac,
2471
+ verifyJwt,
2472
+ verifyOidcDelivery,
2244
2473
  verifySharedToken,
2245
2474
  withResolvedLimit
2246
2475
  };