@typeship-ax/mcp 0.6.0 → 0.9.1
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/AGENTS.md +31 -0
- package/README.md +67 -10
- package/api.json +6735 -3243
- package/api.md +8537 -248
- package/dist/api-identity.d.ts +40 -0
- package/dist/api-identity.d.ts.map +1 -0
- package/dist/api-identity.js +128 -0
- package/dist/auth-profiles.d.ts +30 -0
- package/dist/auth-profiles.d.ts.map +1 -0
- package/dist/auth-profiles.js +138 -0
- package/dist/core/http.d.ts +21 -92
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +143 -221
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +6 -34
- package/dist/credential-storage.d.ts +24 -0
- package/dist/credential-storage.d.ts.map +1 -0
- package/dist/credential-storage.js +207 -0
- package/dist/dates.d.ts +0 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +0 -1
- package/dist/docs.d.ts +36 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +258 -0
- package/dist/errors.d.ts +42 -34
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +30 -20
- package/dist/index.d.ts +27 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -14
- package/dist/mcp-authorization.d.ts +52 -0
- package/dist/mcp-authorization.d.ts.map +1 -0
- package/dist/mcp-authorization.js +232 -0
- package/dist/mcp-protocol.d.ts +69 -25
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +386 -138
- package/dist/mcp.d.ts +21 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +199 -85
- package/dist/named-credentials.d.ts +21 -0
- package/dist/named-credentials.d.ts.map +1 -0
- package/dist/named-credentials.js +86 -0
- package/dist/oauth-request.d.ts +21 -0
- package/dist/oauth-request.d.ts.map +1 -0
- package/dist/oauth-request.js +119 -0
- package/dist/oauth-session.d.ts +106 -0
- package/dist/oauth-session.d.ts.map +1 -0
- package/dist/oauth-session.js +244 -0
- package/dist/ops.d.ts +18 -0
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +31 -17
- package/dist/resources/account.d.ts +4 -4
- package/dist/resources/account.d.ts.map +1 -1
- package/dist/resources/account.js +1 -0
- package/dist/resources/api-keys.d.ts +13 -8
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +5 -1
- package/dist/resources/definition-revisions.d.ts +58 -0
- package/dist/resources/definition-revisions.d.ts.map +1 -0
- package/dist/resources/definition-revisions.js +114 -0
- package/dist/resources/definitions.d.ts +35 -0
- package/dist/resources/definitions.d.ts.map +1 -0
- package/dist/resources/definitions.js +60 -0
- package/dist/resources/generate.d.ts +18 -7
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +13 -5
- package/dist/resources/generations.d.ts +6 -6
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +3 -1
- package/dist/resources/projects.d.ts +111 -35
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +125 -15
- package/dist/resources/targets.d.ts +97 -0
- package/dist/resources/targets.d.ts.map +1 -0
- package/dist/resources/targets.js +197 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +135 -62
- package/dist/types.d.ts +2072 -267
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +20 -3
- package/dist/worker.js +4 -4
- package/package.json +11 -1
- package/server.json +42 -0
- package/src/api-identity.ts +98 -0
- package/src/auth-profiles.ts +114 -0
- package/src/core/http.ts +156 -305
- package/src/core/pagination.ts +6 -30
- package/src/credential-storage.ts +183 -0
- package/src/dates.ts +0 -1
- package/src/docs.ts +239 -0
- package/src/errors.ts +52 -41
- package/src/index.ts +49 -14
- package/src/mcp-authorization.ts +211 -0
- package/src/mcp-protocol.ts +432 -133
- package/src/mcp.ts +204 -90
- package/src/named-credentials.ts +74 -0
- package/src/oauth-request.ts +90 -0
- package/src/oauth-session.ts +258 -0
- package/src/ops.ts +56 -17
- package/src/resources/account.ts +6 -3
- package/src/resources/api-keys.ts +27 -7
- package/src/resources/definition-revisions.ts +207 -0
- package/src/resources/definitions.ts +122 -0
- package/src/resources/generate.ts +29 -6
- package/src/resources/generations.ts +9 -4
- package/src/resources/projects.ts +274 -41
- package/src/resources/targets.ts +378 -0
- package/src/schemas.ts +135 -62
- package/src/types.ts +2273 -322
- package/src/worker.ts +4 -4
- package/dist/resources/spec-revisions.d.ts +0 -47
- package/dist/resources/spec-revisions.d.ts.map +0 -1
- package/dist/resources/spec-revisions.js +0 -90
- package/src/resources/spec-revisions.ts +0 -150
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7D,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE3I;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACpD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACtD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACnD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBAClD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACzD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBAC7D,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACrD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;gBACxD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACzD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACxD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBAClD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;gBACpD,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY;CAGzD"}
|
package/dist/errors.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// typeship — typed error classes.
|
|
2
2
|
// Generated by typeship — https://typeship.dev — do not edit by hand.
|
|
3
3
|
import { ApiError } from "./core/http.js";
|
|
4
|
-
export { ApiError,
|
|
4
|
+
export { ApiError, ResponseParseError, TransportError, UnexpectedApiError, ValidationError, unwrap } from "./core/http.js";
|
|
5
5
|
/**
|
|
6
|
-
* The request body,
|
|
6
|
+
* The request body, Definition source, target selection, or package name is invalid.
|
|
7
7
|
* Raised for HTTP 400 responses.
|
|
8
8
|
*/
|
|
9
9
|
export class BadRequestError extends ApiError {
|
|
10
10
|
constructor(body, response) {
|
|
11
|
-
super("The request body,
|
|
11
|
+
super("The request body, Definition source, target selection, or package name is invalid.", 400, body, response);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -29,6 +29,15 @@ export class ForbiddenError extends ApiError {
|
|
|
29
29
|
super("The credentials are valid but cannot act on the requested organization.", 403, body, response);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The key identifies changed intent.
|
|
34
|
+
* Raised for HTTP 409 responses.
|
|
35
|
+
*/
|
|
36
|
+
export class ConflictError extends ApiError {
|
|
37
|
+
constructor(body, response) {
|
|
38
|
+
super("The key identifies changed intent.", 409, body, response);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
32
41
|
/**
|
|
33
42
|
* Spec exceeds the 10MB limit.
|
|
34
43
|
* Raised for HTTP 413 responses.
|
|
@@ -39,21 +48,22 @@ export class PayloadTooLargeError extends ApiError {
|
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
42
|
-
* The
|
|
51
|
+
* The Definition could not be resolved or understood.
|
|
43
52
|
* Raised for HTTP 422 responses.
|
|
44
53
|
*/
|
|
45
54
|
export class UnprocessableEntityError extends ApiError {
|
|
46
55
|
constructor(body, response) {
|
|
47
|
-
super("The
|
|
56
|
+
super("The Definition could not be resolved or understood.", 422, body, response);
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
/**
|
|
51
|
-
* Too many requests. Wait for Retry-After before
|
|
60
|
+
* Too many requests, or an identical write is still in progress. Wait for Retry-After before
|
|
61
|
+
* retrying.
|
|
52
62
|
* Raised for HTTP 429 responses.
|
|
53
63
|
*/
|
|
54
64
|
export class RateLimitedError extends ApiError {
|
|
55
65
|
constructor(body, response) {
|
|
56
|
-
super("Too many requests. Wait for Retry-After before retrying.", 429, body, response);
|
|
66
|
+
super("Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.", 429, body, response);
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
/**
|
|
@@ -66,30 +76,21 @@ export class ApiResponseError extends ApiError {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
/**
|
|
69
|
-
* The plan does not include another project or the requested
|
|
79
|
+
* The plan does not include another project or the requested target configuration.
|
|
70
80
|
* Raised for HTTP 402 responses.
|
|
71
81
|
*/
|
|
72
82
|
export class PaymentRequiredError extends ApiError {
|
|
73
83
|
constructor(body, response) {
|
|
74
|
-
super("The plan does not include another project or the requested
|
|
84
|
+
super("The plan does not include another project or the requested target configuration.", 402, body, response);
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
/**
|
|
78
|
-
*
|
|
79
|
-
* Raised for HTTP 409 responses.
|
|
80
|
-
*/
|
|
81
|
-
export class ConflictError extends ApiError {
|
|
82
|
-
constructor(body, response) {
|
|
83
|
-
super("The Idempotency-Key was already used with different request parameters.", 409, body, response);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* The original idempotent request is temporarily unavailable.
|
|
88
|
+
* Project setup failed unexpectedly; the key reservation is released.
|
|
88
89
|
* Raised for HTTP 500 responses.
|
|
89
90
|
*/
|
|
90
91
|
export class InternalServerError extends ApiError {
|
|
91
92
|
constructor(body, response) {
|
|
92
|
-
super("
|
|
93
|
+
super("Project setup failed unexpectedly; the key reservation is released.", 500, body, response);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
/**
|
|
@@ -101,3 +102,12 @@ export class NotFoundError extends ApiError {
|
|
|
101
102
|
super("No such resource in this account.", 404, body, response);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* The Project was saved, but an obsolete release pull request could not be retired.
|
|
107
|
+
* Raised for HTTP 502 responses.
|
|
108
|
+
*/
|
|
109
|
+
export class BadGatewayError extends ApiError {
|
|
110
|
+
constructor(body, response) {
|
|
111
|
+
super("The Project was saved, but an obsolete release pull request could not be retired.", 502, body, response);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { type DebugEvent, type RequestContext } from "./core/http.js";
|
|
1
|
+
import { type AuthValue, type DebugEvent, type RequestContext } from "./core/http.js";
|
|
2
2
|
import { GenerateResource } from "./resources/generate.js";
|
|
3
3
|
import { ProjectsResource } from "./resources/projects.js";
|
|
4
|
+
import { DefinitionsResource } from "./resources/definitions.js";
|
|
5
|
+
import { TargetsResource } from "./resources/targets.js";
|
|
4
6
|
import { GenerationsResource } from "./resources/generations.js";
|
|
5
|
-
import {
|
|
7
|
+
import { DefinitionRevisionsResource } from "./resources/definition-revisions.js";
|
|
6
8
|
import { AccountResource } from "./resources/account.js";
|
|
7
9
|
import { ApiKeysResource } from "./resources/api-keys.js";
|
|
8
10
|
/** This package's version, also sent as the `User-Agent`. */
|
|
9
|
-
export declare const VERSION = "0.
|
|
11
|
+
export declare const VERSION = "0.9.1";
|
|
10
12
|
export interface ClientOptions {
|
|
11
13
|
/** Override the server URL. Default: `https://typeship.dev/api/v1` */
|
|
12
14
|
baseUrl?: string;
|
|
@@ -15,6 +17,13 @@ export interface ClientOptions {
|
|
|
15
17
|
* before every attempt.
|
|
16
18
|
*/
|
|
17
19
|
bearerToken?: string | (() => string | Promise<string>);
|
|
20
|
+
/**
|
|
21
|
+
* Credentials keyed by the Definition's security scheme names. Only one complete alternative is
|
|
22
|
+
* sent for each operation. Named values take precedence over convenience options.
|
|
23
|
+
*/
|
|
24
|
+
credentials?: {
|
|
25
|
+
"apiKey"?: AuthValue;
|
|
26
|
+
};
|
|
18
27
|
/** Per-attempt timeout in milliseconds. Default: 30000. */
|
|
19
28
|
timeoutMs?: number;
|
|
20
29
|
/** Retries after the first attempt (retryable failures only). Default: 2. */
|
|
@@ -60,21 +69,25 @@ export interface ClientOptions {
|
|
|
60
69
|
};
|
|
61
70
|
}
|
|
62
71
|
/**
|
|
63
|
-
* typeship — v0.
|
|
72
|
+
* typeship — v0.9.1
|
|
64
73
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
74
|
+
* Resolve an OpenAPI or GraphQL Definition, diagnose it, and keep every
|
|
75
|
+
* selected SDK, CLI, and MCP Target current.
|
|
67
76
|
*
|
|
68
|
-
* Every operation but one requires
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
77
|
+
* Every operation but one requires a bearer credential: an organization
|
|
78
|
+
* API key from the console, or an OAuth access token carrying the operation's
|
|
79
|
+
* read, generate, or write capability and the organization selected during
|
|
80
|
+
* consent. OAuth grants cannot switch organizations after consent. A browser
|
|
81
|
+
* session is not a credential for this API. The exception is POST /generate,
|
|
82
|
+
* which works anonymously with the free plan's limits.
|
|
72
83
|
*/
|
|
73
84
|
export declare class TypeshipClient {
|
|
74
85
|
readonly generate: GenerateResource;
|
|
75
86
|
readonly projects: ProjectsResource;
|
|
87
|
+
readonly definitions: DefinitionsResource;
|
|
88
|
+
readonly targets: TargetsResource;
|
|
76
89
|
readonly generations: GenerationsResource;
|
|
77
|
-
readonly
|
|
90
|
+
readonly definitionRevisions: DefinitionRevisionsResource;
|
|
78
91
|
readonly account: AccountResource;
|
|
79
92
|
readonly apiKeys: ApiKeysResource;
|
|
80
93
|
constructor(options?: ClientOptions);
|
|
@@ -85,8 +98,10 @@ export { formatDebugEvent, type ApiResult, type AuthValue, type DebugEvent, type
|
|
|
85
98
|
export { Page, PagePromise } from "./core/pagination.js";
|
|
86
99
|
export * from "./resources/generate.js";
|
|
87
100
|
export * from "./resources/projects.js";
|
|
101
|
+
export * from "./resources/definitions.js";
|
|
102
|
+
export * from "./resources/targets.js";
|
|
88
103
|
export * from "./resources/generations.js";
|
|
89
|
-
export * from "./resources/
|
|
104
|
+
export * from "./resources/definition-revisions.js";
|
|
90
105
|
export * from "./resources/account.js";
|
|
91
106
|
export * from "./resources/api-keys.js";
|
|
92
107
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -4,29 +4,35 @@ import { HttpCore, formatDebugEvent, bearerAuth, } from "./core/http.js";
|
|
|
4
4
|
import { DEFS, SCHEMAS } from "./schemas.js";
|
|
5
5
|
import { GenerateResource } from "./resources/generate.js";
|
|
6
6
|
import { ProjectsResource } from "./resources/projects.js";
|
|
7
|
+
import { DefinitionsResource } from "./resources/definitions.js";
|
|
8
|
+
import { TargetsResource } from "./resources/targets.js";
|
|
7
9
|
import { GenerationsResource } from "./resources/generations.js";
|
|
8
|
-
import {
|
|
10
|
+
import { DefinitionRevisionsResource } from "./resources/definition-revisions.js";
|
|
9
11
|
import { AccountResource } from "./resources/account.js";
|
|
10
12
|
import { ApiKeysResource } from "./resources/api-keys.js";
|
|
11
13
|
/** This package's version, also sent as the `User-Agent`. */
|
|
12
|
-
export const VERSION = "0.
|
|
13
|
-
const USER_AGENT = "@typeship-ax/mcp/0.
|
|
14
|
+
export const VERSION = "0.9.1";
|
|
15
|
+
const USER_AGENT = "@typeship-ax/mcp/0.9.1 (typeship)";
|
|
14
16
|
/**
|
|
15
|
-
* typeship — v0.
|
|
17
|
+
* typeship — v0.9.1
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* Resolve an OpenAPI or GraphQL Definition, diagnose it, and keep every
|
|
20
|
+
* selected SDK, CLI, and MCP Target current.
|
|
19
21
|
*
|
|
20
|
-
* Every operation but one requires
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* Every operation but one requires a bearer credential: an organization
|
|
23
|
+
* API key from the console, or an OAuth access token carrying the operation's
|
|
24
|
+
* read, generate, or write capability and the organization selected during
|
|
25
|
+
* consent. OAuth grants cannot switch organizations after consent. A browser
|
|
26
|
+
* session is not a credential for this API. The exception is POST /generate,
|
|
27
|
+
* which works anonymously with the free plan's limits.
|
|
24
28
|
*/
|
|
25
29
|
export class TypeshipClient {
|
|
26
30
|
generate;
|
|
27
31
|
projects;
|
|
32
|
+
definitions;
|
|
33
|
+
targets;
|
|
28
34
|
generations;
|
|
29
|
-
|
|
35
|
+
definitionRevisions;
|
|
30
36
|
account;
|
|
31
37
|
apiKeys;
|
|
32
38
|
constructor(options = {}) {
|
|
@@ -41,8 +47,23 @@ export class TypeshipClient {
|
|
|
41
47
|
? (event) => console.error(formatDebugEvent("typeship", event))
|
|
42
48
|
: undefined;
|
|
43
49
|
const query = {};
|
|
50
|
+
const authHeaders = {};
|
|
51
|
+
const authQuery = {};
|
|
52
|
+
let bearerCredential;
|
|
44
53
|
if (options.bearerToken !== undefined)
|
|
45
|
-
|
|
54
|
+
bearerCredential = bearerAuth(options.bearerToken);
|
|
55
|
+
const allowedCredentials = new Set(["apiKey"]);
|
|
56
|
+
for (const name of Object.keys(options.credentials ?? {})) {
|
|
57
|
+
if (!allowedCredentials.has(name))
|
|
58
|
+
throw new Error("Unknown security scheme: " + name);
|
|
59
|
+
}
|
|
60
|
+
const credentials = Object.create(null);
|
|
61
|
+
{
|
|
62
|
+
const named = options.credentials && Object.hasOwn(options.credentials, "apiKey") ? options.credentials["apiKey"] : undefined;
|
|
63
|
+
const value = named !== undefined ? bearerAuth(named) : bearerCredential;
|
|
64
|
+
if (value !== undefined)
|
|
65
|
+
credentials["apiKey"] = { headers: { ["Authorization"]: value } };
|
|
66
|
+
}
|
|
46
67
|
const v = options.validate;
|
|
47
68
|
const validate = v
|
|
48
69
|
? {
|
|
@@ -55,6 +76,7 @@ export class TypeshipClient {
|
|
|
55
76
|
baseUrl: options.baseUrl ?? "https://typeship.dev/api/v1",
|
|
56
77
|
headers,
|
|
57
78
|
query,
|
|
79
|
+
credentials,
|
|
58
80
|
fetch: options.fetch ?? fetch,
|
|
59
81
|
timeoutMs: options.timeoutMs ?? 30_000,
|
|
60
82
|
maxRetries: options.maxRetries ?? 2,
|
|
@@ -68,8 +90,10 @@ export class TypeshipClient {
|
|
|
68
90
|
});
|
|
69
91
|
this.generate = new GenerateResource(core);
|
|
70
92
|
this.projects = new ProjectsResource(core);
|
|
93
|
+
this.definitions = new DefinitionsResource(core);
|
|
94
|
+
this.targets = new TargetsResource(core);
|
|
71
95
|
this.generations = new GenerationsResource(core);
|
|
72
|
-
this.
|
|
96
|
+
this.definitionRevisions = new DefinitionRevisionsResource(core);
|
|
73
97
|
this.account = new AccountResource(core);
|
|
74
98
|
this.apiKeys = new ApiKeysResource(core);
|
|
75
99
|
}
|
|
@@ -80,7 +104,9 @@ export { formatDebugEvent, } from "./core/http.js";
|
|
|
80
104
|
export { Page, PagePromise } from "./core/pagination.js";
|
|
81
105
|
export * from "./resources/generate.js";
|
|
82
106
|
export * from "./resources/projects.js";
|
|
107
|
+
export * from "./resources/definitions.js";
|
|
108
|
+
export * from "./resources/targets.js";
|
|
83
109
|
export * from "./resources/generations.js";
|
|
84
|
-
export * from "./resources/
|
|
110
|
+
export * from "./resources/definition-revisions.js";
|
|
85
111
|
export * from "./resources/account.js";
|
|
86
112
|
export * from "./resources/api-keys.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface McpAuthorizationConfiguration {
|
|
2
|
+
/** Exact authorization-server issuer. Never inferred from an incoming token. */
|
|
3
|
+
issuer: string;
|
|
4
|
+
/** Canonical public MCP endpoint, including its path. */
|
|
5
|
+
resource: string;
|
|
6
|
+
/** Optional public signing-key endpoint; otherwise use issuer discovery. */
|
|
7
|
+
jwksUrl?: string;
|
|
8
|
+
/** Minimum connection scopes; upstream API permissions are independent. */
|
|
9
|
+
scopes?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export interface McpPrincipal {
|
|
12
|
+
readonly issuer: string;
|
|
13
|
+
readonly subject: string;
|
|
14
|
+
readonly resource: string;
|
|
15
|
+
readonly scopes: readonly string[];
|
|
16
|
+
/** Provider claims verified by signature or authenticated introspection. */
|
|
17
|
+
readonly claims: Readonly<Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
/** Server-only credentials. Supply at runtime, never in generation config. */
|
|
20
|
+
export interface McpTokenIntrospectionConfiguration {
|
|
21
|
+
/** Fixed provider endpoint authorized to receive this issuer's tokens. */
|
|
22
|
+
url: string;
|
|
23
|
+
clientId: string;
|
|
24
|
+
clientSecret: string;
|
|
25
|
+
authMethod?: "client_secret_basic" | "client_secret_post";
|
|
26
|
+
/** Entire response deadline, including the body; defaults to 10 seconds. */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class McpAuthorizationError extends Error {
|
|
30
|
+
readonly status: 401 | 403 | 503;
|
|
31
|
+
readonly code: "invalid_token" | "insufficient_scope" | "authorization_unavailable";
|
|
32
|
+
constructor(status: 401 | 403 | 503, code: "invalid_token" | "insufficient_scope" | "authorization_unavailable");
|
|
33
|
+
}
|
|
34
|
+
/** Validate configuration independently of any request or token claims. */
|
|
35
|
+
export declare function mcpAuthorizationConfiguration(value: McpAuthorizationConfiguration): McpAuthorizationConfiguration;
|
|
36
|
+
/** Resource-server validation using platform crypto or explicit introspection. The caller supplies
|
|
37
|
+
* only a token; issuer, resource, discovery and key destinations are fixed by
|
|
38
|
+
* the owner. No Authorization header is sent during discovery/key fetching.
|
|
39
|
+
* Introspection replaces JWT verification for every request: no fallback and
|
|
40
|
+
* no token-result cache, so provider revocation is checked on the next request. */
|
|
41
|
+
export declare function createMcpAuthorizer(input: McpAuthorizationConfiguration, introspection?: McpTokenIntrospectionConfiguration): {
|
|
42
|
+
configuration: McpAuthorizationConfiguration;
|
|
43
|
+
metadata(): {
|
|
44
|
+
resource: string;
|
|
45
|
+
authorization_servers: string[];
|
|
46
|
+
bearer_methods_supported: string[];
|
|
47
|
+
scopes_supported: string[];
|
|
48
|
+
};
|
|
49
|
+
challenge(error?: "invalid_token" | "insufficient_scope"): string;
|
|
50
|
+
authorize(header: string | null): Promise<McpPrincipal>;
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=mcp-authorization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-authorization.d.ts","sourceRoot":"","sources":["../src/mcp-authorization.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,6BAA6B;IAC5C,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED,8EAA8E;AAC9E,MAAM,WAAW,kCAAkC;IACjD,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;IAC1D,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,oBAAoB,GAAG,2BAA2B;gBAA5G,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAW,IAAI,EAAE,eAAe,GAAG,oBAAoB,GAAG,2BAA2B;CAIlI;AAeD,2EAA2E;AAC3E,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,6BAA6B,GAAG,6BAA6B,CAMjH;AA+DD;;;;mFAImF;AACnF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,6BAA6B,EAAE,aAAa,CAAC,EAAE,kCAAkC;;;;;;;;sBA2DtG,eAAe,GAAG,oBAAoB;sBAKhC,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC;EAkBhE"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { oauthJsonRequest } from "./oauth-request.js";
|
|
2
|
+
export class McpAuthorizationError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code) {
|
|
6
|
+
super(code === "invalid_token" ? "A valid MCP access token is required." : code === "insufficient_scope" ? "The access token lacks the required MCP permissions." : "MCP authorization is unavailable. Check the server's authorization configuration.");
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "McpAuthorizationError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const invalid = () => new McpAuthorizationError(401, "invalid_token");
|
|
13
|
+
const unavailable = () => new McpAuthorizationError(503, "authorization_unavailable");
|
|
14
|
+
const scopePattern = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
|
|
15
|
+
function endpoint(value, query = false) {
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(value);
|
|
18
|
+
if (value !== value.trim() || /[\u0000-\u0020\u007F"\\]/.test(value) || url.username || url.password || url.hash || (!query && url.search) ||
|
|
19
|
+
!(url.protocol === "https:" || url.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(url.hostname)))
|
|
20
|
+
throw unavailable();
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw unavailable();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Validate configuration independently of any request or token claims. */
|
|
28
|
+
export function mcpAuthorizationConfiguration(value) {
|
|
29
|
+
if (!value || typeof value.issuer !== "string" || typeof value.resource !== "string")
|
|
30
|
+
throw unavailable();
|
|
31
|
+
const issuer = endpoint(value.issuer), resource = endpoint(value.resource);
|
|
32
|
+
const scopes = value.scopes ?? [];
|
|
33
|
+
if (!Array.isArray(scopes) || scopes.length > 20 || scopes.some(s => typeof s !== "string" || s.length > 500 || !scopePattern.test(s)))
|
|
34
|
+
throw unavailable();
|
|
35
|
+
return Object.freeze({ issuer, resource, ...(value.jwksUrl ? { jwksUrl: endpoint(value.jwksUrl, true) } : {}), scopes: Object.freeze([...new Set(scopes)]) });
|
|
36
|
+
}
|
|
37
|
+
function bytes(value) {
|
|
38
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1)
|
|
39
|
+
throw invalid();
|
|
40
|
+
try {
|
|
41
|
+
const decoded = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), ch => ch.charCodeAt(0));
|
|
42
|
+
if (btoa(String.fromCharCode(...decoded)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") !== value)
|
|
43
|
+
throw invalid();
|
|
44
|
+
return decoded;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw invalid();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function object(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
|
|
51
|
+
function decode(value) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes(value)));
|
|
54
|
+
if (!object(parsed))
|
|
55
|
+
throw invalid();
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw invalid();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function principalFor(claims, configuration) {
|
|
63
|
+
const now = Date.now() / 1000;
|
|
64
|
+
const audiences = typeof claims.aud === "string" ? [claims.aud] : claims.aud;
|
|
65
|
+
if (claims.cnf !== undefined || claims.iss !== configuration.issuer || typeof claims.sub !== "string" || !claims.sub || claims.sub.length > 1000 ||
|
|
66
|
+
!Array.isArray(audiences) || !audiences.every(a => typeof a === "string") || !audiences.includes(configuration.resource) ||
|
|
67
|
+
typeof claims.exp !== "number" || !Number.isFinite(claims.exp) || claims.exp <= now ||
|
|
68
|
+
(claims.nbf !== undefined && (typeof claims.nbf !== "number" || !Number.isFinite(claims.nbf) || claims.nbf > now)) ||
|
|
69
|
+
(claims.iat !== undefined && (typeof claims.iat !== "number" || !Number.isFinite(claims.iat) || claims.iat > now + 60)) ||
|
|
70
|
+
(claims.scope !== undefined && (typeof claims.scope !== "string" || claims.scope.length > 10_000)))
|
|
71
|
+
throw invalid();
|
|
72
|
+
const scopes = typeof claims.scope === "string" ? claims.scope.split(" ").filter(Boolean) : [];
|
|
73
|
+
if (scopes.some(s => !scopePattern.test(s)))
|
|
74
|
+
throw invalid();
|
|
75
|
+
if (configuration.scopes.some(s => !scopes.includes(s)))
|
|
76
|
+
throw new McpAuthorizationError(403, "insufficient_scope");
|
|
77
|
+
return Object.freeze({ issuer: configuration.issuer, subject: claims.sub, resource: configuration.resource, scopes: Object.freeze([...new Set(scopes)]), claims: Object.freeze(claims) });
|
|
78
|
+
}
|
|
79
|
+
function introspector(input, configuration) {
|
|
80
|
+
const url = endpoint(input.url);
|
|
81
|
+
const method = input.authMethod ?? "client_secret_basic", timeoutMs = input.timeoutMs ?? 10_000;
|
|
82
|
+
if (!["client_secret_basic", "client_secret_post"].includes(method) || !Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 30_000 ||
|
|
83
|
+
[input.clientId, input.clientSecret].some(value => typeof value !== "string" || !value || value.length > 16_000 || /[\u0000-\u001F\u007F]/.test(value)))
|
|
84
|
+
throw unavailable();
|
|
85
|
+
// Snapshot secrets in a private closure. Public metadata/config never exposes them.
|
|
86
|
+
const { clientId, clientSecret } = input;
|
|
87
|
+
const form = (value) => new URLSearchParams({ v: value }).toString().slice(2);
|
|
88
|
+
const authorization = method === "client_secret_basic" ? "Basic " + btoa(form(clientId) + ":" + form(clientSecret)) : undefined;
|
|
89
|
+
return async (token) => {
|
|
90
|
+
const body = new URLSearchParams({ token, token_type_hint: "access_token" });
|
|
91
|
+
if (method === "client_secret_post") {
|
|
92
|
+
body.set("client_id", clientId);
|
|
93
|
+
body.set("client_secret", clientSecret);
|
|
94
|
+
}
|
|
95
|
+
let data;
|
|
96
|
+
try {
|
|
97
|
+
const response = await oauthJsonRequest(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", ...(authorization ? { Authorization: authorization } : {}) }, body }, timeoutMs);
|
|
98
|
+
if (response.status !== 200)
|
|
99
|
+
throw unavailable();
|
|
100
|
+
data = response.data;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw unavailable();
|
|
104
|
+
}
|
|
105
|
+
if (!data || typeof data.active !== "boolean")
|
|
106
|
+
throw unavailable();
|
|
107
|
+
if (!data.active || (data.token_type !== undefined && (typeof data.token_type !== "string" || data.token_type.toLowerCase() !== "bearer")))
|
|
108
|
+
throw invalid();
|
|
109
|
+
// RFC 7662 permits omitted issuer: the owner-pinned authenticated endpoint
|
|
110
|
+
// establishes it. Audience, subject and expiry are mandatory for this handler.
|
|
111
|
+
// Never substitute client_id for the MCP resource audience.
|
|
112
|
+
const claims = { ...data, iss: data.iss === undefined ? configuration.issuer : data.iss };
|
|
113
|
+
for (const key of ["token", "access_token", "refresh_token", "id_token", "client_secret"])
|
|
114
|
+
delete claims[key];
|
|
115
|
+
return principalFor(claims, configuration);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Resource-server validation using platform crypto or explicit introspection. The caller supplies
|
|
119
|
+
* only a token; issuer, resource, discovery and key destinations are fixed by
|
|
120
|
+
* the owner. No Authorization header is sent during discovery/key fetching.
|
|
121
|
+
* Introspection replaces JWT verification for every request: no fallback and
|
|
122
|
+
* no token-result cache, so provider revocation is checked on the next request. */
|
|
123
|
+
export function createMcpAuthorizer(input, introspection) {
|
|
124
|
+
const configuration = mcpAuthorizationConfiguration(input);
|
|
125
|
+
const introspect = introspection === undefined ? undefined : introspector(introspection, configuration);
|
|
126
|
+
const cacheMs = 300_000, retryMs = 30_000;
|
|
127
|
+
let keys = [], expiresAt = 0, lastAttempt = -Infinity;
|
|
128
|
+
let pending;
|
|
129
|
+
async function refresh() {
|
|
130
|
+
if (pending)
|
|
131
|
+
return pending;
|
|
132
|
+
if (Date.now() - lastAttempt < retryMs)
|
|
133
|
+
return;
|
|
134
|
+
lastAttempt = Date.now();
|
|
135
|
+
pending = (async () => {
|
|
136
|
+
try {
|
|
137
|
+
let jwksUrl = configuration.jwksUrl;
|
|
138
|
+
if (!jwksUrl) {
|
|
139
|
+
const issuer = new URL(configuration.issuer), path = issuer.pathname.replace(/\/$/, "");
|
|
140
|
+
const candidates = [...new Set([
|
|
141
|
+
issuer.origin + "/.well-known/oauth-authorization-server" + path,
|
|
142
|
+
issuer.origin + "/.well-known/openid-configuration" + path,
|
|
143
|
+
issuer.origin + path + "/.well-known/openid-configuration",
|
|
144
|
+
])];
|
|
145
|
+
for (const url of candidates) {
|
|
146
|
+
const response = await oauthJsonRequest(url, {}, 10_000);
|
|
147
|
+
if (response.status === 404)
|
|
148
|
+
continue;
|
|
149
|
+
if (response.status !== 200 || response.data?.issuer !== configuration.issuer || typeof response.data.jwks_uri !== "string")
|
|
150
|
+
throw unavailable();
|
|
151
|
+
jwksUrl = endpoint(response.data.jwks_uri, true);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (!jwksUrl)
|
|
155
|
+
throw unavailable();
|
|
156
|
+
}
|
|
157
|
+
const response = await oauthJsonRequest(jwksUrl, {}, 10_000);
|
|
158
|
+
if (response.status !== 200 || !Array.isArray(response.data?.keys) || response.data.keys.length === 0 || response.data.keys.length > 100)
|
|
159
|
+
throw unavailable();
|
|
160
|
+
keys = response.data.keys.filter(object);
|
|
161
|
+
expiresAt = Date.now() + cacheMs;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw unavailable();
|
|
165
|
+
}
|
|
166
|
+
})().finally(() => { pending = undefined; });
|
|
167
|
+
return pending;
|
|
168
|
+
}
|
|
169
|
+
function eligible(header) {
|
|
170
|
+
return keys.filter(key => (!header.kid || key.kid === header.kid) && (!key.alg || key.alg === header.alg) &&
|
|
171
|
+
(!key.use || key.use === "sig") && (!key.key_ops || Array.isArray(key.key_ops) && key.key_ops.includes("verify")) && !key.d && !key.k &&
|
|
172
|
+
(header.alg === "RS256" && key.kty === "RSA" || header.alg === "ES256" && key.kty === "EC" && key.crv === "P-256" || header.alg === "EdDSA" && key.kty === "OKP" && key.crv === "Ed25519"));
|
|
173
|
+
}
|
|
174
|
+
async function verifySignature(header, data, signature) {
|
|
175
|
+
const matches = eligible(header);
|
|
176
|
+
if (matches.length !== 1)
|
|
177
|
+
return false;
|
|
178
|
+
const key = matches[0];
|
|
179
|
+
try {
|
|
180
|
+
const algorithm = header.alg === "RS256" ? { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } : header.alg === "ES256" ? { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" } : { name: "Ed25519" };
|
|
181
|
+
if (header.alg === "RS256") {
|
|
182
|
+
if (!key.n || bytes(key.n).byteLength < 256)
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const imported = await crypto.subtle.importKey("jwk", key, algorithm, false, ["verify"]);
|
|
186
|
+
if (header.alg === "RS256" && (imported.algorithm.modulusLength ?? 0) < 2048)
|
|
187
|
+
return false;
|
|
188
|
+
return await crypto.subtle.verify(algorithm, imported, signature, data);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
configuration,
|
|
196
|
+
metadata() { return { resource: configuration.resource, authorization_servers: [configuration.issuer], bearer_methods_supported: ["header"], scopes_supported: [...configuration.scopes] }; },
|
|
197
|
+
challenge(error) {
|
|
198
|
+
const resource = new URL(configuration.resource);
|
|
199
|
+
const metadata = resource.origin + "/.well-known/oauth-protected-resource" + resource.pathname.replace(/\/$/, "");
|
|
200
|
+
return 'Bearer resource_metadata="' + metadata + '"' + (configuration.scopes.length ? ', scope="' + configuration.scopes.join(" ") + '"' : "") + (error ? ', error="' + error + '"' : "");
|
|
201
|
+
},
|
|
202
|
+
async authorize(header) {
|
|
203
|
+
if (!header || header.length > 16_400 || !/^Bearer [A-Za-z0-9._~+/-]+=*$/i.test(header))
|
|
204
|
+
throw invalid();
|
|
205
|
+
const token = header.slice(7), parts = token.split(".");
|
|
206
|
+
if (introspect)
|
|
207
|
+
return introspect(token);
|
|
208
|
+
if (parts.length !== 3)
|
|
209
|
+
throw invalid();
|
|
210
|
+
const protectedHeader = decode(parts[0]), claims = decode(parts[1]);
|
|
211
|
+
if (!["RS256", "ES256", "EdDSA"].includes(String(protectedHeader.alg)) || protectedHeader.crit !== undefined || protectedHeader.jku !== undefined || protectedHeader.x5u !== undefined ||
|
|
212
|
+
(protectedHeader.typ !== undefined && !["at+jwt", "JWT"].includes(String(protectedHeader.typ))) ||
|
|
213
|
+
(protectedHeader.kid !== undefined && (typeof protectedHeader.kid !== "string" || !protectedHeader.kid || protectedHeader.kid.length > 200)))
|
|
214
|
+
throw invalid();
|
|
215
|
+
const signed = new TextEncoder().encode(parts[0] + "." + parts[1]), signature = bytes(parts[2]);
|
|
216
|
+
if (expiresAt <= Date.now()) {
|
|
217
|
+
await refresh();
|
|
218
|
+
if (expiresAt <= Date.now())
|
|
219
|
+
throw unavailable();
|
|
220
|
+
}
|
|
221
|
+
let verified = await verifySignature(protectedHeader, signed, signature);
|
|
222
|
+
if (!verified && Date.now() - lastAttempt >= retryMs) {
|
|
223
|
+
await refresh();
|
|
224
|
+
verified = await verifySignature(protectedHeader, signed, signature);
|
|
225
|
+
}
|
|
226
|
+
if (!verified)
|
|
227
|
+
throw invalid();
|
|
228
|
+
// No bearer token or unverified claims cross into credential resolution.
|
|
229
|
+
return principalFor(claims, configuration);
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|