@cboxdk/id-js 0.19.0 → 0.20.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.cjs CHANGED
@@ -675,11 +675,11 @@ function parseActor(claim, depth) {
675
675
  if (typeof claim !== "object" || Array.isArray(claim)) {
676
676
  return { sub: null, actor: null };
677
677
  }
678
- const record = claim;
679
- const sub = record["sub"];
678
+ const record2 = claim;
679
+ const sub = record2["sub"];
680
680
  return {
681
681
  sub: typeof sub === "string" && sub !== "" ? sub : null,
682
- actor: depth + 1 < MAX_ACTOR_DEPTH ? parseActor(record["act"], depth + 1) : null
682
+ actor: depth + 1 < MAX_ACTOR_DEPTH ? parseActor(record2["act"], depth + 1) : null
683
683
  };
684
684
  }
685
685
  function stringList(claim) {
@@ -944,9 +944,9 @@ function header(headers, name) {
944
944
  if (typeof Headers !== "undefined" && headers instanceof Headers) {
945
945
  return headers.get(name) ?? void 0;
946
946
  }
947
- const record = headers;
948
- const key = Object.keys(record).find((k) => k.toLowerCase() === name);
949
- const value = key === void 0 ? void 0 : record[key];
947
+ const record2 = headers;
948
+ const key = Object.keys(record2).find((k) => k.toLowerCase() === name);
949
+ const value = key === void 0 ? void 0 : record2[key];
950
950
  return Array.isArray(value) ? value.join(" ") : value;
951
951
  }
952
952
  function base64ToBytes(value) {
@@ -1644,11 +1644,11 @@ function parseOrganizations(claim) {
1644
1644
  if (typeof entry !== "object" || entry === null) {
1645
1645
  continue;
1646
1646
  }
1647
- const record = entry;
1648
- const id = record["id"];
1649
- const name = record["name"];
1647
+ const record2 = entry;
1648
+ const id = record2["id"];
1649
+ const name = record2["name"];
1650
1650
  if (typeof id === "string" && id !== "" && typeof name === "string" && name !== "") {
1651
- orgs.push({ id, name, role: typeof record["role"] === "string" ? record["role"] : null });
1651
+ orgs.push({ id, name, role: typeof record2["role"] === "string" ? record2["role"] : null });
1652
1652
  }
1653
1653
  }
1654
1654
  return orgs;
@@ -1702,6 +1702,11 @@ async function publishManifest(config, declaration) {
1702
1702
  throw new ConfigurationError("Publishing a manifest needs issuer, clientId and clientSecret.");
1703
1703
  }
1704
1704
  const timeoutMs = config.timeoutMs ?? 1e4;
1705
+ assertSecureIssuer(config.issuer);
1706
+ const issuer = new URL(config.issuer);
1707
+ if (issuer.username || issuer.password || issuer.search || issuer.hash) {
1708
+ throw new ConfigurationError("The manifest issuer must not contain credentials, a query or a fragment.");
1709
+ }
1705
1710
  const manifest = await buildManifest(declaration);
1706
1711
  const discovery = new Discovery(config.issuer, timeoutMs, 36e5);
1707
1712
  const token = await mintToken(config, discovery, timeoutMs);
@@ -1709,6 +1714,7 @@ async function publishManifest(config, declaration) {
1709
1714
  `${config.issuer.replace(/\/$/, "")}/api/v1/apps/manifest`,
1710
1715
  {
1711
1716
  method: "POST",
1717
+ redirect: "error",
1712
1718
  headers: {
1713
1719
  authorization: `Bearer ${token}`,
1714
1720
  accept: "application/json",
@@ -1719,10 +1725,20 @@ async function publishManifest(config, declaration) {
1719
1725
  timeoutMs
1720
1726
  );
1721
1727
  if (!response.ok) {
1722
- throw new AuthenticationError(`Manifest push failed: HTTP ${response.status} ${await response.text()}`);
1728
+ throw new AuthenticationError(`Manifest push failed: HTTP ${response.status}`);
1729
+ }
1730
+ const json2 = await response.json().catch(() => {
1731
+ throw new AuthenticationError("Cbox ID returned an invalid manifest acknowledgement.");
1732
+ });
1733
+ const checksum = await sha256Hex(canonicalManifestJson(declaration.permissions ?? [], declaration.roles ?? []));
1734
+ if (json2 === null || typeof json2 !== "object" || Array.isArray(json2)) {
1735
+ throw new AuthenticationError("Cbox ID did not acknowledge the authorization manifest.");
1723
1736
  }
1724
- const json2 = await response.json();
1725
- return json2 !== null && typeof json2 === "object" ? json2 : {};
1737
+ const summary = json2;
1738
+ if (summary.client_id !== config.clientId || summary.version !== manifest.version || summary.checksum !== checksum || typeof summary.unchanged !== "boolean" || !Number.isInteger(summary.roles_declared) || !Number.isInteger(summary.permissions_declared) || (summary.roles_declared ?? -1) < 0 || (summary.permissions_declared ?? -1) < 0 || !summary.unchanged && (summary.roles_declared !== manifest.roles.length || summary.permissions_declared !== manifest.permissions.length)) {
1739
+ throw new AuthenticationError("Cbox ID did not acknowledge this app and its complete authorization manifest.");
1740
+ }
1741
+ return summary;
1726
1742
  }
1727
1743
  async function mintToken(config, discovery, timeoutMs) {
1728
1744
  const response = await fetchWithTimeout(
@@ -1730,6 +1746,7 @@ async function mintToken(config, discovery, timeoutMs) {
1730
1746
  {
1731
1747
  method: "POST",
1732
1748
  headers: { "content-type": "application/x-www-form-urlencoded" },
1749
+ redirect: "error",
1733
1750
  body: new URLSearchParams({
1734
1751
  grant_type: "client_credentials",
1735
1752
  client_id: config.clientId,
@@ -1740,7 +1757,7 @@ async function mintToken(config, discovery, timeoutMs) {
1740
1757
  timeoutMs
1741
1758
  );
1742
1759
  const json2 = await response.json().catch(() => ({}));
1743
- if (typeof json2.access_token !== "string" || json2.access_token === "") {
1760
+ if (!response.ok || typeof json2.access_token !== "string" || json2.access_token === "") {
1744
1761
  throw new AuthenticationError(
1745
1762
  "Could not obtain an apps.manifest access token \u2014 check the client credentials and that the client holds the apps.manifest scope."
1746
1763
  );
@@ -1862,6 +1879,8 @@ async function fetchWithTimeout(url, init, timeoutMs) {
1862
1879
  const timer = setTimeout(() => controller.abort(), timeoutMs);
1863
1880
  try {
1864
1881
  return await fetch(url, { ...init, signal: controller.signal });
1882
+ } catch {
1883
+ throw new AuthenticationError("Cbox ID manifest publication could not be verified.");
1865
1884
  } finally {
1866
1885
  clearTimeout(timer);
1867
1886
  }
@@ -1875,6 +1894,98 @@ function assertLegacyLogin(legacy) {
1875
1894
  }
1876
1895
  }
1877
1896
 
1897
+ // src/decisions.ts
1898
+ var PermissionDecision = class {
1899
+ constructor(subject, organization2, clientId, organizationRole, organizationActive, permissions2) {
1900
+ this.subject = subject;
1901
+ this.organization = organization2;
1902
+ this.clientId = clientId;
1903
+ this.organizationRole = organizationRole;
1904
+ this.organizationActive = organizationActive;
1905
+ this.permissions = permissions2;
1906
+ }
1907
+ subject;
1908
+ organization;
1909
+ clientId;
1910
+ organizationRole;
1911
+ organizationActive;
1912
+ permissions;
1913
+ allows(permission) {
1914
+ return this.organizationActive && this.permissions[permission] === true;
1915
+ }
1916
+ allowed() {
1917
+ const values = Object.values(this.permissions);
1918
+ return this.organizationActive && values.length > 0 && values.every((value) => value === true);
1919
+ }
1920
+ };
1921
+ async function checkPermissions(config, accessToken, subject, organization2, permissions2) {
1922
+ assertSecureIssuer(config.issuer);
1923
+ const issuer = new URL(config.issuer);
1924
+ if (issuer.username || issuer.password || issuer.search || issuer.hash || !config.clientId || !subject || organization2 === "" || !accessToken || accessToken.length > 32768 || /[\x00-\x20\x7f]/.test(accessToken) || !permissions2.length || permissions2.length > 50 || permissions2.some((permission) => !permission.trim() || new TextEncoder().encode(permission).length > 255)) {
1925
+ throw new ConfigurationError("Permission decision request is not configured or valid.");
1926
+ }
1927
+ const requested = [...new Set(permissions2)];
1928
+ try {
1929
+ const response = await fetch(`${config.issuer.replace(/\/$/, "")}/oauth/decisions`, {
1930
+ method: "POST",
1931
+ redirect: "error",
1932
+ cache: "no-store",
1933
+ signal: AbortSignal.timeout(Math.max(1, config.timeoutMs ?? 1e4)),
1934
+ headers: { authorization: `Bearer ${accessToken}`, accept: "application/json", "content-type": "application/json" },
1935
+ body: JSON.stringify({ subject, org: organization2, permission: requested })
1936
+ });
1937
+ if (response.status !== 200) {
1938
+ await response.body?.cancel();
1939
+ throw new AuthenticationError("Permission decision could not be verified.", void 0, void 0, response.status);
1940
+ }
1941
+ const reader = response.body?.getReader();
1942
+ if (!reader) throw new AuthenticationError("Permission decision response is invalid.");
1943
+ let size = 0;
1944
+ let raw = "";
1945
+ const decoder = new TextDecoder();
1946
+ try {
1947
+ for (; ; ) {
1948
+ const { value, done } = await reader.read();
1949
+ if (done) break;
1950
+ size += value.byteLength;
1951
+ if (size > 65536) throw new AuthenticationError("Permission decision response exceeded its limit.");
1952
+ raw += decoder.decode(value, { stream: true });
1953
+ }
1954
+ raw += decoder.decode();
1955
+ } finally {
1956
+ await reader.cancel();
1957
+ reader.releaseLock();
1958
+ }
1959
+ const data = JSON.parse(raw);
1960
+ if (!record(data) || !record(data.subject) || data.mode !== "rbac" || data.subject.type !== "user" || data.subject.id !== subject || data.organization !== organization2 || data.client_id !== config.clientId || typeof data.organization_active !== "boolean" || typeof data.allowed !== "boolean" || !(data.org_role === null || typeof data.org_role === "string" && ["owner", "admin", "developer", "member", "viewer"].includes(data.org_role)) || !Array.isArray(data.results) || data.results.length !== requested.length) {
1961
+ throw new AuthenticationError("Permission decision response binding is invalid.");
1962
+ }
1963
+ const checks = /* @__PURE__ */ Object.create(null);
1964
+ for (const result of data.results) {
1965
+ if (!record(result) || typeof result.permission !== "string" || typeof result.allowed !== "boolean" || !requested.includes(result.permission) || Object.hasOwn(checks, result.permission)) {
1966
+ throw new AuthenticationError("Permission decision result is invalid.");
1967
+ }
1968
+ checks[result.permission] = result.allowed;
1969
+ }
1970
+ const decision = new PermissionDecision(
1971
+ subject,
1972
+ organization2,
1973
+ config.clientId,
1974
+ data.org_role,
1975
+ data.organization_active,
1976
+ Object.freeze(checks)
1977
+ );
1978
+ if (data.allowed !== decision.allowed()) throw new AuthenticationError("Permission decision result is inconsistent.");
1979
+ return decision;
1980
+ } catch (error) {
1981
+ if (error instanceof AuthenticationError) throw error;
1982
+ throw new AuthenticationError("Permission decision could not be verified.");
1983
+ }
1984
+ }
1985
+ function record(value) {
1986
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1987
+ }
1988
+
1878
1989
  exports.AuthenticationError = AuthenticationError;
1879
1990
  exports.CboxIdClient = CboxIdClient;
1880
1991
  exports.CboxIdError = CboxIdError;
@@ -1883,6 +1994,7 @@ exports.ConfigurationError = ConfigurationError;
1883
1994
  exports.FEATURE_FLAGS_SCOPE = FEATURE_FLAGS_SCOPE;
1884
1995
  exports.FrontendApiError = FrontendApiError;
1885
1996
  exports.InvalidStateError = InvalidStateError;
1997
+ exports.PermissionDecision = PermissionDecision;
1886
1998
  exports.PipeLeaseDeniedError = PipeLeaseDeniedError;
1887
1999
  exports.PipeLeaseError = PipeLeaseError;
1888
2000
  exports.PipeNotConnectedError = PipeNotConnectedError;
@@ -1892,6 +2004,7 @@ exports.VaultClient = VaultClient;
1892
2004
  exports.actor = actor;
1893
2005
  exports.buildManifest = buildManifest;
1894
2006
  exports.challenge = challenge;
2007
+ exports.checkPermissions = checkPermissions;
1895
2008
  exports.createLegacyVerifier = createLegacyVerifier;
1896
2009
  exports.createVerifier = createVerifier;
1897
2010
  exports.defineAuthz = defineAuthz;