@canopy-io/node 0.3.3 → 0.3.5

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,51 @@
1
1
  # @canopy-io/node
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - bd5a478: Set up an organization's directory sync (SCIM) from your backend.
8
+
9
+ - `organizations.setDirectoryGrantableRoles` and `listDirectoryGrantableRoles` wrap the two new operations, `PUT`/`GET /api/v1/organizations/{id}/directory/grantable-roles`. Naming the roles an organization's identity provider may grant, and the one people arrive with, is what opens directory sync for it. Only your API key can set the list; an organization admin's token can read it and is refused changing it.
10
+ - The rest of an organization's directory is wrapped too: `getDirectory`, `createDirectory`, `removeDirectory`, `listDirectoryTokens`, `mintDirectoryToken`, `rotateDirectoryToken`, `revokeDirectoryToken`, `listDirectoryActivity`, `listDirectoryGroups`, `mapDirectoryGroup` and `unmapDirectoryGroup`. They were already typed and reachable through `client.request`.
11
+ - `base_url` on a directory and on a minted token is now the full URL, including the host, so it can be handed to a customer as it is. If you were putting your API host in front of it, stop.
12
+
13
+ The published spec gained 2 operations (132 to 134), both additive, so this is a patch.
14
+
15
+ ## 0.3.4
16
+
17
+ ### Patch Changes
18
+
19
+ - faefa0a: Type the organization sign-in surface: SSO connection, domains, recovery codes
20
+ and directory sync.
21
+
22
+ The API has let a backend configure an organization's own sign-in since
23
+ 2026-09-18, and nothing in this package said so. A consumer checking the SDK —
24
+ or the coding agent working for them — found no such operations and concluded
25
+ Canopy could not do it. The published spec now carries 132 operations (was
26
+ 102), all additive: no operation was removed or renamed, and no existing
27
+ request or response shape lost a field.
28
+
29
+ Newly typed, under `/api/v1/organizations/{id}`:
30
+
31
+ - **SSO connection** — create, read, update, remove, import SAML metadata,
32
+ activate, disable, service-provider details, recent sign-ins.
33
+ - **Domains** — list, claim (returns the TXT challenge), verify, remove.
34
+ Routing and just-in-time provisioning read only verified domains.
35
+ - **SSO recovery codes** — regenerate. Turning `require_sso` on through
36
+ `updatePolicy` issues the first batch.
37
+ - **Test sign-in** — start, and read the outcome.
38
+ - **Directory sync** — create, read, remove; mint, rotate and revoke SCIM
39
+ tokens; activity; groups and their role mappings; the roles a directory may
40
+ grant.
41
+ - **Roles** — the roles assignable in the organization.
42
+
43
+ Plus Environment branding (read and update).
44
+
45
+ These are typed and callable today through `canopy.client.request` with
46
+ `RequestBody<…>` / `ResponseBody<…>`; resource methods for them are not in this
47
+ release. No caller changes are needed to take it.
48
+
3
49
  ## 0.3.3
4
50
 
5
51
  ### Patch Changes
package/README.md CHANGED
@@ -136,8 +136,8 @@ plus `mfa` once a second factor was verified), so a backend can insist on
136
136
  `claims.amr?.includes("mfa")` before a sensitive action.
137
137
 
138
138
  The organizations themselves are wrapped on the client: provision a tenant,
139
- manage its members and invitations, tighten its sign-in policy, and bind its
140
- identity provider.
139
+ manage its members and invitations, tighten its sign-in policy, bind its
140
+ identity provider, and set up its directory sync.
141
141
 
142
142
  ```ts
143
143
  const acme = await canopy.organizations.create({ name: "Acme Corp" });
@@ -150,6 +150,25 @@ await canopy.organizations.addMember(acme.id, {
150
150
  await canopy.organizations.updatePolicy(acme.id, { mfa_required: true });
151
151
  ```
152
152
 
153
+ Directory sync (SCIM) opens when you name the roles an organization's identity
154
+ provider may grant; the organization's own admin can read that list but not
155
+ change it. The token is returned once, with the full base URL its connector
156
+ needs.
157
+
158
+ ```ts
159
+ await canopy.organizations.setDirectoryGrantableRoles(acme.id, {
160
+ role_ids: [memberRoleId, leadRoleId],
161
+ default_role_id: memberRoleId,
162
+ });
163
+
164
+ await canopy.organizations.createDirectory(acme.id);
165
+
166
+ const { token, base_url } = await canopy.organizations.mintDirectoryToken(
167
+ acme.id,
168
+ { name: "Okta" },
169
+ );
170
+ ```
171
+
153
172
  ### Authorizing without a call per request
154
173
 
155
174
  Asking "may this identity act _here_" on every request puts Canopy in your
package/dist/index.cjs CHANGED
@@ -770,6 +770,127 @@ var Organizations = class {
770
770
  `/api/v1/organizations/${enc(id)}/sso-connections/${enc(connectionId)}`
771
771
  );
772
772
  }
773
+ // ── Directory sync: the organization's own SCIM connection ──
774
+ /**
775
+ * The roles this organization's identity provider may grant, and the one
776
+ * people arrive with. An empty list means directory sync is not open for
777
+ * the organization.
778
+ */
779
+ listDirectoryGrantableRoles(id) {
780
+ return this.client.request(
781
+ "GET",
782
+ `/api/v1/organizations/${enc(id)}/directory/grantable-roles`
783
+ );
784
+ }
785
+ /**
786
+ * Replace the list in one act, which is how directory sync is opened for
787
+ * an organization. Exactly one role is `default_role_id`, the one people
788
+ * arrive with. Withdrawing a role unmaps the groups naming it and keeps
789
+ * their members in the organization; an empty list is refused while a
790
+ * directory is connected. Your API key only: an organization admin's
791
+ * token can read the list but is refused changing it.
792
+ */
793
+ setDirectoryGrantableRoles(id, input) {
794
+ return this.client.request(
795
+ "PUT",
796
+ `/api/v1/organizations/${enc(id)}/directory/grantable-roles`,
797
+ { body: input }
798
+ );
799
+ }
800
+ /**
801
+ * The organization's directory, its full SCIM base URL, and the roles it
802
+ * may grant. `id` is null until a directory is connected.
803
+ */
804
+ getDirectory(id) {
805
+ return this.client.request(
806
+ "GET",
807
+ `/api/v1/organizations/${enc(id)}/directory`
808
+ );
809
+ }
810
+ /** Connect a directory. Refused until the organization's roles are set. */
811
+ createDirectory(id) {
812
+ return this.client.request(
813
+ "POST",
814
+ `/api/v1/organizations/${enc(id)}/directory`
815
+ );
816
+ }
817
+ /**
818
+ * End the connection. The people it provisioned keep their accounts and
819
+ * their membership; what ends is the provider's ability to push.
820
+ */
821
+ removeDirectory(id) {
822
+ return this.client.request(
823
+ "DELETE",
824
+ `/api/v1/organizations/${enc(id)}/directory`
825
+ );
826
+ }
827
+ listDirectoryTokens(id) {
828
+ return this.client.request(
829
+ "GET",
830
+ `/api/v1/organizations/${enc(id)}/directory/tokens`
831
+ );
832
+ }
833
+ /**
834
+ * The raw token is in this answer and nowhere else, alongside the full
835
+ * base URL the identity provider's connector needs with it.
836
+ */
837
+ mintDirectoryToken(id, input = {}) {
838
+ return this.client.request(
839
+ "POST",
840
+ `/api/v1/organizations/${enc(id)}/directory/tokens`,
841
+ { body: input }
842
+ );
843
+ }
844
+ /**
845
+ * A replacement token, shown once. The old one keeps working for a short
846
+ * overlap, so the provider can be moved across without an outage.
847
+ */
848
+ rotateDirectoryToken(id, tokenId, input = {}) {
849
+ return this.client.request(
850
+ "POST",
851
+ `/api/v1/organizations/${enc(id)}/directory/tokens/${enc(tokenId)}/rotate`,
852
+ { body: input }
853
+ );
854
+ }
855
+ revokeDirectoryToken(id, tokenId) {
856
+ return this.client.request(
857
+ "DELETE",
858
+ `/api/v1/organizations/${enc(id)}/directory/tokens/${enc(tokenId)}`
859
+ );
860
+ }
861
+ /** The organization's own provisioning events, newest first. */
862
+ listDirectoryActivity(id) {
863
+ return this.client.request(
864
+ "GET",
865
+ `/api/v1/organizations/${enc(id)}/directory/activity`
866
+ );
867
+ }
868
+ /** The groups the provider has pushed, each with its role mapping. */
869
+ listDirectoryGroups(id) {
870
+ return this.client.request(
871
+ "GET",
872
+ `/api/v1/organizations/${enc(id)}/directory/groups`
873
+ );
874
+ }
875
+ /**
876
+ * Grant a listed role to every member of a pushed group, at the
877
+ * organization. A role not on the organization's list is refused with
878
+ * `scim.role_not_grantable`.
879
+ */
880
+ mapDirectoryGroup(id, groupId, input) {
881
+ return this.client.request(
882
+ "PUT",
883
+ `/api/v1/organizations/${enc(id)}/directory/groups/${enc(groupId)}/mapping`,
884
+ { body: input }
885
+ );
886
+ }
887
+ /** Members fall back to the arrival role and stay in the organization. */
888
+ unmapDirectoryGroup(id, groupId) {
889
+ return this.client.request(
890
+ "DELETE",
891
+ `/api/v1/organizations/${enc(id)}/directory/groups/${enc(groupId)}/mapping`
892
+ );
893
+ }
773
894
  };
774
895
  function enc(segment) {
775
896
  return encodeURIComponent(segment);