@typeship-ax/cli 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 +26 -9
- package/api.json +6861 -3083
- package/api.md +508 -277
- 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/cli-agent.d.ts +9 -1
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +26 -9
- package/dist/cli.js +600 -582
- package/dist/console-login-check.d.ts +21 -0
- package/dist/console-login-check.d.ts.map +1 -0
- package/dist/console-login-check.js +107 -0
- package/dist/console-login-contract.d.ts +45 -0
- package/dist/console-login-contract.d.ts.map +1 -0
- package/dist/console-login-contract.js +40 -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/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-login.d.ts +39 -0
- package/dist/oauth-login.d.ts.map +1 -0
- package/dist/oauth-login.js +171 -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/polling-login.d.ts +57 -0
- package/dist/polling-login.d.ts.map +1 -0
- package/dist/polling-login.js +204 -0
- 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/package.json +2 -1
- package/src/api-identity.ts +98 -0
- package/src/auth-profiles.ts +114 -0
- package/src/cli-agent.ts +28 -11
- package/src/cli.ts +528 -560
- package/src/console-login-check.ts +88 -0
- package/src/console-login-contract.ts +65 -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/named-credentials.ts +74 -0
- package/src/oauth-login.ts +184 -0
- package/src/oauth-request.ts +90 -0
- package/src/oauth-session.ts +258 -0
- package/src/ops.ts +56 -17
- package/src/polling-login.ts +165 -0
- 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/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/cli/0.
|
|
14
|
+
export const VERSION = "0.9.1";
|
|
15
|
+
const USER_AGENT = "@typeship-ax/cli/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,21 @@
|
|
|
1
|
+
/** Runtime credentials keyed by the API's exact security-scheme names. */
|
|
2
|
+
export type NamedCredential = string | {
|
|
3
|
+
username: string;
|
|
4
|
+
password: string;
|
|
5
|
+
};
|
|
6
|
+
export type NamedCredentials = Record<string, NamedCredential>;
|
|
7
|
+
export type CredentialSchemes = Record<string, {
|
|
8
|
+
kind: string;
|
|
9
|
+
options: string[];
|
|
10
|
+
}>;
|
|
11
|
+
export declare function parseNamedCredentials(value: unknown, schemes: CredentialSchemes): NamedCredentials;
|
|
12
|
+
/** Low-to-high source order. Within a source, exact names beat convenience options. */
|
|
13
|
+
export declare function resolveNamedCredentials(schemes: CredentialSchemes, layers: {
|
|
14
|
+
named?: NamedCredentials;
|
|
15
|
+
options?: Record<string, unknown>;
|
|
16
|
+
}[]): NamedCredentials;
|
|
17
|
+
/** Make convenience and named inputs comparable without expanding combinations. */
|
|
18
|
+
export declare function namedCredentialAvailability(schemes: CredentialSchemes, options: Set<string>, named: NamedCredentials): Set<string>;
|
|
19
|
+
/** Bound file/stdin reads before allocating a full credential document. */
|
|
20
|
+
export declare function readNamedCredentialsFile(input: string, schemes: CredentialSchemes): NamedCredentials;
|
|
21
|
+
//# sourceMappingURL=named-credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"named-credentials.d.ts","sourceRoot":"","sources":["../src/named-credentials.ts"],"names":[],"mappings":"AAEA,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAEpF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAqBlG;AAED,uFAAuF;AACvF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,GAAG,gBAAgB,CAa/J;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,CAKlI;AAED,2EAA2E;AAC3E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAkBpG"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { openSync, readSync, closeSync } from "node:fs";
|
|
2
|
+
export function parseNamedCredentials(value, schemes) {
|
|
3
|
+
if (typeof value === "string") {
|
|
4
|
+
if (new TextEncoder().encode(value).byteLength > 1_048_576)
|
|
5
|
+
throw new Error("Named credentials exceed the 1 MiB limit.");
|
|
6
|
+
try {
|
|
7
|
+
value = JSON.parse(value);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new Error("Named credentials must be a JSON object keyed by security scheme. Credential contents are not included in errors.");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14
|
+
throw new Error("Named credentials must be a JSON object keyed by security scheme.");
|
|
15
|
+
const result = Object.create(null);
|
|
16
|
+
for (const [name, credential] of Object.entries(value)) {
|
|
17
|
+
if (!Object.hasOwn(schemes, name))
|
|
18
|
+
throw new Error("Named credentials contain an unknown or unsupported security scheme. Check the names listed by login --help.");
|
|
19
|
+
if (schemes[name].kind === "basic") {
|
|
20
|
+
if (!credential || typeof credential !== "object" || Array.isArray(credential) || Object.keys(credential).some((key) => key !== "username" && key !== "password") ||
|
|
21
|
+
typeof credential.username !== "string" || typeof credential.password !== "string" || !credential.username || !credential.password || credential.username.includes(":")) {
|
|
22
|
+
throw new Error("A named Basic credential requires a nonempty username and password; the username cannot contain a colon.");
|
|
23
|
+
}
|
|
24
|
+
result[name] = { username: credential.username, password: credential.password };
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
if (typeof credential !== "string" || !credential || /[\x00-\x1f\x7f]/.test(credential) || (schemes[name].kind === "bearer" && credential.includes(" ")))
|
|
28
|
+
throw new Error("A named token or API key must be a nonempty string without control characters; bearer tokens cannot contain spaces.");
|
|
29
|
+
result[name] = credential;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** Low-to-high source order. Within a source, exact names beat convenience options. */
|
|
35
|
+
export function resolveNamedCredentials(schemes, layers) {
|
|
36
|
+
const resolved = Object.create(null);
|
|
37
|
+
for (const layer of layers) {
|
|
38
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
39
|
+
for (const option of scheme.options) {
|
|
40
|
+
if (option === "clientCredentials")
|
|
41
|
+
continue;
|
|
42
|
+
const value = layer.options?.[option];
|
|
43
|
+
if (value !== undefined)
|
|
44
|
+
resolved[name] = value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (layer.named)
|
|
48
|
+
Object.assign(resolved, parseNamedCredentials(layer.named, schemes));
|
|
49
|
+
}
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
/** Make convenience and named inputs comparable without expanding combinations. */
|
|
53
|
+
export function namedCredentialAvailability(schemes, options, named) {
|
|
54
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
55
|
+
if (Object.hasOwn(named, name) || scheme.options.some((option) => options.has(option)))
|
|
56
|
+
options.add("credentials." + name);
|
|
57
|
+
}
|
|
58
|
+
return options;
|
|
59
|
+
}
|
|
60
|
+
/** Bound file/stdin reads before allocating a full credential document. */
|
|
61
|
+
export function readNamedCredentialsFile(input, schemes) {
|
|
62
|
+
let fd;
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let total = 0;
|
|
65
|
+
try {
|
|
66
|
+
fd = input === "-" ? 0 : openSync(input.slice(1), "r");
|
|
67
|
+
while (true) {
|
|
68
|
+
const buffer = Buffer.alloc(Math.min(32_768, 1_048_577 - total));
|
|
69
|
+
const size = readSync(fd, buffer, 0, buffer.length, null);
|
|
70
|
+
if (!size)
|
|
71
|
+
break;
|
|
72
|
+
total += size;
|
|
73
|
+
if (total > 1_048_576)
|
|
74
|
+
throw new Error("size");
|
|
75
|
+
chunks.push(buffer.subarray(0, size));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error("Cannot read named credentials. Supply a readable JSON file or stdin input of at most 1 MiB.");
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (fd !== undefined && input !== "-")
|
|
83
|
+
closeSync(fd);
|
|
84
|
+
}
|
|
85
|
+
return parseNamedCredentials(Buffer.concat(chunks).toString("utf8"), schemes);
|
|
86
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface OAuthLoginConfig {
|
|
2
|
+
issuer: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
discoveryUrl?: string;
|
|
5
|
+
authorizationUrl?: string;
|
|
6
|
+
tokenUrl?: string;
|
|
7
|
+
/** Register http://127.0.0.1/callback; an omitted port uses a random port. */
|
|
8
|
+
redirectUri?: string;
|
|
9
|
+
scopes?: string[];
|
|
10
|
+
audience?: string;
|
|
11
|
+
resource?: string;
|
|
12
|
+
/** Explicit runtime selection. The CLI verifies this ID through the API. */
|
|
13
|
+
organization?: {
|
|
14
|
+
parameter: "organization" | "organization_id";
|
|
15
|
+
id: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface OAuthLoginSession {
|
|
19
|
+
accessToken: string;
|
|
20
|
+
refreshToken?: string;
|
|
21
|
+
expiresAt?: number;
|
|
22
|
+
issuer: string;
|
|
23
|
+
clientId: string;
|
|
24
|
+
tokenUrl: string;
|
|
25
|
+
/** The actual native callback used for this exchange, including its port. */
|
|
26
|
+
redirectUri: string;
|
|
27
|
+
revocationUrl?: string;
|
|
28
|
+
scopes?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface OAuthLoginInteraction {
|
|
31
|
+
/** Open or display the URL. Never send the verifier or returned tokens. */
|
|
32
|
+
authorize(url: string): void | Promise<void>;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Authorization Code + S256 PKCE for a public native client. The listener
|
|
37
|
+
* binds only a loopback IP, verifies state/issuer, and closes on every path. */
|
|
38
|
+
export declare function oauthBrowserLogin(config: OAuthLoginConfig, interaction: OAuthLoginInteraction): Promise<OAuthLoginSession>;
|
|
39
|
+
//# sourceMappingURL=oauth-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-login.d.ts","sourceRoot":"","sources":["../src/oauth-login.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,YAAY,CAAC,EAAE;QAAE,SAAS,EAAE,cAAc,GAAG,iBAAiB,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgDD;gFACgF;AAChF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,qBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgGhI"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { oauthJsonRequest, OAuthResponseError } from "./oauth-request.js";
|
|
4
|
+
function endpoint(value) {
|
|
5
|
+
const url = new URL(value);
|
|
6
|
+
const loopback = ["127.0.0.1", "[::1]", "localhost"].includes(url.hostname);
|
|
7
|
+
if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) || url.username || url.password || url.hash) {
|
|
8
|
+
throw new Error("OAuth endpoints require HTTPS without credentials or fragments; loopback HTTP is allowed for development.");
|
|
9
|
+
}
|
|
10
|
+
return url;
|
|
11
|
+
}
|
|
12
|
+
async function metadata(config, signal) {
|
|
13
|
+
const issuer = endpoint(config.issuer);
|
|
14
|
+
if (issuer.search)
|
|
15
|
+
throw new Error("The OAuth issuer must not contain a query.");
|
|
16
|
+
const path = issuer.pathname.replace(/\/$/, "");
|
|
17
|
+
const urls = config.discoveryUrl ? [config.discoveryUrl] : [
|
|
18
|
+
issuer.origin + "/.well-known/oauth-authorization-server" + path,
|
|
19
|
+
issuer.origin + path + "/.well-known/openid-configuration",
|
|
20
|
+
];
|
|
21
|
+
for (const url of urls) {
|
|
22
|
+
let response;
|
|
23
|
+
try {
|
|
24
|
+
response = await oauthJsonRequest(endpoint(url), { headers: { Accept: "application/json" }, signal }, 10_000);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (!signal.aborted && error instanceof OAuthResponseError && error.code === "request_failed")
|
|
28
|
+
continue;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
if (response.status !== 200)
|
|
32
|
+
continue;
|
|
33
|
+
const data = response.data;
|
|
34
|
+
if (data.issuer !== config.issuer)
|
|
35
|
+
throw new Error("OAuth discovery returned a different issuer. Check the issuer and discovery URL.");
|
|
36
|
+
if (Array.isArray(data.code_challenge_methods_supported) && !data.code_challenge_methods_supported.includes("S256")) {
|
|
37
|
+
throw new Error("The provider does not advertise S256 PKCE support.");
|
|
38
|
+
}
|
|
39
|
+
const authorizationUrl = config.authorizationUrl ?? data.authorization_endpoint;
|
|
40
|
+
const tokenUrl = config.tokenUrl ?? data.token_endpoint;
|
|
41
|
+
if (typeof authorizationUrl !== "string" || typeof tokenUrl !== "string")
|
|
42
|
+
throw new Error("Browser login requires authorization and token endpoints.");
|
|
43
|
+
return {
|
|
44
|
+
authorizationUrl: endpoint(authorizationUrl), tokenUrl: endpoint(tokenUrl).href,
|
|
45
|
+
...(typeof data.revocation_endpoint === "string" ? { revocationUrl: endpoint(data.revocation_endpoint).href } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Some custom providers publish no metadata. Their explicit endpoints are
|
|
49
|
+
// owner configuration, not endpoints accepted from an unverified document.
|
|
50
|
+
if (config.authorizationUrl && config.tokenUrl)
|
|
51
|
+
return {
|
|
52
|
+
authorizationUrl: endpoint(config.authorizationUrl), tokenUrl: endpoint(config.tokenUrl).href,
|
|
53
|
+
};
|
|
54
|
+
throw new Error("Could not discover OAuth endpoints. Check the issuer or provide explicit authorization and token URLs.");
|
|
55
|
+
}
|
|
56
|
+
/** Authorization Code + S256 PKCE for a public native client. The listener
|
|
57
|
+
* binds only a loopback IP, verifies state/issuer, and closes on every path. */
|
|
58
|
+
export async function oauthBrowserLogin(config, interaction) {
|
|
59
|
+
if (!config.clientId.trim())
|
|
60
|
+
throw new Error("Browser login requires a public client ID.");
|
|
61
|
+
if (config.organization && (!["organization", "organization_id"].includes(config.organization.parameter) || typeof config.organization.id !== "string" || !config.organization.id || config.organization.id.length > 512 || /\s|[\u0000-\u001F\u007F]/.test(config.organization.id)))
|
|
62
|
+
throw new Error("Organization selection requires a supported parameter and a nonempty organization ID without whitespace.");
|
|
63
|
+
const signal = AbortSignal.any([interaction.signal ?? new AbortController().signal, AbortSignal.timeout(interaction.timeoutMs ?? 600_000)]);
|
|
64
|
+
signal.throwIfAborted();
|
|
65
|
+
const endpoints = await metadata(config, signal);
|
|
66
|
+
const redirect = new URL(config.redirectUri ?? "http://127.0.0.1/callback");
|
|
67
|
+
if (redirect.protocol !== "http:" || !["127.0.0.1", "[::1]"].includes(redirect.hostname) || redirect.username || redirect.password || redirect.search || redirect.hash) {
|
|
68
|
+
throw new Error("The OAuth redirect must be an HTTP loopback IP URL without credentials, query, or fragment.");
|
|
69
|
+
}
|
|
70
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
71
|
+
const state = randomBytes(32).toString("base64url");
|
|
72
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
73
|
+
let resolveCode;
|
|
74
|
+
let rejectCode;
|
|
75
|
+
const codeResult = new Promise((resolve, reject) => { resolveCode = resolve; rejectCode = reject; });
|
|
76
|
+
// Observe rejection even if opening the browser or starting the server fails.
|
|
77
|
+
void codeResult.catch(() => undefined);
|
|
78
|
+
let accepted = false;
|
|
79
|
+
const server = createServer((request, response) => {
|
|
80
|
+
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
81
|
+
response.setHeader("Cache-Control", "no-store");
|
|
82
|
+
response.setHeader("Referrer-Policy", "no-referrer");
|
|
83
|
+
response.setHeader("Content-Security-Policy", "default-src 'none'");
|
|
84
|
+
let incoming;
|
|
85
|
+
try {
|
|
86
|
+
incoming = new URL(request.url ?? "/", redirect.origin);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
response.writeHead(400).end("Invalid callback URL.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (accepted || request.method !== "GET" || request.headers.host !== redirect.host || incoming.pathname !== redirect.pathname) {
|
|
93
|
+
response.writeHead(404).end("Not found.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const returnedState = Buffer.from(incoming.searchParams.get("state") ?? "");
|
|
97
|
+
const expectedState = Buffer.from(state);
|
|
98
|
+
if (incoming.searchParams.getAll("state").length !== 1 || returnedState.length !== expectedState.length || !timingSafeEqual(returnedState, expectedState)) {
|
|
99
|
+
response.writeHead(400).end("Invalid login state. Return to the terminal and try again.");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const returnedIssuer = incoming.searchParams.get("iss");
|
|
103
|
+
if (returnedIssuer !== null && (incoming.searchParams.getAll("iss").length !== 1 || returnedIssuer !== config.issuer)) {
|
|
104
|
+
response.writeHead(400).end("The login issuer did not match.");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
accepted = true;
|
|
108
|
+
if (incoming.searchParams.has("error")) {
|
|
109
|
+
response.writeHead(400).end("Login was not approved. Return to the terminal.", () => rejectCode(new Error("OAuth authorization was denied or cancelled. Run login again to retry.")));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const code = incoming.searchParams.get("code");
|
|
113
|
+
if (!code || incoming.searchParams.getAll("code").length !== 1) {
|
|
114
|
+
response.writeHead(400).end("The provider returned no authorization code.", () => rejectCode(new Error("OAuth callback did not contain one authorization code.")));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
response.end("Authorization received. Return to the terminal to finish signing in.", () => resolveCode(code));
|
|
118
|
+
});
|
|
119
|
+
server.headersTimeout = 10_000;
|
|
120
|
+
server.requestTimeout = 10_000;
|
|
121
|
+
const aborted = () => rejectCode(new Error("Browser login timed out or was cancelled. Run login again to retry."));
|
|
122
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
123
|
+
try {
|
|
124
|
+
await new Promise((resolve, reject) => {
|
|
125
|
+
server.once("error", reject);
|
|
126
|
+
server.listen(Number(redirect.port) || 0, redirect.hostname === "[::1]" ? "::1" : "127.0.0.1", () => { server.off("error", reject); resolve(); });
|
|
127
|
+
});
|
|
128
|
+
const address = server.address();
|
|
129
|
+
if (!address || typeof address === "string")
|
|
130
|
+
throw new Error("Could not start the OAuth callback listener.");
|
|
131
|
+
redirect.port = String(address.port);
|
|
132
|
+
signal.throwIfAborted();
|
|
133
|
+
const authorization = new URL(endpoints.authorizationUrl);
|
|
134
|
+
if (config.organization) {
|
|
135
|
+
const other = config.organization.parameter === "organization" ? "organization_id" : "organization";
|
|
136
|
+
if (authorization.searchParams.has(other))
|
|
137
|
+
throw new Error("The authorization URL contains a conflicting organization parameter. Update the provider configuration.");
|
|
138
|
+
authorization.searchParams.set(config.organization.parameter, config.organization.id);
|
|
139
|
+
}
|
|
140
|
+
for (const [key, value] of Object.entries({ response_type: "code", client_id: config.clientId, redirect_uri: redirect.href, state, code_challenge: challenge, code_challenge_method: "S256", ...(config.scopes?.length ? { scope: config.scopes.join(" ") } : {}), ...(config.audience ? { audience: config.audience } : {}), ...(config.resource ? { resource: config.resource } : {}) }))
|
|
141
|
+
authorization.searchParams.set(key, value);
|
|
142
|
+
await interaction.authorize(authorization.href);
|
|
143
|
+
const code = await codeResult;
|
|
144
|
+
signal.throwIfAborted();
|
|
145
|
+
const response = await oauthJsonRequest(endpoints.tokenUrl, {
|
|
146
|
+
method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" }, redirect: "error",
|
|
147
|
+
body: new URLSearchParams({ grant_type: "authorization_code", client_id: config.clientId, redirect_uri: redirect.href, code, code_verifier: verifier, ...(config.audience ? { audience: config.audience } : {}), ...(config.resource ? { resource: config.resource } : {}) }),
|
|
148
|
+
signal,
|
|
149
|
+
});
|
|
150
|
+
const data = response.data;
|
|
151
|
+
if (response.status !== 200 || !data || typeof data.access_token !== "string" || !data.access_token || typeof data.token_type !== "string" || data.token_type.toLowerCase() !== "bearer") {
|
|
152
|
+
throw new Error(`OAuth token exchange failed (HTTP ${response.status}). Check the public client registration and run login again.`);
|
|
153
|
+
}
|
|
154
|
+
if (data.expires_in !== undefined && (typeof data.expires_in !== "number" || !Number.isFinite(data.expires_in) || data.expires_in <= 0))
|
|
155
|
+
throw new Error("OAuth provider returned an invalid token lifetime.");
|
|
156
|
+
return {
|
|
157
|
+
accessToken: data.access_token,
|
|
158
|
+
...(typeof data.refresh_token === "string" && data.refresh_token ? { refreshToken: data.refresh_token } : {}),
|
|
159
|
+
...(typeof data.expires_in === "number" ? { expiresAt: Date.now() + data.expires_in * 1000 } : {}),
|
|
160
|
+
issuer: config.issuer, clientId: config.clientId, tokenUrl: endpoints.tokenUrl, redirectUri: redirect.href,
|
|
161
|
+
...(endpoints.revocationUrl ? { revocationUrl: endpoints.revocationUrl } : {}),
|
|
162
|
+
...(typeof data.scope === "string" ? { scopes: data.scope.split(/\s+/).filter(Boolean) } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
signal.removeEventListener("abort", aborted);
|
|
167
|
+
server.closeAllConnections();
|
|
168
|
+
if (server.listening)
|
|
169
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
170
|
+
}
|
|
171
|
+
}
|