@canopy-io/node 0.9.0 → 0.11.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 +27 -0
- package/README.md +53 -2
- package/dist/index.cjs +284 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -20
- package/dist/index.d.ts +316 -20
- package/dist/index.js +284 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -748,7 +748,7 @@ interface paths {
|
|
|
748
748
|
};
|
|
749
749
|
/**
|
|
750
750
|
* Get identity's effective permissions
|
|
751
|
-
* @description Returns the identity's effective
|
|
751
|
+
* @description Returns the identity's effective permission keys for the current Environment as a flat `items` array of strings. Without `node_id`, the Application-wide answer: every permission its role assignments grant anywhere. With `node_id`, the answer at that node: the permissions granted by assignments on the node's lineage, the list a screen renders its controls from. Returns `404` when the identity has no membership in this Environment, or when `node_id` names no node in it.
|
|
752
752
|
*/
|
|
753
753
|
get: operations["ApiIdentitiesController_getIdentityPermissions"];
|
|
754
754
|
put?: never;
|
|
@@ -759,6 +759,26 @@ interface paths {
|
|
|
759
759
|
patch?: never;
|
|
760
760
|
trace?: never;
|
|
761
761
|
};
|
|
762
|
+
"/api/v1/identities/{id}/accessible-nodes": {
|
|
763
|
+
parameters: {
|
|
764
|
+
query?: never;
|
|
765
|
+
header?: never;
|
|
766
|
+
path?: never;
|
|
767
|
+
cookie?: never;
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* Get where an identity may act with one permission
|
|
771
|
+
* @description Returns where the identity may act with `permission`, for a list query: `roots`, the nodes it holds the permission at that no other such node already covers, and `nodes`, every node beneath those roots, the roots included, when there are at most `max_nodes` of them (default 10,000; at most 50,000). Past the cap `nodes` is null and `complete` false, and the caller filters by `roots` against its own closure table or materialised path. `env_wide` is true when a root is the Environment's own root, so every node qualifies and no filter is needed; `nodes` is null then too. The same rules as `evaluate` decide the roots, so a list filtered by this answer agrees with the check a write makes. Returns `404` when the identity has no membership in this Environment.
|
|
772
|
+
*/
|
|
773
|
+
get: operations["ApiIdentitiesController_getIdentityAccessibleNodes"];
|
|
774
|
+
put?: never;
|
|
775
|
+
post?: never;
|
|
776
|
+
delete?: never;
|
|
777
|
+
options?: never;
|
|
778
|
+
head?: never;
|
|
779
|
+
patch?: never;
|
|
780
|
+
trace?: never;
|
|
781
|
+
};
|
|
762
782
|
"/api/v1/identities/{id}/grants": {
|
|
763
783
|
parameters: {
|
|
764
784
|
query?: never;
|
|
@@ -768,7 +788,7 @@ interface paths {
|
|
|
768
788
|
};
|
|
769
789
|
/**
|
|
770
790
|
* Get where an identity holds each permission
|
|
771
|
-
* @description Returns, for the current Environment, one page of `(permission, node_id)` rows: every permission the identity holds, paired with each hierarchy node its grants were made at — the nodes themselves, **not** expanded through their descendants. Rows are ordered by permission, then node; a caller building the whole map walks every page. A grant already means "this node and everything beneath it", so a caller answers a node-scoped question by walking up from the node in question and looking for one of these roots, using a copy of the parent edges fetched once from `GET /api/v1/nodes/parents` and shared across identities. Answers both scopes without a further call: the permission appearing at all is the Application-wide answer, and the walk is the node-scoped one.
|
|
791
|
+
* @description Returns, for the current Environment, one page of `(permission, node_id)` rows: every permission the identity holds, paired with each hierarchy node its grants were made at — the nodes themselves, **not** expanded through their descendants. Rows are ordered by permission, then node; a caller building the whole map walks every page. A grant already means "this node and everything beneath it", so a caller answers a node-scoped question by walking up from the node in question and looking for one of these roots, using a copy of the parent edges fetched once from `GET /api/v1/nodes/parents` and shared across identities. Answers both scopes without a further call: the permission appearing at all is the Application-wide answer, and the walk is the node-scoped one. By default, assignments outside their effective window are excluded. With `include=windows`, every assignment is a row carrying `effective_from` and `effective_to` (null for an unbounded side), in effect or not, so a caller applies the window itself: it can say why a check fails (the grant has ended, or has not begun) and drop a grant at its boundary without reading again. Returns `404` when the identity has no membership in this Environment.
|
|
772
792
|
*/
|
|
773
793
|
get: operations["ApiIdentitiesController_getIdentityGrants"];
|
|
774
794
|
put?: never;
|
|
@@ -2739,6 +2759,18 @@ interface components {
|
|
|
2739
2759
|
/** Format: date-time */
|
|
2740
2760
|
updated_at: string;
|
|
2741
2761
|
};
|
|
2762
|
+
IdentityAccessibleNodesResponseDto: {
|
|
2763
|
+
/** @example reports.view */
|
|
2764
|
+
permission: string;
|
|
2765
|
+
/** @description True when a root is the Environment's own root, so every node qualifies and a list needs no filter. `nodes` is null then. */
|
|
2766
|
+
env_wide: boolean;
|
|
2767
|
+
/** @description The nodes the permission is granted at, in effect now, that no other such node already covers. Each reaches everything beneath it. */
|
|
2768
|
+
roots: string[];
|
|
2769
|
+
/** @description Every node beneath the roots, the roots included, for an `IN (...)` filter. Null when `env_wide`, or when there are more than `max_nodes` (then `complete` is false and the caller filters by `roots` instead). */
|
|
2770
|
+
nodes: string[] | null;
|
|
2771
|
+
/** @description False when the set beneath the roots was larger than `max_nodes` and was not sent. */
|
|
2772
|
+
complete: boolean;
|
|
2773
|
+
};
|
|
2742
2774
|
IdentityGrantResponseDto: {
|
|
2743
2775
|
/** @example reports.view */
|
|
2744
2776
|
permission: string;
|
|
@@ -2747,6 +2779,16 @@ interface components {
|
|
|
2747
2779
|
* @description The hierarchy node the permission was granted at. Not expanded through descendants.
|
|
2748
2780
|
*/
|
|
2749
2781
|
node_id: string;
|
|
2782
|
+
/**
|
|
2783
|
+
* Format: date-time
|
|
2784
|
+
* @description When the assignment behind this root begins, or null for no start. Present only with `include=windows`.
|
|
2785
|
+
*/
|
|
2786
|
+
effective_from?: string | null;
|
|
2787
|
+
/**
|
|
2788
|
+
* Format: date-time
|
|
2789
|
+
* @description When the assignment behind this root ends, or null for no end. Present only with `include=windows`.
|
|
2790
|
+
*/
|
|
2791
|
+
effective_to?: string | null;
|
|
2750
2792
|
};
|
|
2751
2793
|
IdentityInviteResponseDto: {
|
|
2752
2794
|
id: string;
|
|
@@ -6470,7 +6512,7 @@ interface operations {
|
|
|
6470
6512
|
"application/json": components["schemas"]["ErrorResponseDto"];
|
|
6471
6513
|
};
|
|
6472
6514
|
};
|
|
6473
|
-
/** @description The email or external_id already belongs to an identity in this Account,
|
|
6515
|
+
/** @description The email or external_id already belongs to an identity in this Account, the Account has reached its plan's identity signup ceiling, or the starting role is placed beneath an organization, where a new identity holds no membership yet */
|
|
6474
6516
|
409: {
|
|
6475
6517
|
headers: {
|
|
6476
6518
|
[name: string]: unknown;
|
|
@@ -6481,7 +6523,7 @@ interface operations {
|
|
|
6481
6523
|
* "error": {
|
|
6482
6524
|
* "statusCode": 409,
|
|
6483
6525
|
* "code": null,
|
|
6484
|
-
* "message": "The email or external_id already belongs to an identity in this Account,
|
|
6526
|
+
* "message": "The email or external_id already belongs to an identity in this Account, the Account has reached its plan's identity signup ceiling, or the starting role is placed beneath an organization, where a new identity holds no membership yet",
|
|
6485
6527
|
* "timestamp": "2026-04-20T12:00:00.000Z",
|
|
6486
6528
|
* "path": "/api/v1/identities",
|
|
6487
6529
|
* "method": "POST"
|
|
@@ -8358,7 +8400,10 @@ interface operations {
|
|
|
8358
8400
|
};
|
|
8359
8401
|
ApiIdentitiesController_getIdentityPermissions: {
|
|
8360
8402
|
parameters: {
|
|
8361
|
-
query?:
|
|
8403
|
+
query?: {
|
|
8404
|
+
/** @description Answer at this node: the permissions granted by assignments on its lineage. Omit for the Application-wide answer. */
|
|
8405
|
+
node_id?: string;
|
|
8406
|
+
};
|
|
8362
8407
|
header?: never;
|
|
8363
8408
|
path: {
|
|
8364
8409
|
id: string;
|
|
@@ -8464,6 +8509,119 @@ interface operations {
|
|
|
8464
8509
|
};
|
|
8465
8510
|
};
|
|
8466
8511
|
};
|
|
8512
|
+
ApiIdentitiesController_getIdentityAccessibleNodes: {
|
|
8513
|
+
parameters: {
|
|
8514
|
+
query: {
|
|
8515
|
+
/** @description The permission to answer for, by key. */
|
|
8516
|
+
permission: string;
|
|
8517
|
+
/** @description The largest `nodes` set to return. Past it, `nodes` is null and `complete` false. */
|
|
8518
|
+
max_nodes?: number;
|
|
8519
|
+
};
|
|
8520
|
+
header?: never;
|
|
8521
|
+
path: {
|
|
8522
|
+
id: string;
|
|
8523
|
+
};
|
|
8524
|
+
cookie?: never;
|
|
8525
|
+
};
|
|
8526
|
+
requestBody?: never;
|
|
8527
|
+
responses: {
|
|
8528
|
+
/** @description The minimal grant roots for the permission, and the nodes beneath them up to the cap */
|
|
8529
|
+
200: {
|
|
8530
|
+
headers: {
|
|
8531
|
+
[name: string]: unknown;
|
|
8532
|
+
};
|
|
8533
|
+
content: {
|
|
8534
|
+
"application/json": {
|
|
8535
|
+
data: components["schemas"]["IdentityAccessibleNodesResponseDto"];
|
|
8536
|
+
};
|
|
8537
|
+
};
|
|
8538
|
+
};
|
|
8539
|
+
/** @description The request failed validation: a body field, a query parameter or an id in the path is missing, malformed or out of range; or the request sent both an `X-API-Key` and an `Authorization` header */
|
|
8540
|
+
400: {
|
|
8541
|
+
headers: {
|
|
8542
|
+
[name: string]: unknown;
|
|
8543
|
+
};
|
|
8544
|
+
content: {
|
|
8545
|
+
/**
|
|
8546
|
+
* @example {
|
|
8547
|
+
* "error": {
|
|
8548
|
+
* "statusCode": 400,
|
|
8549
|
+
* "code": null,
|
|
8550
|
+
* "message": "The request failed validation: a body field, a query parameter or an id in the path is missing, malformed or out of range; or the request sent both an `X-API-Key` and an `Authorization` header",
|
|
8551
|
+
* "timestamp": "2026-04-20T12:00:00.000Z",
|
|
8552
|
+
* "path": "/api/v1/identities/{id}/accessible-nodes",
|
|
8553
|
+
* "method": "GET"
|
|
8554
|
+
* }
|
|
8555
|
+
* }
|
|
8556
|
+
*/
|
|
8557
|
+
"application/json": components["schemas"]["ErrorResponseDto"];
|
|
8558
|
+
};
|
|
8559
|
+
};
|
|
8560
|
+
/** @description Invalid or expired token */
|
|
8561
|
+
401: {
|
|
8562
|
+
headers: {
|
|
8563
|
+
[name: string]: unknown;
|
|
8564
|
+
};
|
|
8565
|
+
content: {
|
|
8566
|
+
/**
|
|
8567
|
+
* @example {
|
|
8568
|
+
* "error": {
|
|
8569
|
+
* "statusCode": 401,
|
|
8570
|
+
* "code": null,
|
|
8571
|
+
* "message": "Invalid or expired token",
|
|
8572
|
+
* "timestamp": "2026-04-20T12:00:00.000Z",
|
|
8573
|
+
* "path": "/api/v1/identities",
|
|
8574
|
+
* "method": "GET"
|
|
8575
|
+
* }
|
|
8576
|
+
* }
|
|
8577
|
+
*/
|
|
8578
|
+
"application/json": components["schemas"]["ErrorResponseDto"];
|
|
8579
|
+
};
|
|
8580
|
+
};
|
|
8581
|
+
/** @description This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa) */
|
|
8582
|
+
403: {
|
|
8583
|
+
headers: {
|
|
8584
|
+
[name: string]: unknown;
|
|
8585
|
+
};
|
|
8586
|
+
content: {
|
|
8587
|
+
/**
|
|
8588
|
+
* @example {
|
|
8589
|
+
* "error": {
|
|
8590
|
+
* "statusCode": 403,
|
|
8591
|
+
* "code": null,
|
|
8592
|
+
* "message": "This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa)",
|
|
8593
|
+
* "timestamp": "2026-04-20T12:00:00.000Z",
|
|
8594
|
+
* "path": "/api/v1/identities",
|
|
8595
|
+
* "method": "GET"
|
|
8596
|
+
* }
|
|
8597
|
+
* }
|
|
8598
|
+
*/
|
|
8599
|
+
"application/json": components["schemas"]["ErrorResponseDto"];
|
|
8600
|
+
};
|
|
8601
|
+
};
|
|
8602
|
+
/** @description No identity with that id has a membership in this Environment */
|
|
8603
|
+
404: {
|
|
8604
|
+
headers: {
|
|
8605
|
+
[name: string]: unknown;
|
|
8606
|
+
};
|
|
8607
|
+
content: {
|
|
8608
|
+
/**
|
|
8609
|
+
* @example {
|
|
8610
|
+
* "error": {
|
|
8611
|
+
* "statusCode": 404,
|
|
8612
|
+
* "code": null,
|
|
8613
|
+
* "message": "No identity with that id has a membership in this Environment",
|
|
8614
|
+
* "timestamp": "2026-04-20T12:00:00.000Z",
|
|
8615
|
+
* "path": "/api/v1/identities/{id}/accessible-nodes",
|
|
8616
|
+
* "method": "GET"
|
|
8617
|
+
* }
|
|
8618
|
+
* }
|
|
8619
|
+
*/
|
|
8620
|
+
"application/json": components["schemas"]["ErrorResponseDto"];
|
|
8621
|
+
};
|
|
8622
|
+
};
|
|
8623
|
+
};
|
|
8624
|
+
};
|
|
8467
8625
|
ApiIdentitiesController_getIdentityGrants: {
|
|
8468
8626
|
parameters: {
|
|
8469
8627
|
query?: {
|
|
@@ -8471,6 +8629,8 @@ interface operations {
|
|
|
8471
8629
|
page?: number;
|
|
8472
8630
|
/** @description Items per page (1-100, default 20) */
|
|
8473
8631
|
take?: number;
|
|
8632
|
+
/** @description `windows` adds `effective_from` and `effective_to` to every row and includes assignments outside their window, so the caller applies the window itself. */
|
|
8633
|
+
include?: "windows";
|
|
8474
8634
|
};
|
|
8475
8635
|
header?: never;
|
|
8476
8636
|
path: {
|
|
@@ -8487,6 +8647,8 @@ interface operations {
|
|
|
8487
8647
|
};
|
|
8488
8648
|
content: {
|
|
8489
8649
|
"application/json": {
|
|
8650
|
+
/** @description The version of the authorization model these rows describe. A client that evaluates from them refuses a version it does not know rather than guessing. */
|
|
8651
|
+
model_version: number;
|
|
8490
8652
|
items: components["schemas"]["IdentityGrantResponseDto"][];
|
|
8491
8653
|
pagination: components["schemas"]["PageMetaDto"];
|
|
8492
8654
|
};
|
|
@@ -9491,6 +9653,8 @@ interface operations {
|
|
|
9491
9653
|
};
|
|
9492
9654
|
content: {
|
|
9493
9655
|
"application/json": {
|
|
9656
|
+
/** @description The version of the authorization model these rows describe. A client that evaluates from them refuses a version it does not know rather than guessing. */
|
|
9657
|
+
model_version: number;
|
|
9494
9658
|
items: components["schemas"]["NodeParentEdgeResponseDto"][];
|
|
9495
9659
|
pagination: components["schemas"]["PageMetaDto"];
|
|
9496
9660
|
};
|
|
@@ -17646,7 +17810,7 @@ interface operations {
|
|
|
17646
17810
|
"application/json": components["schemas"]["ErrorResponseDto"];
|
|
17647
17811
|
};
|
|
17648
17812
|
};
|
|
17649
|
-
/** @description The identity already has this role at this node, or the node
|
|
17813
|
+
/** @description The identity already has this role at this node, already holds its one role at this organization, or the node sits beneath an organization the identity has no membership in effect at */
|
|
17650
17814
|
409: {
|
|
17651
17815
|
headers: {
|
|
17652
17816
|
[name: string]: unknown;
|
|
@@ -17657,7 +17821,7 @@ interface operations {
|
|
|
17657
17821
|
* "error": {
|
|
17658
17822
|
* "statusCode": 409,
|
|
17659
17823
|
* "code": null,
|
|
17660
|
-
* "message": "The identity already has this role at this node, or the node
|
|
17824
|
+
* "message": "The identity already has this role at this node, already holds its one role at this organization, or the node sits beneath an organization the identity has no membership in effect at",
|
|
17661
17825
|
* "timestamp": "2026-04-20T12:00:00.000Z",
|
|
17662
17826
|
* "path": "/api/v1/assignments",
|
|
17663
17827
|
* "method": "POST"
|
|
@@ -21535,7 +21699,18 @@ declare class Identities {
|
|
|
21535
21699
|
* question at the node that matters. This is for showing a person what
|
|
21536
21700
|
* someone can do.
|
|
21537
21701
|
*/
|
|
21538
|
-
permissions(id: string): Promise<ResponseBody<"ApiIdentitiesController_getIdentityPermissions">>;
|
|
21702
|
+
permissions(id: string, query?: QueryParams<"ApiIdentitiesController_getIdentityPermissions">): Promise<ResponseBody<"ApiIdentitiesController_getIdentityPermissions">>;
|
|
21703
|
+
/**
|
|
21704
|
+
* Where the identity may act with one permission, answered by Canopy for a
|
|
21705
|
+
* list query: the minimal grant roots, and every node beneath them up to
|
|
21706
|
+
* `max_nodes` (null past it, with `complete: false`), or `env_wide` when a
|
|
21707
|
+
* root is the Environment's own root and no filter is needed.
|
|
21708
|
+
*
|
|
21709
|
+
* `LocalAuthorizer.accessibleNodes` answers the same question from its
|
|
21710
|
+
* cache without a call; this is the fresh answer, and the one for a process
|
|
21711
|
+
* that does not run the authorizer.
|
|
21712
|
+
*/
|
|
21713
|
+
accessibleNodes(id: string, query: QueryParams<"ApiIdentitiesController_getIdentityAccessibleNodes">): Promise<ResponseBody<"ApiIdentitiesController_getIdentityAccessibleNodes">>;
|
|
21539
21714
|
/**
|
|
21540
21715
|
* Every session the identity holds that is neither revoked nor expired,
|
|
21541
21716
|
* newest first: when and where it was signed in, when it expires, and the
|
|
@@ -21835,6 +22010,39 @@ declare class Canopy {
|
|
|
21835
22010
|
* delay is short, so a longer value is refused rather than honoured.
|
|
21836
22011
|
*/
|
|
21837
22012
|
declare const MAX_TTL_MS = 300000;
|
|
22013
|
+
/**
|
|
22014
|
+
* The largest node set `accessibleNodes` builds unless told otherwise. Past
|
|
22015
|
+
* it the answer carries the roots alone, and the caller filters against its
|
|
22016
|
+
* own closure table or materialised path instead of an `IN (...)` list.
|
|
22017
|
+
*/
|
|
22018
|
+
declare const DEFAULT_MAX_NODES = 10000;
|
|
22019
|
+
/**
|
|
22020
|
+
* Why a check was denied, as far as the grant roots and the tree can say.
|
|
22021
|
+
*
|
|
22022
|
+
* - `permission_not_held`: no assignment grants the permission anywhere, in
|
|
22023
|
+
* effect or not.
|
|
22024
|
+
* - `outside_grants`: it is granted, but at no node on the lineage of the
|
|
22025
|
+
* node asked about.
|
|
22026
|
+
* - `expired`: a grant that would answer has ended, and none is in effect.
|
|
22027
|
+
* - `not_yet_effective`: a grant that would answer has not begun, and none
|
|
22028
|
+
* is in effect.
|
|
22029
|
+
* - `node_missing`: a `node` check named no node, which is a denial rather
|
|
22030
|
+
* than an error, because a request whose subject cannot be established
|
|
22031
|
+
* must not pass.
|
|
22032
|
+
*/
|
|
22033
|
+
type LocalDenialReason = "permission_not_held" | "outside_grants" | "expired" | "not_yet_effective" | "node_missing";
|
|
22034
|
+
/** The answer to a check: the verdict, and why when it is no. */
|
|
22035
|
+
interface LocalDecision {
|
|
22036
|
+
allowed: boolean;
|
|
22037
|
+
reason: LocalDenialReason | null;
|
|
22038
|
+
}
|
|
22039
|
+
/** What a decision hook receives, after the verdict is in. */
|
|
22040
|
+
interface LocalDecisionEvent {
|
|
22041
|
+
query: LocalAuthorizationQuery;
|
|
22042
|
+
decision: LocalDecision;
|
|
22043
|
+
/** How long the check took, reads included. */
|
|
22044
|
+
durationMs: number;
|
|
22045
|
+
}
|
|
21838
22046
|
interface LocalAuthorizerOptions {
|
|
21839
22047
|
/**
|
|
21840
22048
|
* How long grant roots and the hierarchy are reused, in milliseconds.
|
|
@@ -21867,6 +22075,19 @@ interface LocalAuthorizerOptions {
|
|
|
21867
22075
|
timeoutMs?: number;
|
|
21868
22076
|
/** Retries for those reads. */
|
|
21869
22077
|
maxRetries?: number;
|
|
22078
|
+
/**
|
|
22079
|
+
* The largest node set {@link LocalAuthorizer.accessibleNodes} builds
|
|
22080
|
+
* unless a call says otherwise. Defaults to {@link DEFAULT_MAX_NODES}.
|
|
22081
|
+
*/
|
|
22082
|
+
maxNodes?: number;
|
|
22083
|
+
/**
|
|
22084
|
+
* Called after every decision, allowed or not, with the question and the
|
|
22085
|
+
* answer. Never awaited and never allowed to fail the check: a hook that
|
|
22086
|
+
* throws is ignored. For audit trails and metrics.
|
|
22087
|
+
*/
|
|
22088
|
+
onDecision?: (event: LocalDecisionEvent) => void;
|
|
22089
|
+
/** Called after every denial, the same way. For alerting on refusals. */
|
|
22090
|
+
onDenied?: (event: LocalDecisionEvent) => void;
|
|
21870
22091
|
/** Injectable clock, for tests. */
|
|
21871
22092
|
now?: () => number;
|
|
21872
22093
|
}
|
|
@@ -21877,6 +22098,26 @@ interface LocalAuthorizationQuery {
|
|
|
21877
22098
|
scope?: "node" | "app_wide";
|
|
21878
22099
|
node_id?: string;
|
|
21879
22100
|
}
|
|
22101
|
+
/** Where an identity may act with one permission, shaped for a list query. */
|
|
22102
|
+
interface AccessibleNodes {
|
|
22103
|
+
/**
|
|
22104
|
+
* The nodes the permission is granted at, in effect now, that no other
|
|
22105
|
+
* such node already covers. Each reaches everything beneath it.
|
|
22106
|
+
*/
|
|
22107
|
+
roots: string[];
|
|
22108
|
+
/**
|
|
22109
|
+
* Every node beneath the roots, the roots included, for an `IN (...)`
|
|
22110
|
+
* filter. Null when `envWide`, or when the set would exceed the cap.
|
|
22111
|
+
*/
|
|
22112
|
+
nodes: Set<string> | null;
|
|
22113
|
+
/** False when the set exceeded the cap and `nodes` was not built. */
|
|
22114
|
+
complete: boolean;
|
|
22115
|
+
/**
|
|
22116
|
+
* True when a root is the Environment's own root: every node qualifies, so
|
|
22117
|
+
* a list needs no filter at all.
|
|
22118
|
+
*/
|
|
22119
|
+
envWide: boolean;
|
|
22120
|
+
}
|
|
21880
22121
|
interface LocalAuthorizerStats {
|
|
21881
22122
|
/** Reads of an identity's grant roots, one per refresh however many pages it took. */
|
|
21882
22123
|
grantFetches: number;
|
|
@@ -21896,8 +22137,11 @@ interface LocalAuthorizerStats {
|
|
|
21896
22137
|
declare class LocalAuthorizer {
|
|
21897
22138
|
private readonly client;
|
|
21898
22139
|
private readonly ttlMs;
|
|
22140
|
+
private readonly maxNodes;
|
|
21899
22141
|
private readonly now;
|
|
21900
22142
|
private readonly readOptions;
|
|
22143
|
+
private readonly onDecision;
|
|
22144
|
+
private readonly onDenied;
|
|
21901
22145
|
private readonly grants;
|
|
21902
22146
|
private tree;
|
|
21903
22147
|
/**
|
|
@@ -21913,7 +22157,7 @@ declare class LocalAuthorizer {
|
|
|
21913
22157
|
private stats;
|
|
21914
22158
|
constructor(client: CanopyClient, options?: LocalAuthorizerOptions);
|
|
21915
22159
|
/**
|
|
21916
|
-
* Whether the identity holds the permission.
|
|
22160
|
+
* Whether the identity holds the permission, and why not when it does not.
|
|
21917
22161
|
*
|
|
21918
22162
|
* Shaped like the API's own evaluate so a caller can swap one for the other.
|
|
21919
22163
|
* A `node` check with no node is a denial rather than an error: a request
|
|
@@ -21921,9 +22165,51 @@ declare class LocalAuthorizer {
|
|
|
21921
22165
|
*/
|
|
21922
22166
|
evaluate(query: LocalAuthorizationQuery, options?: {
|
|
21923
22167
|
signal?: AbortSignal;
|
|
21924
|
-
}): Promise<
|
|
21925
|
-
|
|
22168
|
+
}): Promise<LocalDecision>;
|
|
22169
|
+
/**
|
|
22170
|
+
* The nodes the identity holds the permission at, in effect now, reduced to
|
|
22171
|
+
* the ones no other such node already covers, and whether one of them is
|
|
22172
|
+
* the Environment's root. The roots alone are enough for an application
|
|
22173
|
+
* that keeps a closure table or a materialised path: it filters by "beneath
|
|
22174
|
+
* one of these" without an expanded set.
|
|
22175
|
+
*
|
|
22176
|
+
* Reads the tree, since minimality and `envWide` are questions about it; a
|
|
22177
|
+
* root the readable hierarchy does not contain raises
|
|
22178
|
+
* `authorizer.hierarchy_incomplete`, as a check at such a node does.
|
|
22179
|
+
*/
|
|
22180
|
+
grantRoots(identityId: string, permission: string): Promise<{
|
|
22181
|
+
roots: string[];
|
|
22182
|
+
envWide: boolean;
|
|
21926
22183
|
}>;
|
|
22184
|
+
/**
|
|
22185
|
+
* Where the identity may act with the permission, as the set a list query
|
|
22186
|
+
* filters by: {@link grantRoots} expanded through their descendants, up to
|
|
22187
|
+
* `maxNodes` (the authorizer's default unless given here).
|
|
22188
|
+
*
|
|
22189
|
+
* Past the cap `nodes` is null and `complete` false: the set is too large
|
|
22190
|
+
* to be worth an `IN (...)` list, and the caller filters by `roots`
|
|
22191
|
+
* instead. An `envWide` answer carries no set either, since every node
|
|
22192
|
+
* qualifies and a filter would only slow the query down.
|
|
22193
|
+
*
|
|
22194
|
+
* Computed from the same cached grant roots and tree a check uses, so a
|
|
22195
|
+
* list filtered by it and the check on a write cannot disagree, and it goes
|
|
22196
|
+
* stale on the same `ttlMs`.
|
|
22197
|
+
*/
|
|
22198
|
+
accessibleNodes(identityId: string, permission: string, options?: {
|
|
22199
|
+
maxNodes?: number;
|
|
22200
|
+
}): Promise<AccessibleNodes>;
|
|
22201
|
+
/**
|
|
22202
|
+
* Every permission the identity holds at the node, by grant or by
|
|
22203
|
+
* inheritance: the list a screen renders its controls from, answered from
|
|
22204
|
+
* the same cached grant roots and tree a check uses.
|
|
22205
|
+
*/
|
|
22206
|
+
permissionsAt(identityId: string, nodeId: string): Promise<string[]>;
|
|
22207
|
+
/**
|
|
22208
|
+
* {@link permissionsAt} for several nodes at once, as node → permissions:
|
|
22209
|
+
* the rows of a list each with their own controls, answered from one read
|
|
22210
|
+
* of the grant roots and one walk per node.
|
|
22211
|
+
*/
|
|
22212
|
+
permissionsAtMany(identityId: string, nodeIds: readonly string[]): Promise<Map<string, string[]>>;
|
|
21927
22213
|
private decide;
|
|
21928
22214
|
/** Counters for observability — how much traffic the cache is actually saving. */
|
|
21929
22215
|
snapshot(): LocalAuthorizerStats;
|
|
@@ -21950,19 +22236,29 @@ declare class LocalAuthorizer {
|
|
|
21950
22236
|
* top of it — size the TTL to the revocation latency you can promise.
|
|
21951
22237
|
*/
|
|
21952
22238
|
invalidate(identityId?: string): void;
|
|
21953
|
-
/**
|
|
21954
|
-
private
|
|
22239
|
+
/** Fire the decision hooks, after the verdict and without waiting on them. */
|
|
22240
|
+
private announce;
|
|
22241
|
+
/** The node and its ancestors, from the tree this client can read. */
|
|
22242
|
+
private lineageOf;
|
|
22243
|
+
private hierarchyIncomplete;
|
|
21955
22244
|
private grantRootsFor;
|
|
21956
22245
|
/**
|
|
21957
|
-
* Every page of the identity's grant roots,
|
|
22246
|
+
* Every page of the identity's grant roots, with their windows, folded
|
|
22247
|
+
* into permission → roots.
|
|
22248
|
+
*
|
|
22249
|
+
* Each row is one permission at one node for one assignment, so a
|
|
22250
|
+
* permission granted at three nodes arrives as three rows, possibly on
|
|
22251
|
+
* different pages. Stopping after the first page would under-report what
|
|
22252
|
+
* the identity holds: a false deny, the safe direction, but a wrong answer
|
|
22253
|
+
* all the same.
|
|
21958
22254
|
*
|
|
21959
|
-
*
|
|
21960
|
-
*
|
|
21961
|
-
*
|
|
21962
|
-
*
|
|
22255
|
+
* Rows outside their window are kept: they are what lets a denial say
|
|
22256
|
+
* `expired` or `not_yet_effective`, and the entry expires at the nearest
|
|
22257
|
+
* window boundary when that comes before the TTL, so a grant takes effect
|
|
22258
|
+
* or ends on time rather than up to `ttlMs` late.
|
|
21963
22259
|
*/
|
|
21964
22260
|
private fetchGrantRoots;
|
|
21965
|
-
private
|
|
22261
|
+
private treeEntry;
|
|
21966
22262
|
/**
|
|
21967
22263
|
* Revalidate rather than re-read. The hierarchy is the expensive half and
|
|
21968
22264
|
* the one that changes least, so the common case is a `304` and no transfer
|
|
@@ -22428,4 +22724,4 @@ type WebhookEventData<Name extends WebhookEventName> = WebhookEvent<Name>["data"
|
|
|
22428
22724
|
*/
|
|
22429
22725
|
declare function isWebhookEvent<Name extends WebhookEventName>(delivery: WebhookDelivery, name: Name): delivery is WebhookEvent<Name>;
|
|
22430
22726
|
|
|
22431
|
-
export { type AssignmentItem, Assignments, type CallOptions, Canopy, CanopyAuthorizerError, CanopyClient, type CanopyClientOptions, CanopyConnectionError, CanopyError, type CanopyErrorBody, type CanopyOrgContext, type CanopyTokenClaims, CanopyTokenError, type Collection, type ConcurrencyOptions, type ConditionalResult, type CursorPagination, type FailureStatus, Identities, type IdentityAssignmentItem, type IdentityItem, type IdentitySessionItem, type LocalAuthorizationQuery, LocalAuthorizer, type LocalAuthorizerOptions, type LocalAuthorizerStats, MAX_TTL_MS, type OffsetPagination, type OperationId, type OrganizationDirectoryGroupItem, type OrganizationInviteItem, type OrganizationItem, type OrganizationMemberItem, Organizations, type PageFetcher, type PageParams, type PaginateOptions, type Pagination, Paginator, type PartialSuccess, type PermissionItem, Permissions, type QueryParams, type RequestBody, type RequestOptions, type ResponseBody, type RoleItem, Roles, type Schema, TokenVerifier, type TokenVerifierOptions, type WebhookDelivery, type WebhookEvent, type WebhookEventData, type WebhookEventName, authorizationErrorStatus, type components, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, type operations, orgContext, paginate, type paths, tokenErrorStatus, withConcurrency };
|
|
22727
|
+
export { type AccessibleNodes, type AssignmentItem, Assignments, type CallOptions, Canopy, CanopyAuthorizerError, CanopyClient, type CanopyClientOptions, CanopyConnectionError, CanopyError, type CanopyErrorBody, type CanopyOrgContext, type CanopyTokenClaims, CanopyTokenError, type Collection, type ConcurrencyOptions, type ConditionalResult, type CursorPagination, DEFAULT_MAX_NODES, type FailureStatus, Identities, type IdentityAssignmentItem, type IdentityItem, type IdentitySessionItem, type LocalAuthorizationQuery, LocalAuthorizer, type LocalAuthorizerOptions, type LocalAuthorizerStats, type LocalDecision, type LocalDecisionEvent, type LocalDenialReason, MAX_TTL_MS, type OffsetPagination, type OperationId, type OrganizationDirectoryGroupItem, type OrganizationInviteItem, type OrganizationItem, type OrganizationMemberItem, Organizations, type PageFetcher, type PageParams, type PaginateOptions, type Pagination, Paginator, type PartialSuccess, type PermissionItem, Permissions, type QueryParams, type RequestBody, type RequestOptions, type ResponseBody, type RoleItem, Roles, type Schema, TokenVerifier, type TokenVerifierOptions, type WebhookDelivery, type WebhookEvent, type WebhookEventData, type WebhookEventName, authorizationErrorStatus, type components, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, type operations, orgContext, paginate, type paths, tokenErrorStatus, withConcurrency };
|