@canopy-io/node 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/index.cjs +625 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9688 -0
- package/dist/index.d.ts +9688 -0
- package/dist/index.js +611 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var CanopyError = class extends Error {
|
|
3
|
+
name = "CanopyError";
|
|
4
|
+
statusCode;
|
|
5
|
+
code;
|
|
6
|
+
details;
|
|
7
|
+
data;
|
|
8
|
+
/** The path and method that failed, for logging. */
|
|
9
|
+
request;
|
|
10
|
+
constructor(body, request) {
|
|
11
|
+
super(body.message);
|
|
12
|
+
this.statusCode = body.statusCode;
|
|
13
|
+
this.code = body.code;
|
|
14
|
+
this.details = body.details;
|
|
15
|
+
this.data = body.data;
|
|
16
|
+
this.request = request;
|
|
17
|
+
}
|
|
18
|
+
/** A 429. `retryAfterMs` is populated when the server said how long to wait. */
|
|
19
|
+
get isRateLimited() {
|
|
20
|
+
return this.statusCode === 429;
|
|
21
|
+
}
|
|
22
|
+
/** 401 or 403 — the credential is wrong or not permitted here. */
|
|
23
|
+
get isAuthFailure() {
|
|
24
|
+
return this.statusCode === 401 || this.statusCode === 403;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var CanopyConnectionError = class extends Error {
|
|
28
|
+
name = "CanopyConnectionError";
|
|
29
|
+
request;
|
|
30
|
+
constructor(message, request, options) {
|
|
31
|
+
super(message, options);
|
|
32
|
+
this.request = request;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function isCanopyError(error) {
|
|
36
|
+
return error instanceof Error && error.name === "CanopyError";
|
|
37
|
+
}
|
|
38
|
+
function isCanopyConnectionError(error) {
|
|
39
|
+
return error instanceof Error && error.name === "CanopyConnectionError";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/client.ts
|
|
43
|
+
function isCursorPagination(pagination) {
|
|
44
|
+
return "next_cursor" in pagination;
|
|
45
|
+
}
|
|
46
|
+
var DEFAULT_BASE_URL = "https://auth.canopy-io.com";
|
|
47
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
48
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
49
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE"]);
|
|
50
|
+
var CanopyClient = class {
|
|
51
|
+
baseUrl;
|
|
52
|
+
timeoutMs;
|
|
53
|
+
maxRetries;
|
|
54
|
+
authHeaders;
|
|
55
|
+
extraHeaders;
|
|
56
|
+
fetchImpl;
|
|
57
|
+
constructor(options = {}) {
|
|
58
|
+
if (!options.apiKey && !options.accessToken) {
|
|
59
|
+
throw new TypeError(
|
|
60
|
+
"CanopyClient requires either `apiKey` (server-to-server) or `accessToken` (a user or identity JWT)."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
64
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
65
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
66
|
+
this.extraHeaders = options.headers ?? {};
|
|
67
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
68
|
+
if (typeof this.fetchImpl !== "function") {
|
|
69
|
+
throw new TypeError(
|
|
70
|
+
"No `fetch` available. Node 18+ provides one; on older runtimes pass `fetch` explicitly."
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
this.authHeaders = options.apiKey ? { "X-API-Key": options.apiKey } : { Authorization: `Bearer ${options.accessToken ?? ""}` };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Issue a request and return the payload with the envelope removed.
|
|
77
|
+
*
|
|
78
|
+
* `{ data }` → the resource
|
|
79
|
+
* `{ items, pagination }` → the whole object, so pagination survives
|
|
80
|
+
* `{ summary, results }` → the whole object
|
|
81
|
+
* 204 → undefined
|
|
82
|
+
*
|
|
83
|
+
* Non-2xx throws `CanopyError`; a request that never completed throws
|
|
84
|
+
* `CanopyConnectionError`.
|
|
85
|
+
*/
|
|
86
|
+
async request(method, path, options = {}) {
|
|
87
|
+
const url = this.buildUrl(path, options.query);
|
|
88
|
+
const upper = method.toUpperCase();
|
|
89
|
+
const retryable = options.idempotent ?? IDEMPOTENT_METHODS.has(upper);
|
|
90
|
+
let lastError;
|
|
91
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
92
|
+
if (attempt > 0) {
|
|
93
|
+
await delay(backoffMs(attempt, lastError));
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const response = await this.send(upper, url, options);
|
|
97
|
+
if (this.shouldRetry(response.status, retryable, attempt)) {
|
|
98
|
+
lastError = await this.toError(response, upper, path);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw await this.toError(response, upper, path);
|
|
103
|
+
}
|
|
104
|
+
return await this.unwrap(response);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof CanopyError) {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
lastError = error;
|
|
110
|
+
if (!retryable || attempt === this.maxRetries) {
|
|
111
|
+
throw new CanopyConnectionError(
|
|
112
|
+
`${upper} ${path} failed: ${describe(error)}`,
|
|
113
|
+
{ method: upper, path },
|
|
114
|
+
{ cause: error }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new CanopyConnectionError(
|
|
120
|
+
`${upper} ${path} exhausted ${this.maxRetries + 1} attempts`,
|
|
121
|
+
{ method: upper, path },
|
|
122
|
+
{ cause: lastError }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
shouldRetry(status, retryable, attempt) {
|
|
126
|
+
if (attempt >= this.maxRetries) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (status === 429) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return status >= 500 && retryable;
|
|
133
|
+
}
|
|
134
|
+
async send(method, url, options) {
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
const timer = this.timeoutMs > 0 ? setTimeout(() => controller.abort(), this.timeoutMs) : void 0;
|
|
137
|
+
const onAbort = () => controller.abort();
|
|
138
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
139
|
+
const headers = {
|
|
140
|
+
Accept: "application/json",
|
|
141
|
+
...this.extraHeaders,
|
|
142
|
+
...this.authHeaders
|
|
143
|
+
};
|
|
144
|
+
if (options.body !== void 0) {
|
|
145
|
+
headers["Content-Type"] = "application/json";
|
|
146
|
+
}
|
|
147
|
+
const init = { method, headers, signal: controller.signal };
|
|
148
|
+
if (options.body !== void 0) {
|
|
149
|
+
init.body = JSON.stringify(options.body);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
return await this.fetchImpl(url, init);
|
|
153
|
+
} finally {
|
|
154
|
+
if (timer !== void 0) {
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
}
|
|
157
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
buildUrl(path, query) {
|
|
161
|
+
const url = new URL(
|
|
162
|
+
path.startsWith("/") ? path : `/${path}`,
|
|
163
|
+
`${this.baseUrl}/`
|
|
164
|
+
);
|
|
165
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
166
|
+
if (value === void 0 || value === null) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
url.searchParams.set(key, String(value));
|
|
170
|
+
}
|
|
171
|
+
return url.toString();
|
|
172
|
+
}
|
|
173
|
+
async unwrap(response) {
|
|
174
|
+
if (response.status === 204) {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
const text = await response.text();
|
|
178
|
+
if (text === "") {
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
const parsed = JSON.parse(text);
|
|
182
|
+
if (parsed && typeof parsed === "object" && "data" in parsed) {
|
|
183
|
+
return parsed["data"];
|
|
184
|
+
}
|
|
185
|
+
return parsed;
|
|
186
|
+
}
|
|
187
|
+
async toError(response, method, path) {
|
|
188
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
189
|
+
let body = {
|
|
190
|
+
statusCode: response.status,
|
|
191
|
+
code: null,
|
|
192
|
+
message: `${response.status} ${response.statusText}`.trim()
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(await response.text());
|
|
196
|
+
if (parsed.error) {
|
|
197
|
+
body = {
|
|
198
|
+
...parsed.error,
|
|
199
|
+
statusCode: parsed.error.statusCode ?? response.status
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
const error = new CanopyError(body, { method, path });
|
|
205
|
+
if (retryAfter !== null) {
|
|
206
|
+
Object.defineProperty(error, "retryAfterMs", {
|
|
207
|
+
value: retryAfter,
|
|
208
|
+
enumerable: true
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return error;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
function parseRetryAfter(header) {
|
|
215
|
+
if (!header) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const seconds = Number(header);
|
|
219
|
+
if (Number.isFinite(seconds)) {
|
|
220
|
+
return Math.max(0, seconds * 1e3);
|
|
221
|
+
}
|
|
222
|
+
const date = Date.parse(header);
|
|
223
|
+
if (Number.isNaN(date)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return Math.max(0, date - Date.now());
|
|
227
|
+
}
|
|
228
|
+
function backoffMs(attempt, lastError) {
|
|
229
|
+
const advised = lastError && typeof lastError === "object" && "retryAfterMs" in lastError ? Number(lastError.retryAfterMs) : NaN;
|
|
230
|
+
if (Number.isFinite(advised)) {
|
|
231
|
+
return advised;
|
|
232
|
+
}
|
|
233
|
+
const base = 250 * 2 ** (attempt - 1);
|
|
234
|
+
return base + Math.random() * base;
|
|
235
|
+
}
|
|
236
|
+
function delay(ms) {
|
|
237
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
238
|
+
}
|
|
239
|
+
function describe(error) {
|
|
240
|
+
if (error instanceof Error) {
|
|
241
|
+
return error.name === "AbortError" ? "timed out or aborted" : error.message;
|
|
242
|
+
}
|
|
243
|
+
return String(error);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/pagination.ts
|
|
247
|
+
var DEFAULT_MAX_PAGES = 1e3;
|
|
248
|
+
var Paginator = class {
|
|
249
|
+
fetchPage;
|
|
250
|
+
initial;
|
|
251
|
+
maxPages;
|
|
252
|
+
constructor(fetchPage, initial = {}, options = {}) {
|
|
253
|
+
this.fetchPage = fetchPage;
|
|
254
|
+
this.initial = initial;
|
|
255
|
+
this.maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;
|
|
256
|
+
}
|
|
257
|
+
/** Every item across every page. */
|
|
258
|
+
async *[Symbol.asyncIterator]() {
|
|
259
|
+
for await (const page of this.pages()) {
|
|
260
|
+
for (const item of page.items) {
|
|
261
|
+
yield item;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Page by page, for callers that need the pagination metadata or want to
|
|
267
|
+
* process in batches rather than item by item.
|
|
268
|
+
*/
|
|
269
|
+
async *pages() {
|
|
270
|
+
let params = { ...this.initial };
|
|
271
|
+
let seenCursor = null;
|
|
272
|
+
let pageCount = 0;
|
|
273
|
+
for (; ; ) {
|
|
274
|
+
const page = await this.fetchPage(params);
|
|
275
|
+
pageCount++;
|
|
276
|
+
yield page;
|
|
277
|
+
const pagination = page.pagination;
|
|
278
|
+
if (!pagination) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (page.items.length === 0) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (pageCount >= this.maxPages) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (isCursorPagination(pagination)) {
|
|
288
|
+
const next = pagination.next_cursor;
|
|
289
|
+
if (next === null || next === seenCursor) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
seenCursor = next;
|
|
293
|
+
params = { ...params, cursor: next };
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (!pagination.has_next_page) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
params = { ...params, page: pagination.page + 1 };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Collect everything into an array.
|
|
304
|
+
*
|
|
305
|
+
* Bounded on purpose: an unbounded drain of an audit log will exhaust memory
|
|
306
|
+
* on a busy account, so the cap is a parameter rather than a footnote. Use
|
|
307
|
+
* the iterator for large feeds.
|
|
308
|
+
*/
|
|
309
|
+
async all(max = 1e4) {
|
|
310
|
+
const collected = [];
|
|
311
|
+
for await (const item of this) {
|
|
312
|
+
collected.push(item);
|
|
313
|
+
if (collected.length >= max) {
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return collected;
|
|
318
|
+
}
|
|
319
|
+
/** The first item, or undefined. Stops after one page. */
|
|
320
|
+
async first() {
|
|
321
|
+
for await (const item of this) {
|
|
322
|
+
return item;
|
|
323
|
+
}
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
function paginate(fetchPage, initial = {}, options = {}) {
|
|
328
|
+
return new Paginator(fetchPage, initial, options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/resources/assignments.ts
|
|
332
|
+
var Assignments = class {
|
|
333
|
+
constructor(client) {
|
|
334
|
+
this.client = client;
|
|
335
|
+
}
|
|
336
|
+
client;
|
|
337
|
+
/** Every assignment in the Application, across all nodes. */
|
|
338
|
+
list(query = {}) {
|
|
339
|
+
return paginate(
|
|
340
|
+
(params) => this.client.request("GET", "/api/v1/assignments/app-wide", {
|
|
341
|
+
query: params
|
|
342
|
+
}),
|
|
343
|
+
{ ...query }
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Grant a role at a node.
|
|
348
|
+
*
|
|
349
|
+
* Not retried automatically on a 5xx: a repeat could create a second grant,
|
|
350
|
+
* and the server cannot tell the two apart. If the call fails without an
|
|
351
|
+
* answer, read the current assignments before retrying.
|
|
352
|
+
*/
|
|
353
|
+
create(input) {
|
|
354
|
+
return this.client.request("POST", "/api/v1/assignments", { body: input });
|
|
355
|
+
}
|
|
356
|
+
update(id, input) {
|
|
357
|
+
return this.client.request(
|
|
358
|
+
"PATCH",
|
|
359
|
+
`/api/v1/assignments/${encodeURIComponent(id)}`,
|
|
360
|
+
{ body: input }
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
delete(id) {
|
|
364
|
+
return this.client.request(
|
|
365
|
+
"DELETE",
|
|
366
|
+
`/api/v1/assignments/${encodeURIComponent(id)}`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Grant many at once. Answers 207 with a per-item result rather than failing
|
|
371
|
+
* the batch, so inspect `results` — a 2xx here does not mean every grant
|
|
372
|
+
* succeeded.
|
|
373
|
+
*/
|
|
374
|
+
bulkCreate(input) {
|
|
375
|
+
return this.client.request("POST", "/api/v1/assignments/bulk-create", {
|
|
376
|
+
body: input
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/** Same partial-success contract as `bulkCreate`. */
|
|
380
|
+
bulkRemove(input) {
|
|
381
|
+
return this.client.request("POST", "/api/v1/assignments/bulk-remove", {
|
|
382
|
+
body: input
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/** Same partial-success contract as `bulkCreate`. */
|
|
386
|
+
bulkChangeRole(input) {
|
|
387
|
+
return this.client.request("POST", "/api/v1/assignments/bulk-change-role", {
|
|
388
|
+
body: input
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// src/resources/identities.ts
|
|
394
|
+
var Identities = class {
|
|
395
|
+
constructor(client) {
|
|
396
|
+
this.client = client;
|
|
397
|
+
}
|
|
398
|
+
client;
|
|
399
|
+
list(query = {}) {
|
|
400
|
+
return paginate(
|
|
401
|
+
(params) => this.client.request("GET", "/api/v1/identities", { query: params }),
|
|
402
|
+
{ ...query }
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
get(id) {
|
|
406
|
+
return this.client.request(
|
|
407
|
+
"GET",
|
|
408
|
+
`/api/v1/identities/${encodeURIComponent(id)}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
create(input) {
|
|
412
|
+
return this.client.request("POST", "/api/v1/identities", { body: input });
|
|
413
|
+
}
|
|
414
|
+
update(id, input) {
|
|
415
|
+
return this.client.request(
|
|
416
|
+
"PATCH",
|
|
417
|
+
`/api/v1/identities/${encodeURIComponent(id)}`,
|
|
418
|
+
{ body: input }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
delete(id) {
|
|
422
|
+
return this.client.request(
|
|
423
|
+
"DELETE",
|
|
424
|
+
`/api/v1/identities/${encodeURIComponent(id)}`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Deactivation is the reversible counterpart to `delete` — sessions are
|
|
429
|
+
* revoked and sign-in refused, but the identity and its assignments survive.
|
|
430
|
+
* Prefer it for offboarding you may need to undo.
|
|
431
|
+
*/
|
|
432
|
+
deactivate(id) {
|
|
433
|
+
return this.client.request(
|
|
434
|
+
"POST",
|
|
435
|
+
`/api/v1/identities/${encodeURIComponent(id)}/deactivate`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
activate(id) {
|
|
439
|
+
return this.client.request(
|
|
440
|
+
"POST",
|
|
441
|
+
`/api/v1/identities/${encodeURIComponent(id)}/activate`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
/** Every role this identity holds, and where. */
|
|
445
|
+
assignments(id) {
|
|
446
|
+
return this.client.request(
|
|
447
|
+
"GET",
|
|
448
|
+
`/api/v1/identities/${encodeURIComponent(id)}/assignments`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* The permissions this identity effectively holds, inheritance resolved.
|
|
453
|
+
*
|
|
454
|
+
* For enforcing a single check, use `permissions.evaluate` — it answers one
|
|
455
|
+
* question at the node that matters. This is for showing a person what
|
|
456
|
+
* someone can do.
|
|
457
|
+
*/
|
|
458
|
+
permissions(id) {
|
|
459
|
+
return this.client.request(
|
|
460
|
+
"GET",
|
|
461
|
+
`/api/v1/identities/${encodeURIComponent(id)}/permissions`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/resources/permissions.ts
|
|
467
|
+
var Permissions = class {
|
|
468
|
+
constructor(client) {
|
|
469
|
+
this.client = client;
|
|
470
|
+
}
|
|
471
|
+
client;
|
|
472
|
+
/**
|
|
473
|
+
* Ask whether an identity holds a permission.
|
|
474
|
+
*
|
|
475
|
+
* With `scope: "node"` the engine walks the node's lineage, so a permission
|
|
476
|
+
* granted on an ancestor is inherited at `node_id`. With `scope: "app_wide"`
|
|
477
|
+
* it answers the coarse "anywhere in this Application" question and returns
|
|
478
|
+
* `effective_node_id: null` — that answer must never be used to guard a
|
|
479
|
+
* resource that belongs to a specific node, which is why the scope is a
|
|
480
|
+
* required field rather than a default.
|
|
481
|
+
*/
|
|
482
|
+
evaluate(input) {
|
|
483
|
+
return this.client.request("POST", "/api/v1/permissions/evaluate", {
|
|
484
|
+
body: input
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Evaluate many decisions in one round trip.
|
|
489
|
+
*
|
|
490
|
+
* Prefer this to a loop over `evaluate` when rendering a screen: the checks
|
|
491
|
+
* are answered together instead of paying request latency for each.
|
|
492
|
+
*/
|
|
493
|
+
evaluateBulk(input) {
|
|
494
|
+
return this.client.request("POST", "/api/v1/permissions/evaluate/bulk", {
|
|
495
|
+
body: input
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* The same decision with its reasoning — which role granted it, which node
|
|
500
|
+
* it was inherited from. For debugging an unexpected allow or deny, not for
|
|
501
|
+
* the enforcement path.
|
|
502
|
+
*/
|
|
503
|
+
explain(input) {
|
|
504
|
+
return this.client.request("POST", "/api/v1/permissions/evaluate/explain", {
|
|
505
|
+
body: input
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/** Every permission in the Environment, page by page. */
|
|
509
|
+
list(query = {}) {
|
|
510
|
+
return paginate(
|
|
511
|
+
(params) => this.client.request("GET", "/api/v1/permissions", { query: params }),
|
|
512
|
+
{ ...query }
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
get(id) {
|
|
516
|
+
return this.client.request(
|
|
517
|
+
"GET",
|
|
518
|
+
`/api/v1/permissions/${encodeURIComponent(id)}`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
/** Define permissions. The request takes a batch, not a single key. */
|
|
522
|
+
create(input) {
|
|
523
|
+
return this.client.request("POST", "/api/v1/permissions", { body: input });
|
|
524
|
+
}
|
|
525
|
+
update(id, input) {
|
|
526
|
+
return this.client.request(
|
|
527
|
+
"PATCH",
|
|
528
|
+
`/api/v1/permissions/${encodeURIComponent(id)}`,
|
|
529
|
+
{ body: input }
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
delete(id) {
|
|
533
|
+
return this.client.request(
|
|
534
|
+
"DELETE",
|
|
535
|
+
`/api/v1/permissions/${encodeURIComponent(id)}`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/resources/roles.ts
|
|
541
|
+
var Roles = class {
|
|
542
|
+
constructor(client) {
|
|
543
|
+
this.client = client;
|
|
544
|
+
}
|
|
545
|
+
client;
|
|
546
|
+
list(query = {}) {
|
|
547
|
+
return paginate(
|
|
548
|
+
(params) => this.client.request("GET", "/api/v1/roles", { query: params }),
|
|
549
|
+
{ ...query }
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
get(id) {
|
|
553
|
+
return this.client.request(
|
|
554
|
+
"GET",
|
|
555
|
+
`/api/v1/roles/${encodeURIComponent(id)}`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
create(input) {
|
|
559
|
+
return this.client.request("POST", "/api/v1/roles", { body: input });
|
|
560
|
+
}
|
|
561
|
+
update(id, input) {
|
|
562
|
+
return this.client.request(
|
|
563
|
+
"PATCH",
|
|
564
|
+
`/api/v1/roles/${encodeURIComponent(id)}`,
|
|
565
|
+
{ body: input }
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
delete(id) {
|
|
569
|
+
return this.client.request(
|
|
570
|
+
"DELETE",
|
|
571
|
+
`/api/v1/roles/${encodeURIComponent(id)}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
permissions(id) {
|
|
575
|
+
return this.client.request(
|
|
576
|
+
"GET",
|
|
577
|
+
`/api/v1/roles/${encodeURIComponent(id)}/permissions`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Replaces the role's permissions wholesale — this is a PUT, so anything
|
|
582
|
+
* absent from `input` is removed. Read `permissions` first if you mean to add.
|
|
583
|
+
*/
|
|
584
|
+
setPermissions(id, input) {
|
|
585
|
+
return this.client.request(
|
|
586
|
+
"PUT",
|
|
587
|
+
`/api/v1/roles/${encodeURIComponent(id)}/permissions`,
|
|
588
|
+
{ body: input }
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/canopy.ts
|
|
594
|
+
var Canopy = class {
|
|
595
|
+
client;
|
|
596
|
+
permissions;
|
|
597
|
+
identities;
|
|
598
|
+
roles;
|
|
599
|
+
assignments;
|
|
600
|
+
constructor(options) {
|
|
601
|
+
this.client = new CanopyClient(options);
|
|
602
|
+
this.permissions = new Permissions(this.client);
|
|
603
|
+
this.identities = new Identities(this.client);
|
|
604
|
+
this.roles = new Roles(this.client);
|
|
605
|
+
this.assignments = new Assignments(this.client);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
export { Assignments, Canopy, CanopyClient, CanopyConnectionError, CanopyError, Identities, Paginator, Permissions, Roles, isCanopyConnectionError, isCanopyError, isCursorPagination, paginate };
|
|
610
|
+
//# sourceMappingURL=index.js.map
|
|
611
|
+
//# sourceMappingURL=index.js.map
|