@canopy-io/node 0.5.0 → 0.7.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
@@ -82,6 +82,15 @@ function isCanopyTokenError(error) {
82
82
  return error instanceof Error && error.name === "CanopyTokenError";
83
83
  }
84
84
 
85
+ // src/strings.ts
86
+ function withoutTrailingSlashes(value) {
87
+ let end = value.length;
88
+ while (end > 0 && value[end - 1] === "/") {
89
+ end -= 1;
90
+ }
91
+ return value.slice(0, end);
92
+ }
93
+
85
94
  // src/client.ts
86
95
  function isCursorPagination(pagination) {
87
96
  return "next_cursor" in pagination;
@@ -106,7 +115,7 @@ var CanopyClient = class {
106
115
  "CanopyClient requires either `apiKey` (server-to-server) or `accessToken` (a user or identity JWT)."
107
116
  );
108
117
  }
109
- this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
118
+ this.baseUrl = withoutTrailingSlashes(options.baseUrl ?? DEFAULT_BASE_URL);
110
119
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
111
120
  this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
112
121
  this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
@@ -550,29 +559,19 @@ var Identities = class {
550
559
  { body: input }
551
560
  );
552
561
  }
562
+ /**
563
+ * Takes the identity out of this key's Environment: its membership here is
564
+ * deactivated and its role assignments here are dropped. The identity and
565
+ * its access to other Environments are untouched. Deactivating an identity
566
+ * everywhere is an Account-level action, not available to an
567
+ * Environment-scoped key.
568
+ */
553
569
  delete(id) {
554
570
  return this.client.request(
555
571
  "DELETE",
556
572
  `/api/v1/identities/${encodeURIComponent(id)}`
557
573
  );
558
574
  }
559
- /**
560
- * Deactivation is the reversible counterpart to `delete` — sessions are
561
- * revoked and sign-in refused, but the identity and its assignments survive.
562
- * Prefer it for offboarding you may need to undo.
563
- */
564
- deactivate(id) {
565
- return this.client.request(
566
- "POST",
567
- `/api/v1/identities/${encodeURIComponent(id)}/deactivate`
568
- );
569
- }
570
- activate(id) {
571
- return this.client.request(
572
- "POST",
573
- `/api/v1/identities/${encodeURIComponent(id)}/activate`
574
- );
575
- }
576
575
  /**
577
576
  * Every role this identity holds, and where — across all pages.
578
577
  *
@@ -634,7 +633,7 @@ var Identities = class {
634
633
  * Ends every session the identity holds, so the next refresh on any device
635
634
  * fails and the person signs in again. Access tokens already issued keep
636
635
  * working until their short lifetime runs out. For a suspected compromise;
637
- * `deactivate` also refuses the next sign-in.
636
+ * `delete` also takes the identity out of this Environment.
638
637
  */
639
638
  revokeSessions(id) {
640
639
  return this.client.request(
@@ -1117,6 +1116,7 @@ var Canopy = class {
1117
1116
 
1118
1117
  // src/authorizer.ts
1119
1118
  var DEFAULT_TTL_MS = 6e4;
1119
+ var MAX_TTL_MS = 3e5;
1120
1120
  var MAX_WALK_DEPTH = 256;
1121
1121
  var LocalAuthorizer = class {
1122
1122
  client;
@@ -1143,6 +1143,11 @@ var LocalAuthorizer = class {
1143
1143
  constructor(client, options = {}) {
1144
1144
  this.client = client;
1145
1145
  this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
1146
+ if (!Number.isFinite(this.ttlMs) || this.ttlMs < 0 || this.ttlMs > MAX_TTL_MS) {
1147
+ throw new RangeError(
1148
+ `ttlMs must be between 0 and ${MAX_TTL_MS} (five minutes); got ${String(this.ttlMs)}. It is how long a revoked user keeps access, so it is capped.`
1149
+ );
1150
+ }
1146
1151
  this.now = options.now ?? (() => Date.now());
1147
1152
  this.readOptions = {
1148
1153
  ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
@@ -1210,7 +1215,9 @@ var LocalAuthorizer = class {
1210
1215
  * With no argument, everything goes: grants and the hierarchy tree. Not
1211
1216
  * needed in normal operation, where entries expire on their own; useful in
1212
1217
  * tests and after a change whose reach you cannot name (a role's
1213
- * permissions edited, a node moved).
1218
+ * permissions edited, a role deactivated or reactivated, a node moved). A
1219
+ * deactivated role grants nothing to anyone holding it, so until the cache
1220
+ * is cleared or expires, its holders keep being allowed locally.
1214
1221
  *
1215
1222
  * Multi-instance honesty: an invalidation reaches THIS process only. A
1216
1223
  * webhook lands on one instance behind a load balancer; the others serve
@@ -1330,12 +1337,29 @@ var LocalAuthorizer = class {
1330
1337
  };
1331
1338
 
1332
1339
  // src/verify.ts
1333
- var DEFAULT_ISSUER = "https://auth.canopy-io.com";
1340
+ var HOSTED_ORIGIN = "https://auth.canopy-io.com";
1334
1341
  var DEFAULT_JWKS_CACHE_MAX_AGE_MS = 10 * 60 * 1e3;
1335
1342
  var DEFAULT_JWKS_MIN_REFETCH_INTERVAL_MS = 30 * 1e3;
1336
1343
  var DEFAULT_JWKS_TIMEOUT_MS = 5e3;
1337
1344
  var PRINCIPAL_TYPES = /* @__PURE__ */ new Set(["user", "identity", "api_key", "platform"]);
1338
1345
  var DEFAULT_CLOCK_TOLERANCE_SEC = 60;
1346
+ function resolveIssuer(options) {
1347
+ const { environmentId, issuer } = options;
1348
+ if (environmentId && issuer) {
1349
+ throw new TypeError(
1350
+ "TokenVerifier takes environmentId or issuer, not both."
1351
+ );
1352
+ }
1353
+ if (issuer) {
1354
+ return withoutTrailingSlashes(issuer);
1355
+ }
1356
+ if (environmentId) {
1357
+ return `${HOSTED_ORIGIN}/environments/${environmentId}`;
1358
+ }
1359
+ throw new TypeError(
1360
+ "TokenVerifier needs environmentId (the Environment your tokens come from) or, on a custom auth domain or self-hosted instance, issuer."
1361
+ );
1362
+ }
1339
1363
  function orgContext(claims) {
1340
1364
  const { org_id, org_role } = claims;
1341
1365
  if (typeof org_id !== "string" || org_id === "" || typeof org_role !== "string" || org_role === "") {
@@ -1380,7 +1404,7 @@ var TokenVerifier = class {
1380
1404
  /** In-flight fetch, so a burst of requests triggers one call, not N. */
1381
1405
  inFlight = null;
1382
1406
  constructor(options = {}) {
1383
- this.issuer = (options.issuer ?? DEFAULT_ISSUER).replace(/\/+$/, "");
1407
+ this.issuer = resolveIssuer(options);
1384
1408
  this.audience = options.audience;
1385
1409
  this.jwksUri = options.jwksUri ?? `${this.issuer}/.well-known/jwks.json`;
1386
1410
  this.jwksCacheMaxAgeMs = options.jwksCacheMaxAgeMs ?? DEFAULT_JWKS_CACHE_MAX_AGE_MS;
@@ -1658,6 +1682,6 @@ function isWebhookEvent(delivery, name) {
1658
1682
  return delivery.event === name;
1659
1683
  }
1660
1684
 
1661
- export { Assignments, Canopy, CanopyAuthorizerError, CanopyClient, CanopyConnectionError, CanopyError, CanopyTokenError, Identities, LocalAuthorizer, Organizations, Paginator, Permissions, Roles, TokenVerifier, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, orgContext, paginate, withConcurrency };
1685
+ export { Assignments, Canopy, CanopyAuthorizerError, CanopyClient, CanopyConnectionError, CanopyError, CanopyTokenError, Identities, LocalAuthorizer, MAX_TTL_MS, Organizations, Paginator, Permissions, Roles, TokenVerifier, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, orgContext, paginate, withConcurrency };
1662
1686
  //# sourceMappingURL=index.js.map
1663
1687
  //# sourceMappingURL=index.js.map