@canopy-io/node 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @canopy-io/node
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f2ee649: `LocalAuthorizer` is held to Canopy's own authorization rules. Its suite replays the conformance vectors Canopy publishes at `/conformance/authorization.json`, the same cases Canopy's API suite replays end to end, and `npm run generate` keeps the copy in step with the published one.
8
+
9
+ The grant roots (`GET /api/v1/identities/:id/grants`) and the hierarchy's parent edges (`GET /api/v1/nodes/parents`) now carry the `model_version` of the authorization model they describe. `LocalAuthorizer` refuses a version it does not know with `CanopyAuthorizerError` (`authorizer.model_version_unknown`), which `authorizationErrorStatus` and the NestJS guard answer as a `500` misconfiguration, rather than walking rows whose meaning has changed. It knows version 1. This release requires a Canopy that sends `model_version`; against one that does not, every check is refused, so upgrade after the API has it.
10
+
11
+ `@canopy-io/nestjs` picks up the new authorizer; its own options and behaviour are unchanged.
12
+
3
13
  ## 0.9.0
4
14
 
5
15
  ### Minor Changes
package/README.md CHANGED
@@ -278,8 +278,12 @@ instances behind a load balancer, each keeps its own cache until its TTL runs
278
278
  out or it is told to invalidate too.
279
279
 
280
280
  A failed check (Canopy unreachable, a credential that cannot read the
281
- hierarchy, an identity not in the Environment) throws rather than answering
282
- `false`. `authorizationErrorStatus` turns the error into the response to send:
281
+ hierarchy, an identity not in the Environment, or a Canopy serving an
282
+ authorization model this version does not know) throws rather than answering
283
+ `false`. The grant roots and the hierarchy each carry the `model_version` of
284
+ the rules they describe; an unknown one is refused with `CanopyAuthorizerError`
285
+ (`authorizer.model_version_unknown`) instead of being walked as if the rules
286
+ had not changed, and upgrading the package is the fix. `authorizationErrorStatus` turns the error into the response to send:
283
287
  `503` when Canopy did not answer, `500` for a misconfiguration no retry fixes,
284
288
  `403` for an identity Canopy does not know. The NestJS guard uses the same
285
289
  mapping.
package/dist/index.cjs CHANGED
@@ -1155,6 +1155,16 @@ var MAX_WALK_DEPTH = 256;
1155
1155
  var PAGE_SIZE = 100;
1156
1156
  var MAX_PAGES = 1e4;
1157
1157
  var MAX_TREE_WALKS = 3;
1158
+ var KNOWN_MODEL_VERSIONS = /* @__PURE__ */ new Set([1]);
1159
+ function understood(page, path) {
1160
+ if (!KNOWN_MODEL_VERSIONS.has(page.model_version)) {
1161
+ throw new CanopyAuthorizerError(
1162
+ "authorizer.model_version_unknown",
1163
+ `${path} describes authorization model version ${page.model_version}, which this version of @canopy-io/node does not understand. It knows ${[...KNOWN_MODEL_VERSIONS].join(", ")}. Upgrade @canopy-io/node; until then this authorizer cannot decide, rather than guess.`
1164
+ );
1165
+ }
1166
+ return page;
1167
+ }
1158
1168
  var GRANTS_PATH = (identityId) => `/api/v1/identities/${encodeURIComponent(identityId)}/grants`;
1159
1169
  var PARENTS_PATH = "/api/v1/nodes/parents";
1160
1170
  var LocalAuthorizer = class {
@@ -1330,7 +1340,7 @@ var LocalAuthorizer = class {
1330
1340
  return this.client.request("GET", path, {
1331
1341
  ...this.readOptions,
1332
1342
  query
1333
- });
1343
+ }).then((page) => understood(page, path));
1334
1344
  },
1335
1345
  { page: 1, take: PAGE_SIZE },
1336
1346
  { maxPages: MAX_PAGES }
@@ -1410,12 +1420,15 @@ var LocalAuthorizer = class {
1410
1420
  }
1411
1421
  parentsFirstPage(etag) {
1412
1422
  this.stats.treePages += 1;
1413
- return this.client.requestConditional(
1414
- "GET",
1415
- PARENTS_PATH,
1416
- etag,
1417
- { ...this.readOptions, query: { page: 1, take: PAGE_SIZE } }
1418
- );
1423
+ return this.client.requestConditional("GET", PARENTS_PATH, etag, {
1424
+ ...this.readOptions,
1425
+ query: { page: 1, take: PAGE_SIZE }
1426
+ }).then((result) => {
1427
+ if (result.modified) {
1428
+ understood(result.data, PARENTS_PATH);
1429
+ }
1430
+ return result;
1431
+ });
1419
1432
  }
1420
1433
  /** The first page's edges followed by every later page's. */
1421
1434
  async parentsRemainingPages(first) {
@@ -1429,7 +1442,7 @@ var LocalAuthorizer = class {
1429
1442
  return this.client.request("GET", PARENTS_PATH, {
1430
1443
  ...this.readOptions,
1431
1444
  query
1432
- });
1445
+ }).then((page) => understood(page, PARENTS_PATH));
1433
1446
  },
1434
1447
  { page: first.pagination.page + 1, take: PAGE_SIZE },
1435
1448
  { maxPages: MAX_PAGES - 1 }