@canopy-io/node 0.11.0 → 0.12.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 +8 -0
- package/README.md +14 -4
- package/dist/index.cjs +25 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1506 -256
- package/dist/index.d.ts +1506 -256
- package/dist/index.js +25 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @canopy-io/node
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 10d3583: Organization membership is admission, not a role. Identity tokens carry `org_roles` (the names of the roles held at the organization node, sorted, possibly empty) in place of `org_role`; `orgContext(claims)` returns `{ orgId, orgRoles }`, and refuses a token carrying one claim without the other. An empty `orgRoles` is a member holding no role at the organization node, so authorize by evaluating rather than by the list. The conformance bundle gains the `organization-membership` vector: a grant inside an organization counts only while the membership is active, which the grants `LocalAuthorizer` caches already reflect.
|
|
8
|
+
|
|
9
|
+
`organizations.changeMemberRole()` is gone with its route: a member's role at the organization is an assignment at the organization node, written through `assignments.create()`, `assignments.update()` and `assignments.delete()` with the organization node as `node_id`. `assignments.update()` takes `{ ifMatch }` and sends it as `If-Match`; a stale version answers `409 assignment.version_mismatch`. `organizations.removeMember()` deactivates the membership and revokes every role the member held in the organization. Member rows carry `status`, `source`, `joined_at` and `roles[]` in place of the single `role`.
|
|
10
|
+
|
|
3
11
|
## 0.11.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -143,8 +143,10 @@ throws with neither. Getting it wrong fails closed: tokens are rejected, never
|
|
|
143
143
|
mistakenly accepted.
|
|
144
144
|
|
|
145
145
|
With an Environment's **organizations** container on, an identity token also
|
|
146
|
-
names the organization the session is acting in and the
|
|
147
|
-
|
|
146
|
+
names the organization the session is acting in and the roles held at that
|
|
147
|
+
organization's node. Read the pair through `orgContext` rather than off the raw
|
|
148
|
+
claims — Canopy mints the two together, and the helper refuses a half-present
|
|
149
|
+
pair:
|
|
148
150
|
|
|
149
151
|
```ts
|
|
150
152
|
import { orgContext } from "@canopy-io/node";
|
|
@@ -153,12 +155,20 @@ const org = orgContext(claims);
|
|
|
153
155
|
|
|
154
156
|
if (org) {
|
|
155
157
|
org.orgId; // the organization — also a hierarchy node id
|
|
156
|
-
org.
|
|
158
|
+
org.orgRoles; // the roles held at its node, by name; [] for a member holding none there
|
|
157
159
|
}
|
|
158
160
|
```
|
|
159
161
|
|
|
160
162
|
`null` is an ordinary answer: every token from an Environment without the
|
|
161
|
-
container on, and every identity that belongs to no organization yet.
|
|
163
|
+
container on, and every identity that belongs to no organization yet. An empty
|
|
164
|
+
`orgRoles` is not: membership is admission, and a member may hold roles only
|
|
165
|
+
beneath the organization. Authorize by evaluating, never by the list.
|
|
166
|
+
|
|
167
|
+
A grant inside an organization counts only while the membership is active.
|
|
168
|
+
The grants `LocalAuthorizer` caches already apply that: a deactivated
|
|
169
|
+
membership drops every grant inside the organization from the next read, so
|
|
170
|
+
the authorizer sees it within the cache TTL, and sooner on
|
|
171
|
+
`invalidate(identityId)` when an `organization.member.removed` webhook arrives.
|
|
162
172
|
|
|
163
173
|
Tokens also carry `amr`, how the session was proven (`pwd`, `otp` or `sso`,
|
|
164
174
|
plus `mfa` once a second factor was verified), so a backend can insist on
|
package/dist/index.cjs
CHANGED
|
@@ -473,6 +473,14 @@ function paginate(fetchPage, initial = {}, options = {}) {
|
|
|
473
473
|
return new Paginator(fetchPage, initial, options);
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
// src/schema.ts
|
|
477
|
+
function withConcurrency(options = {}) {
|
|
478
|
+
if (options.ifMatch === void 0) {
|
|
479
|
+
return {};
|
|
480
|
+
}
|
|
481
|
+
return { headers: { "If-Match": options.ifMatch } };
|
|
482
|
+
}
|
|
483
|
+
|
|
476
484
|
// src/resources/assignments.ts
|
|
477
485
|
var Assignments = class {
|
|
478
486
|
constructor(client) {
|
|
@@ -498,11 +506,16 @@ var Assignments = class {
|
|
|
498
506
|
create(input) {
|
|
499
507
|
return this.client.request("POST", "/api/v1/assignments", { body: input });
|
|
500
508
|
}
|
|
501
|
-
|
|
509
|
+
/**
|
|
510
|
+
* Change the role or the window of a grant. Pass the `version` the
|
|
511
|
+
* assignment was read with as `ifMatch`; a stale one answers 409
|
|
512
|
+
* `assignment.version_mismatch` rather than overwriting a newer change.
|
|
513
|
+
*/
|
|
514
|
+
update(id, input, options = {}) {
|
|
502
515
|
return this.client.request(
|
|
503
516
|
"PATCH",
|
|
504
517
|
`/api/v1/assignments/${encodeURIComponent(id)}`,
|
|
505
|
-
{ body: input }
|
|
518
|
+
{ body: input, ...withConcurrency(options) }
|
|
506
519
|
);
|
|
507
520
|
}
|
|
508
521
|
delete(id) {
|
|
@@ -677,14 +690,6 @@ var Identities = class {
|
|
|
677
690
|
}
|
|
678
691
|
};
|
|
679
692
|
|
|
680
|
-
// src/schema.ts
|
|
681
|
-
function withConcurrency(options = {}) {
|
|
682
|
-
if (options.ifMatch === void 0) {
|
|
683
|
-
return {};
|
|
684
|
-
}
|
|
685
|
-
return { headers: { "If-Match": options.ifMatch } };
|
|
686
|
-
}
|
|
687
|
-
|
|
688
693
|
// src/resources/organizations.ts
|
|
689
694
|
var Organizations = class {
|
|
690
695
|
constructor(client) {
|
|
@@ -734,7 +739,7 @@ var Organizations = class {
|
|
|
734
739
|
deleteAll() {
|
|
735
740
|
return this.client.request("DELETE", "/api/v1/organizations");
|
|
736
741
|
}
|
|
737
|
-
// ── Members:
|
|
742
|
+
// ── Members: admission to the organization, apart from the roles held in it ──
|
|
738
743
|
listMembers(id, query = {}) {
|
|
739
744
|
return paginate(
|
|
740
745
|
(params) => this.client.request("GET", `/api/v1/organizations/${enc(id)}/members`, {
|
|
@@ -750,14 +755,12 @@ var Organizations = class {
|
|
|
750
755
|
{ body: input }
|
|
751
756
|
);
|
|
752
757
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
/** Revokes that organization's one role; the identity and its other memberships are untouched. */
|
|
758
|
+
/**
|
|
759
|
+
* Deactivates the membership and revokes every role the member holds at
|
|
760
|
+
* the organization's nodes; the identity and its other memberships are
|
|
761
|
+
* untouched. A role in the organization is changed through the
|
|
762
|
+
* organization's assignment routes, not here.
|
|
763
|
+
*/
|
|
761
764
|
removeMember(id, identityId) {
|
|
762
765
|
return this.client.request(
|
|
763
766
|
"DELETE",
|
|
@@ -1771,11 +1774,11 @@ function resolveIssuer(options) {
|
|
|
1771
1774
|
);
|
|
1772
1775
|
}
|
|
1773
1776
|
function orgContext(claims) {
|
|
1774
|
-
const { org_id,
|
|
1775
|
-
if (typeof org_id !== "string" || org_id === "" || typeof
|
|
1777
|
+
const { org_id, org_roles } = claims;
|
|
1778
|
+
if (typeof org_id !== "string" || org_id === "" || !Array.isArray(org_roles) || org_roles.some((role) => typeof role !== "string" || role === "")) {
|
|
1776
1779
|
return null;
|
|
1777
1780
|
}
|
|
1778
|
-
return { orgId: org_id,
|
|
1781
|
+
return { orgId: org_id, orgRoles: [...org_roles] };
|
|
1779
1782
|
}
|
|
1780
1783
|
function decodeBase64Url(value) {
|
|
1781
1784
|
const padded = value.replace(/-/g, "+").replace(/_/g, "/");
|