@canopy-io/node 0.7.2 → 0.8.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 +10 -0
- package/README.md +50 -10
- package/dist/index.cjs +88 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -3
- package/dist/index.d.ts +58 -3
- package/dist/index.js +87 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -21964,8 +21964,9 @@ declare class CanopyConnectionError extends Error {
|
|
|
21964
21964
|
* whole point of verifying a signature rather than calling an endpoint.
|
|
21965
21965
|
*
|
|
21966
21966
|
* Branch on `code`; it follows the same dot-notation contract as the API's own
|
|
21967
|
-
* codes. Every one
|
|
21968
|
-
*
|
|
21967
|
+
* codes. Every one but `token.jwks_unavailable` means the same thing to an
|
|
21968
|
+
* HTTP caller, 401; that one means the keys could not be read, so the token may
|
|
21969
|
+
* be good and the answer is 503. {@link tokenErrorStatus} maps them.
|
|
21969
21970
|
*/
|
|
21970
21971
|
declare class CanopyTokenError extends Error {
|
|
21971
21972
|
readonly name = "CanopyTokenError";
|
|
@@ -21982,6 +21983,8 @@ declare class CanopyTokenError extends Error {
|
|
|
21982
21983
|
* - `token.audience_mismatch` — `aud` does not include the configured audience
|
|
21983
21984
|
* - `token.audience_unverified` — token has an `aud` but none was configured
|
|
21984
21985
|
* - `token.preauth_not_allowed` — a pre-auth token, which grants no access
|
|
21986
|
+
* - `token.type_not_allowed` — a principal type the verifier does not accept
|
|
21987
|
+
* (see `allowedTypes`)
|
|
21985
21988
|
*/
|
|
21986
21989
|
readonly code: string;
|
|
21987
21990
|
constructor(code: string, message: string, options?: {
|
|
@@ -22013,6 +22016,44 @@ declare function isCanopyError(error: unknown): error is CanopyError;
|
|
|
22013
22016
|
declare function isCanopyConnectionError(error: unknown): error is CanopyConnectionError;
|
|
22014
22017
|
declare function isCanopyTokenError(error: unknown): error is CanopyTokenError;
|
|
22015
22018
|
|
|
22019
|
+
/**
|
|
22020
|
+
* The HTTP answer a failure should become, for code that verifies tokens or
|
|
22021
|
+
* authorizes requests outside the NestJS guards (Express, Fastify, a worker).
|
|
22022
|
+
* The guards use the same mapping, so every integration answers alike.
|
|
22023
|
+
*/
|
|
22024
|
+
interface FailureStatus {
|
|
22025
|
+
status: number;
|
|
22026
|
+
/** A message safe to send to the caller; the detail stays on the error. */
|
|
22027
|
+
message: string;
|
|
22028
|
+
}
|
|
22029
|
+
/**
|
|
22030
|
+
* The answer for a failed {@link TokenVerifier.verify}: `401` when the token is
|
|
22031
|
+
* at fault, `503` when the signing keys could not be read and the token may be
|
|
22032
|
+
* perfectly good.
|
|
22033
|
+
*
|
|
22034
|
+
* The difference matters. A client reads `401` as "your token is stale",
|
|
22035
|
+
* discards a valid one and asks for another, which aims a refresh storm at an
|
|
22036
|
+
* issuer that is already down, and an outage that only emits 4xx is invisible
|
|
22037
|
+
* to anything watching for 5xx.
|
|
22038
|
+
*
|
|
22039
|
+
* Anything that is not a token error is returned as `500`: it is a bug, not a
|
|
22040
|
+
* verdict on the token.
|
|
22041
|
+
*/
|
|
22042
|
+
declare function tokenErrorStatus(error: unknown): FailureStatus;
|
|
22043
|
+
/**
|
|
22044
|
+
* The answer for a failed authorization check ({@link LocalAuthorizer.evaluate}
|
|
22045
|
+
* or a {@link Canopy} call that decides access). Every branch denies; they say
|
|
22046
|
+
* different things so an incident is diagnosable:
|
|
22047
|
+
*
|
|
22048
|
+
* - `500` when the authorizer cannot see far enough to decide (a credential
|
|
22049
|
+
* that cannot read the hierarchy), when Canopy rejected our own credential,
|
|
22050
|
+
* when the endpoint is not served (an SDK newer than the API), or on a 4xx
|
|
22051
|
+
* nobody anticipated. No retry fixes these.
|
|
22052
|
+
* - `503` when Canopy did not answer, or answered 429 or 5xx.
|
|
22053
|
+
* - `403` when Canopy answered that the identity is not in this Environment.
|
|
22054
|
+
*/
|
|
22055
|
+
declare function authorizationErrorStatus(error: unknown): FailureStatus;
|
|
22056
|
+
|
|
22016
22057
|
interface TokenVerifierOptions {
|
|
22017
22058
|
/**
|
|
22018
22059
|
* The Environment whose tokens are accepted. Every Environment is its own
|
|
@@ -22070,6 +22111,16 @@ interface TokenVerifierOptions {
|
|
|
22070
22111
|
* request path, so an unbounded read there is an unbounded request.
|
|
22071
22112
|
*/
|
|
22072
22113
|
jwksTimeoutMs?: number;
|
|
22114
|
+
/**
|
|
22115
|
+
* The principal types accepted, matched against the `type` claim. Defaults to
|
|
22116
|
+
* `["identity"]`: the people who sign in to your application.
|
|
22117
|
+
*
|
|
22118
|
+
* Canopy also signs tokens for its own administrators (`user`), its staff
|
|
22119
|
+
* (`platform`) and, for some flows, API keys (`api_key`), all with the same
|
|
22120
|
+
* keys and issuer. A backend that serves your end users should accept none
|
|
22121
|
+
* of them, so they are refused unless you name them here.
|
|
22122
|
+
*/
|
|
22123
|
+
allowedTypes?: ReadonlyArray<CanopyTokenClaims["type"]>;
|
|
22073
22124
|
/** Injectable for tests and for runtimes with a non-global fetch. */
|
|
22074
22125
|
fetch?: typeof globalThis.fetch;
|
|
22075
22126
|
}
|
|
@@ -22160,11 +22211,14 @@ declare class TokenVerifier {
|
|
|
22160
22211
|
private readonly jwksTimeoutMs;
|
|
22161
22212
|
private readonly clockToleranceSec;
|
|
22162
22213
|
private readonly allowPreAuthTokens;
|
|
22214
|
+
private readonly allowedTypes;
|
|
22163
22215
|
private readonly fetchImpl;
|
|
22164
22216
|
/** Imported keys by `kid`, so a repeat verification skips the import cost. */
|
|
22165
22217
|
private keys;
|
|
22166
22218
|
private keysFetchedAt;
|
|
22167
22219
|
private lastFetchAttemptAt;
|
|
22220
|
+
/** Whether the latest read of the key set failed, for an unknown `kid`. */
|
|
22221
|
+
private lastFetchFailed;
|
|
22168
22222
|
/** In-flight fetch, so a burst of requests triggers one call, not N. */
|
|
22169
22223
|
private inFlight;
|
|
22170
22224
|
constructor(options?: TokenVerifierOptions);
|
|
@@ -22208,6 +22262,7 @@ declare class TokenVerifier {
|
|
|
22208
22262
|
private shouldRefresh;
|
|
22209
22263
|
private refreshKeys;
|
|
22210
22264
|
private fetchKeys;
|
|
22265
|
+
private readKeys;
|
|
22211
22266
|
}
|
|
22212
22267
|
|
|
22213
22268
|
/**
|
|
@@ -22254,4 +22309,4 @@ type WebhookEventData<Name extends WebhookEventName> = WebhookEvent<Name>["data"
|
|
|
22254
22309
|
*/
|
|
22255
22310
|
declare function isWebhookEvent<Name extends WebhookEventName>(delivery: WebhookDelivery, name: Name): delivery is WebhookEvent<Name>;
|
|
22256
22311
|
|
|
22257
|
-
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, Identities, type IdentityAssignmentItem, type IdentityItem, type IdentitySessionItem, type LocalAuthorizationQuery, LocalAuthorizer, type LocalAuthorizerOptions, type LocalAuthorizerStats, MAX_TTL_MS, type OffsetPagination, type OperationId, 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, type components, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, type operations, orgContext, paginate, type paths, withConcurrency };
|
|
22312
|
+
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 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -21964,8 +21964,9 @@ declare class CanopyConnectionError extends Error {
|
|
|
21964
21964
|
* whole point of verifying a signature rather than calling an endpoint.
|
|
21965
21965
|
*
|
|
21966
21966
|
* Branch on `code`; it follows the same dot-notation contract as the API's own
|
|
21967
|
-
* codes. Every one
|
|
21968
|
-
*
|
|
21967
|
+
* codes. Every one but `token.jwks_unavailable` means the same thing to an
|
|
21968
|
+
* HTTP caller, 401; that one means the keys could not be read, so the token may
|
|
21969
|
+
* be good and the answer is 503. {@link tokenErrorStatus} maps them.
|
|
21969
21970
|
*/
|
|
21970
21971
|
declare class CanopyTokenError extends Error {
|
|
21971
21972
|
readonly name = "CanopyTokenError";
|
|
@@ -21982,6 +21983,8 @@ declare class CanopyTokenError extends Error {
|
|
|
21982
21983
|
* - `token.audience_mismatch` — `aud` does not include the configured audience
|
|
21983
21984
|
* - `token.audience_unverified` — token has an `aud` but none was configured
|
|
21984
21985
|
* - `token.preauth_not_allowed` — a pre-auth token, which grants no access
|
|
21986
|
+
* - `token.type_not_allowed` — a principal type the verifier does not accept
|
|
21987
|
+
* (see `allowedTypes`)
|
|
21985
21988
|
*/
|
|
21986
21989
|
readonly code: string;
|
|
21987
21990
|
constructor(code: string, message: string, options?: {
|
|
@@ -22013,6 +22016,44 @@ declare function isCanopyError(error: unknown): error is CanopyError;
|
|
|
22013
22016
|
declare function isCanopyConnectionError(error: unknown): error is CanopyConnectionError;
|
|
22014
22017
|
declare function isCanopyTokenError(error: unknown): error is CanopyTokenError;
|
|
22015
22018
|
|
|
22019
|
+
/**
|
|
22020
|
+
* The HTTP answer a failure should become, for code that verifies tokens or
|
|
22021
|
+
* authorizes requests outside the NestJS guards (Express, Fastify, a worker).
|
|
22022
|
+
* The guards use the same mapping, so every integration answers alike.
|
|
22023
|
+
*/
|
|
22024
|
+
interface FailureStatus {
|
|
22025
|
+
status: number;
|
|
22026
|
+
/** A message safe to send to the caller; the detail stays on the error. */
|
|
22027
|
+
message: string;
|
|
22028
|
+
}
|
|
22029
|
+
/**
|
|
22030
|
+
* The answer for a failed {@link TokenVerifier.verify}: `401` when the token is
|
|
22031
|
+
* at fault, `503` when the signing keys could not be read and the token may be
|
|
22032
|
+
* perfectly good.
|
|
22033
|
+
*
|
|
22034
|
+
* The difference matters. A client reads `401` as "your token is stale",
|
|
22035
|
+
* discards a valid one and asks for another, which aims a refresh storm at an
|
|
22036
|
+
* issuer that is already down, and an outage that only emits 4xx is invisible
|
|
22037
|
+
* to anything watching for 5xx.
|
|
22038
|
+
*
|
|
22039
|
+
* Anything that is not a token error is returned as `500`: it is a bug, not a
|
|
22040
|
+
* verdict on the token.
|
|
22041
|
+
*/
|
|
22042
|
+
declare function tokenErrorStatus(error: unknown): FailureStatus;
|
|
22043
|
+
/**
|
|
22044
|
+
* The answer for a failed authorization check ({@link LocalAuthorizer.evaluate}
|
|
22045
|
+
* or a {@link Canopy} call that decides access). Every branch denies; they say
|
|
22046
|
+
* different things so an incident is diagnosable:
|
|
22047
|
+
*
|
|
22048
|
+
* - `500` when the authorizer cannot see far enough to decide (a credential
|
|
22049
|
+
* that cannot read the hierarchy), when Canopy rejected our own credential,
|
|
22050
|
+
* when the endpoint is not served (an SDK newer than the API), or on a 4xx
|
|
22051
|
+
* nobody anticipated. No retry fixes these.
|
|
22052
|
+
* - `503` when Canopy did not answer, or answered 429 or 5xx.
|
|
22053
|
+
* - `403` when Canopy answered that the identity is not in this Environment.
|
|
22054
|
+
*/
|
|
22055
|
+
declare function authorizationErrorStatus(error: unknown): FailureStatus;
|
|
22056
|
+
|
|
22016
22057
|
interface TokenVerifierOptions {
|
|
22017
22058
|
/**
|
|
22018
22059
|
* The Environment whose tokens are accepted. Every Environment is its own
|
|
@@ -22070,6 +22111,16 @@ interface TokenVerifierOptions {
|
|
|
22070
22111
|
* request path, so an unbounded read there is an unbounded request.
|
|
22071
22112
|
*/
|
|
22072
22113
|
jwksTimeoutMs?: number;
|
|
22114
|
+
/**
|
|
22115
|
+
* The principal types accepted, matched against the `type` claim. Defaults to
|
|
22116
|
+
* `["identity"]`: the people who sign in to your application.
|
|
22117
|
+
*
|
|
22118
|
+
* Canopy also signs tokens for its own administrators (`user`), its staff
|
|
22119
|
+
* (`platform`) and, for some flows, API keys (`api_key`), all with the same
|
|
22120
|
+
* keys and issuer. A backend that serves your end users should accept none
|
|
22121
|
+
* of them, so they are refused unless you name them here.
|
|
22122
|
+
*/
|
|
22123
|
+
allowedTypes?: ReadonlyArray<CanopyTokenClaims["type"]>;
|
|
22073
22124
|
/** Injectable for tests and for runtimes with a non-global fetch. */
|
|
22074
22125
|
fetch?: typeof globalThis.fetch;
|
|
22075
22126
|
}
|
|
@@ -22160,11 +22211,14 @@ declare class TokenVerifier {
|
|
|
22160
22211
|
private readonly jwksTimeoutMs;
|
|
22161
22212
|
private readonly clockToleranceSec;
|
|
22162
22213
|
private readonly allowPreAuthTokens;
|
|
22214
|
+
private readonly allowedTypes;
|
|
22163
22215
|
private readonly fetchImpl;
|
|
22164
22216
|
/** Imported keys by `kid`, so a repeat verification skips the import cost. */
|
|
22165
22217
|
private keys;
|
|
22166
22218
|
private keysFetchedAt;
|
|
22167
22219
|
private lastFetchAttemptAt;
|
|
22220
|
+
/** Whether the latest read of the key set failed, for an unknown `kid`. */
|
|
22221
|
+
private lastFetchFailed;
|
|
22168
22222
|
/** In-flight fetch, so a burst of requests triggers one call, not N. */
|
|
22169
22223
|
private inFlight;
|
|
22170
22224
|
constructor(options?: TokenVerifierOptions);
|
|
@@ -22208,6 +22262,7 @@ declare class TokenVerifier {
|
|
|
22208
22262
|
private shouldRefresh;
|
|
22209
22263
|
private refreshKeys;
|
|
22210
22264
|
private fetchKeys;
|
|
22265
|
+
private readKeys;
|
|
22211
22266
|
}
|
|
22212
22267
|
|
|
22213
22268
|
/**
|
|
@@ -22254,4 +22309,4 @@ type WebhookEventData<Name extends WebhookEventName> = WebhookEvent<Name>["data"
|
|
|
22254
22309
|
*/
|
|
22255
22310
|
declare function isWebhookEvent<Name extends WebhookEventName>(delivery: WebhookDelivery, name: Name): delivery is WebhookEvent<Name>;
|
|
22256
22311
|
|
|
22257
|
-
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, Identities, type IdentityAssignmentItem, type IdentityItem, type IdentitySessionItem, type LocalAuthorizationQuery, LocalAuthorizer, type LocalAuthorizerOptions, type LocalAuthorizerStats, MAX_TTL_MS, type OffsetPagination, type OperationId, 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, type components, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, type operations, orgContext, paginate, type paths, withConcurrency };
|
|
22312
|
+
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 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 };
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,8 @@ var CanopyTokenError = class extends Error {
|
|
|
54
54
|
* - `token.audience_mismatch` — `aud` does not include the configured audience
|
|
55
55
|
* - `token.audience_unverified` — token has an `aud` but none was configured
|
|
56
56
|
* - `token.preauth_not_allowed` — a pre-auth token, which grants no access
|
|
57
|
+
* - `token.type_not_allowed` — a principal type the verifier does not accept
|
|
58
|
+
* (see `allowedTypes`)
|
|
57
59
|
*/
|
|
58
60
|
code;
|
|
59
61
|
constructor(code, message, options) {
|
|
@@ -1344,6 +1346,56 @@ var LocalAuthorizer = class {
|
|
|
1344
1346
|
}
|
|
1345
1347
|
};
|
|
1346
1348
|
|
|
1349
|
+
// src/status.ts
|
|
1350
|
+
function tokenErrorStatus(error) {
|
|
1351
|
+
if (!isCanopyTokenError(error)) {
|
|
1352
|
+
return { status: 500, message: "Authentication could not be checked." };
|
|
1353
|
+
}
|
|
1354
|
+
if (error.code === "token.jwks_unavailable") {
|
|
1355
|
+
return {
|
|
1356
|
+
status: 503,
|
|
1357
|
+
message: "Authentication is temporarily unavailable."
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
return { status: 401, message: "Invalid or expired access token." };
|
|
1361
|
+
}
|
|
1362
|
+
function authorizationErrorStatus(error) {
|
|
1363
|
+
if (isCanopyAuthorizerError(error)) {
|
|
1364
|
+
return {
|
|
1365
|
+
status: 500,
|
|
1366
|
+
message: `Authorization is misconfigured: ${error.message}`
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
if (!isCanopyError(error)) {
|
|
1370
|
+
return {
|
|
1371
|
+
status: 503,
|
|
1372
|
+
message: "Authorization is temporarily unavailable."
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
if (error.isAuthFailure) {
|
|
1376
|
+
return {
|
|
1377
|
+
status: 500,
|
|
1378
|
+
message: "Authorization is misconfigured. Check the Canopy API key and its scopes."
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
if (error.statusCode === 404 && error.code === null) {
|
|
1382
|
+
return {
|
|
1383
|
+
status: 500,
|
|
1384
|
+
message: `Authorization is misconfigured: ${error.request.method} ${error.request.path} is not served by this Canopy. The SDK requires an API that exposes the identity grants endpoint.`
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (error.statusCode === 404) {
|
|
1388
|
+
return { status: 403, message: "Could not evaluate the permission." };
|
|
1389
|
+
}
|
|
1390
|
+
if (error.isRateLimited || error.statusCode >= 500) {
|
|
1391
|
+
return {
|
|
1392
|
+
status: 503,
|
|
1393
|
+
message: "Authorization is temporarily unavailable."
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
return { status: 500, message: "Authorization could not be evaluated." };
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1347
1399
|
// src/verify.ts
|
|
1348
1400
|
var HOSTED_ORIGIN = "https://auth.canopy-io.com";
|
|
1349
1401
|
var DEFAULT_JWKS_CACHE_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
@@ -1404,11 +1456,14 @@ var TokenVerifier = class {
|
|
|
1404
1456
|
jwksTimeoutMs;
|
|
1405
1457
|
clockToleranceSec;
|
|
1406
1458
|
allowPreAuthTokens;
|
|
1459
|
+
allowedTypes;
|
|
1407
1460
|
fetchImpl;
|
|
1408
1461
|
/** Imported keys by `kid`, so a repeat verification skips the import cost. */
|
|
1409
1462
|
keys = /* @__PURE__ */ new Map();
|
|
1410
1463
|
keysFetchedAt = 0;
|
|
1411
1464
|
lastFetchAttemptAt = 0;
|
|
1465
|
+
/** Whether the latest read of the key set failed, for an unknown `kid`. */
|
|
1466
|
+
lastFetchFailed = false;
|
|
1412
1467
|
/** In-flight fetch, so a burst of requests triggers one call, not N. */
|
|
1413
1468
|
inFlight = null;
|
|
1414
1469
|
constructor(options = {}) {
|
|
@@ -1420,6 +1475,7 @@ var TokenVerifier = class {
|
|
|
1420
1475
|
this.jwksTimeoutMs = options.jwksTimeoutMs ?? DEFAULT_JWKS_TIMEOUT_MS;
|
|
1421
1476
|
this.clockToleranceSec = options.clockToleranceSec ?? DEFAULT_CLOCK_TOLERANCE_SEC;
|
|
1422
1477
|
this.allowPreAuthTokens = options.allowPreAuthTokens ?? false;
|
|
1478
|
+
this.allowedTypes = new Set(options.allowedTypes ?? ["identity"]);
|
|
1423
1479
|
const boundFetch = options.fetch ?? globalThis.fetch;
|
|
1424
1480
|
if (typeof boundFetch !== "function") {
|
|
1425
1481
|
throw new TypeError(
|
|
@@ -1497,6 +1553,14 @@ var TokenVerifier = class {
|
|
|
1497
1553
|
`Token has an unrecognised \`type\` claim: ${JSON.stringify(claims.type)}.`
|
|
1498
1554
|
);
|
|
1499
1555
|
}
|
|
1556
|
+
if (!this.allowedTypes.has(claims.type)) {
|
|
1557
|
+
throw new CanopyTokenError(
|
|
1558
|
+
"token.type_not_allowed",
|
|
1559
|
+
`Token is for a "${claims.type}" principal; this verifier accepts ${[
|
|
1560
|
+
...this.allowedTypes
|
|
1561
|
+
].map((type) => `"${type}"`).join(", ")}. Set \`allowedTypes\` to accept another.`
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1500
1564
|
if (claims.iss !== this.issuer) {
|
|
1501
1565
|
throw new CanopyTokenError(
|
|
1502
1566
|
"token.issuer_mismatch",
|
|
@@ -1569,7 +1633,13 @@ var TokenVerifier = class {
|
|
|
1569
1633
|
const stale = Date.now() - this.keysFetchedAt > this.jwksCacheMaxAgeMs;
|
|
1570
1634
|
if (this.keys.size === 0 || stale) {
|
|
1571
1635
|
if (this.shouldRefresh()) {
|
|
1572
|
-
|
|
1636
|
+
try {
|
|
1637
|
+
await this.refreshKeys();
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
if (this.keys.size === 0) {
|
|
1640
|
+
throw error;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1573
1643
|
} else if (this.keys.size === 0) {
|
|
1574
1644
|
throw new CanopyTokenError(
|
|
1575
1645
|
"token.jwks_unavailable",
|
|
@@ -1588,6 +1658,12 @@ var TokenVerifier = class {
|
|
|
1588
1658
|
if (rotated) {
|
|
1589
1659
|
return rotated;
|
|
1590
1660
|
}
|
|
1661
|
+
if (this.lastFetchFailed) {
|
|
1662
|
+
throw new CanopyTokenError(
|
|
1663
|
+
"token.jwks_unavailable",
|
|
1664
|
+
`Signing keys at ${this.jwksUri} could not be read to look up kid "${kid}".`
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1591
1667
|
throw new CanopyTokenError(
|
|
1592
1668
|
"token.key_not_found",
|
|
1593
1669
|
`No signing key matches kid "${kid}".`
|
|
@@ -1618,6 +1694,15 @@ var TokenVerifier = class {
|
|
|
1618
1694
|
}
|
|
1619
1695
|
async fetchKeys() {
|
|
1620
1696
|
this.lastFetchAttemptAt = Date.now();
|
|
1697
|
+
try {
|
|
1698
|
+
await this.readKeys();
|
|
1699
|
+
this.lastFetchFailed = false;
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
this.lastFetchFailed = true;
|
|
1702
|
+
throw error;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
async readKeys() {
|
|
1621
1706
|
let response;
|
|
1622
1707
|
const controller = new AbortController();
|
|
1623
1708
|
const timer = this.jwksTimeoutMs > 0 ? setTimeout(() => controller.abort(), this.jwksTimeoutMs) : void 0;
|
|
@@ -1690,6 +1775,6 @@ function isWebhookEvent(delivery, name) {
|
|
|
1690
1775
|
return delivery.event === name;
|
|
1691
1776
|
}
|
|
1692
1777
|
|
|
1693
|
-
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 };
|
|
1778
|
+
export { Assignments, Canopy, CanopyAuthorizerError, CanopyClient, CanopyConnectionError, CanopyError, CanopyTokenError, Identities, LocalAuthorizer, MAX_TTL_MS, Organizations, Paginator, Permissions, Roles, TokenVerifier, authorizationErrorStatus, isCanopyAuthorizerError, isCanopyConnectionError, isCanopyError, isCanopyTokenError, isCursorPagination, isWebhookEvent, orgContext, paginate, tokenErrorStatus, withConcurrency };
|
|
1694
1779
|
//# sourceMappingURL=index.js.map
|
|
1695
1780
|
//# sourceMappingURL=index.js.map
|