oauth2-cli 0.8.2 → 0.8.4
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 +14 -0
- package/dist/Client.d.ts +5 -1
- package/dist/Client.js +14 -6
- package/dist/Session.d.ts +2 -2
- package/dist/Session.js +30 -19
- package/dist/WebServer.d.ts +10 -1
- package/dist/WebServer.js +13 -5
- package/package.json +1 -1
- package/views/complete.ejs +4 -1
- package/views/error.ejs +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [0.8.4](https://github.com/battis/oauth2-cli/compare/oauth2-cli/0.8.3...oauth2-cli/0.8.4) (2026-02-19)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* human-readable client name for clarity ([68b4287](https://github.com/battis/oauth2-cli/commit/68b42872998ec423440d07447012282528e3ee76))
|
|
11
|
+
|
|
12
|
+
## [0.8.3](https://github.com/battis/oauth2-cli/compare/oauth2-cli/0.8.2...oauth2-cli/0.8.3) (2026-02-18)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* continued server timeout improvements ([2e8d275](https://github.com/battis/oauth2-cli/commit/2e8d275706be411b317e2257bd97ee8f7fe4bc95))
|
|
18
|
+
|
|
5
19
|
## [0.8.2](https://github.com/battis/oauth2-cli/compare/oauth2-cli/0.8.1...oauth2-cli/0.8.2) (2026-02-18)
|
|
6
20
|
|
|
7
21
|
|
package/dist/Client.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ import * as Token from './Token/index.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export declare const DEFAULT_REDIRECT_URI = "http://localhost:3000/oauth2-cli/redirect";
|
|
16
16
|
export type ClientOptions<C extends Credentials = Credentials> = {
|
|
17
|
+
/** Human-readable name for client in messages */
|
|
18
|
+
name?: string;
|
|
17
19
|
/** Credentials for server access */
|
|
18
20
|
credentials: C;
|
|
19
21
|
/** Optional request components to inject */
|
|
@@ -66,6 +68,7 @@ type GetTokenOptions = {
|
|
|
66
68
|
*/
|
|
67
69
|
export declare class Client<C extends Credentials = Credentials> extends EventEmitter {
|
|
68
70
|
static readonly TokenEvent = "token";
|
|
71
|
+
readonly name?: string;
|
|
69
72
|
protected credentials: C;
|
|
70
73
|
protected base_url?: requestish.URL.ish;
|
|
71
74
|
protected config?: OpenIDClient.Configuration;
|
|
@@ -74,7 +77,8 @@ export declare class Client<C extends Credentials = Credentials> extends EventEm
|
|
|
74
77
|
private token?;
|
|
75
78
|
private tokenLock;
|
|
76
79
|
private storage?;
|
|
77
|
-
constructor({ credentials, base_url, views, inject, storage }: ClientOptions<C>);
|
|
80
|
+
constructor({ name, credentials, base_url, views, inject, storage }: ClientOptions<C>);
|
|
81
|
+
clientName(): string;
|
|
78
82
|
get redirect_uri(): requestish.URL.ish;
|
|
79
83
|
/**
|
|
80
84
|
* @throws IndeterminateConfiguration if provided credentials combined with
|
package/dist/Client.js
CHANGED
|
@@ -19,6 +19,7 @@ export const DEFAULT_REDIRECT_URI = 'http://localhost:3000/oauth2-cli/redirect';
|
|
|
19
19
|
*/
|
|
20
20
|
export class Client extends EventEmitter {
|
|
21
21
|
static TokenEvent = 'token';
|
|
22
|
+
name;
|
|
22
23
|
credentials;
|
|
23
24
|
base_url;
|
|
24
25
|
config;
|
|
@@ -27,14 +28,21 @@ export class Client extends EventEmitter {
|
|
|
27
28
|
token;
|
|
28
29
|
tokenLock = new Mutex();
|
|
29
30
|
storage;
|
|
30
|
-
constructor({ credentials, base_url, views, inject, storage }) {
|
|
31
|
+
constructor({ name, credentials, base_url, views, inject, storage }) {
|
|
31
32
|
super();
|
|
33
|
+
this.name = name;
|
|
32
34
|
this.credentials = credentials;
|
|
33
35
|
this.base_url = base_url;
|
|
34
36
|
this.views = views;
|
|
35
37
|
this.inject = inject;
|
|
36
38
|
this.storage = storage;
|
|
37
39
|
}
|
|
40
|
+
clientName() {
|
|
41
|
+
if (this.name && this.name.length > 0) {
|
|
42
|
+
return this.name;
|
|
43
|
+
}
|
|
44
|
+
return 'oauth2-cli';
|
|
45
|
+
}
|
|
38
46
|
get redirect_uri() {
|
|
39
47
|
return this.credentials.redirect_uri;
|
|
40
48
|
}
|
|
@@ -61,7 +69,7 @@ export class Client extends EventEmitter {
|
|
|
61
69
|
}, this.credentials.client_id, { client_secret: this.credentials.client_secret });
|
|
62
70
|
}
|
|
63
71
|
if (!this.config) {
|
|
64
|
-
throw new Error(
|
|
72
|
+
throw new Error(`The ${this.clientName()} configuration could not be constructed from provided credentials.`, {
|
|
65
73
|
cause: {
|
|
66
74
|
credentials: this.credentials,
|
|
67
75
|
'OpenID configuration result': error
|
|
@@ -121,7 +129,7 @@ export class Client extends EventEmitter {
|
|
|
121
129
|
: undefined));
|
|
122
130
|
}
|
|
123
131
|
catch (cause) {
|
|
124
|
-
session.reject(new Error(
|
|
132
|
+
session.reject(new Error(`Error making ${this.clientName()} Authorization Code Grant request`, { cause }));
|
|
125
133
|
}
|
|
126
134
|
}
|
|
127
135
|
async refreshTokenGrant({ refresh_token = this.token?.refresh_token, inject: request } = {}) {
|
|
@@ -161,7 +169,7 @@ export class Client extends EventEmitter {
|
|
|
161
169
|
async save(token) {
|
|
162
170
|
this.token = token;
|
|
163
171
|
if (!token.access_token) {
|
|
164
|
-
throw new Error(
|
|
172
|
+
throw new Error(`No access_token in response to ${this.clientName()}.`, {
|
|
165
173
|
cause: token
|
|
166
174
|
});
|
|
167
175
|
}
|
|
@@ -190,7 +198,7 @@ export class Client extends EventEmitter {
|
|
|
190
198
|
requestish.URL.toString(this.base_url || this.credentials.issuer), requestish.URL.toString(url).replace(/^\/?/, ''));
|
|
191
199
|
}
|
|
192
200
|
else {
|
|
193
|
-
throw new Error(`Invalid request URL "${url}"`, {
|
|
201
|
+
throw new Error(`Invalid request URL "${url}" to ${this.clientName()}`, {
|
|
194
202
|
cause: {
|
|
195
203
|
base_url: this.base_url,
|
|
196
204
|
issuer: this.credentials.issuer,
|
|
@@ -221,7 +229,7 @@ export class Client extends EventEmitter {
|
|
|
221
229
|
return (await response.json());
|
|
222
230
|
}
|
|
223
231
|
else {
|
|
224
|
-
throw new Error(
|
|
232
|
+
throw new Error(`The response could not be parsed as JSON by ${this.clientName()}.`, {
|
|
225
233
|
cause: response
|
|
226
234
|
});
|
|
227
235
|
}
|
package/dist/Session.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type SessionOptions = {
|
|
|
13
13
|
};
|
|
14
14
|
export type SessionResolver = (response: Token.Response) => void | Promise<void>;
|
|
15
15
|
export declare class Session {
|
|
16
|
-
|
|
16
|
+
readonly client: Client;
|
|
17
17
|
private readonly outOfBandRedirectServer;
|
|
18
18
|
/** PKCE code_verifier */
|
|
19
19
|
readonly code_verifier: string;
|
|
@@ -28,7 +28,7 @@ export declare class Session {
|
|
|
28
28
|
* {@link authorizationCodeGrant}
|
|
29
29
|
*/
|
|
30
30
|
get resolve(): SessionResolver;
|
|
31
|
-
reject(
|
|
31
|
+
reject(cause: unknown): void;
|
|
32
32
|
constructor({ client, views, inject: request }: SessionOptions);
|
|
33
33
|
/** Instantiate the web server that will listen for the out-of-band redirect */
|
|
34
34
|
protected instantiateWebServer(options: Omit<WebServer.WebServerOptions, 'session'>): WebServer.WebServerInterface;
|
package/dist/Session.js
CHANGED
|
@@ -20,16 +20,16 @@ export class Session {
|
|
|
20
20
|
*/
|
|
21
21
|
get resolve() {
|
|
22
22
|
if (!this._resolve) {
|
|
23
|
-
throw new Error('
|
|
23
|
+
throw new Error(`${this.client.clientName()}'s Session resolve method is ${this._resolve}`);
|
|
24
24
|
}
|
|
25
25
|
return this._resolve;
|
|
26
26
|
}
|
|
27
|
-
reject(
|
|
28
|
-
throw
|
|
27
|
+
reject(cause) {
|
|
28
|
+
throw new Error(`${this.client.clientName()}'s Session failed`, { cause });
|
|
29
29
|
}
|
|
30
30
|
constructor({ client, views, inject: request }) {
|
|
31
|
-
this.spinner = ora('Awaiting interactive authorization').start();
|
|
32
31
|
this.client = client;
|
|
32
|
+
this.spinner = ora(`${this.client.clientName()} awaiting interactive authorization`).start();
|
|
33
33
|
this.inject = request;
|
|
34
34
|
this.outOfBandRedirectServer = this.instantiateWebServer({ views });
|
|
35
35
|
}
|
|
@@ -47,36 +47,48 @@ export class Session {
|
|
|
47
47
|
try {
|
|
48
48
|
this._resolve = (response) => {
|
|
49
49
|
let closed = false;
|
|
50
|
-
this.spinner.text =
|
|
51
|
-
'Waiting for out-of-band redirect server to shut down';
|
|
50
|
+
this.spinner.text = `${this.client.clientName()} waiting for out-of-band redirect server to shut down`;
|
|
52
51
|
this.outOfBandRedirectServer.close().then(() => {
|
|
53
52
|
closed = true;
|
|
54
|
-
this.spinner.succeed(
|
|
53
|
+
this.spinner.succeed(`Interactive authorization for ${this.client.clientName()} complete`);
|
|
55
54
|
resolve(response);
|
|
56
55
|
});
|
|
57
56
|
setTimeout(() => {
|
|
58
57
|
if (!closed) {
|
|
59
58
|
this.spinner.text =
|
|
60
|
-
|
|
61
|
-
' Your browser may be holding the connection to the server open.\n' +
|
|
62
|
-
' Please close the "Authorization Complete" tab in your browser
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
`Still waiting for out-of-band redirect server for ${this.client.clientName()} to shut down.\n` +
|
|
60
|
+
' Your browser may be holding the connection to the server open.\n\n' +
|
|
61
|
+
' Please close the "Authorization Complete" tab in your browser.';
|
|
62
|
+
}
|
|
63
|
+
}, 5000);
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
if (!closed) {
|
|
66
|
+
this.spinner.text =
|
|
67
|
+
`Still waiting for out-of-band redirect server for ${this.client.clientName()} to shut down.\n` +
|
|
68
|
+
' Your browser may be holding the connection to the server open.\n\n' +
|
|
69
|
+
' Please close the browser window.';
|
|
65
70
|
}
|
|
66
71
|
}, 10000);
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
if (!closed) {
|
|
74
|
+
this.spinner.text =
|
|
75
|
+
`Still waiting for out-of-band redirect server for ${this.client.clientName()} to shut down.\n` +
|
|
76
|
+
' Your browser may be holding the connection to the server open.\n\n' +
|
|
77
|
+
' Please quit the browser.';
|
|
78
|
+
}
|
|
79
|
+
}, 15000);
|
|
67
80
|
};
|
|
68
81
|
const url = gcrtl
|
|
69
82
|
.expand(this.outOfBandRedirectServer.authorization_endpoint, this.client.redirect_uri)
|
|
70
83
|
.toString();
|
|
71
84
|
//open(url);
|
|
72
|
-
this.spinner.text = `Please continue interactive authorization at ${Colors.url(url)} in your browser`;
|
|
85
|
+
this.spinner.text = `Please continue interactive authorization for ${this.client.clientName()} at ${Colors.url(url)} in your browser`;
|
|
73
86
|
}
|
|
74
87
|
catch (cause) {
|
|
75
|
-
this.spinner.text =
|
|
76
|
-
'Waiting for out-of-band redirect server to shut down';
|
|
88
|
+
this.spinner.text = `Waiting for out-of-band redirect server for ${this.client.clientName()} to shut down`;
|
|
77
89
|
this.outOfBandRedirectServer.close().then(() => {
|
|
78
|
-
this.spinner.fail(
|
|
79
|
-
reject(new Error(
|
|
90
|
+
this.spinner.fail(`Interactive authorization for ${this.client.clientName()} failed`);
|
|
91
|
+
reject(new Error(`Error in Authorization Code flow for ${this.client.clientName()}`, { cause }));
|
|
80
92
|
});
|
|
81
93
|
}
|
|
82
94
|
});
|
|
@@ -93,8 +105,7 @@ export class Session {
|
|
|
93
105
|
* Code Grant flow
|
|
94
106
|
*/
|
|
95
107
|
async handleAuthorizationCodeRedirect(req) {
|
|
96
|
-
this.spinner.text =
|
|
97
|
-
'Completing access token request with provided authorization code';
|
|
108
|
+
this.spinner.text = `Completing access token request fro ${this.client.clientName()} with provided authorization code`;
|
|
98
109
|
return await this.client.handleAuthorizationCodeRedirect(req, this);
|
|
99
110
|
}
|
|
100
111
|
}
|
package/dist/WebServer.d.ts
CHANGED
|
@@ -15,6 +15,15 @@ export type WebServerOptions = {
|
|
|
15
15
|
* URL, the first step in the Authorization Code Grant flow.
|
|
16
16
|
*/
|
|
17
17
|
authorize_endpoint?: PathString;
|
|
18
|
+
/**
|
|
19
|
+
* The number of milliseconds of inactivity before a socket is presumed to
|
|
20
|
+
* have timed out. This can be reduced to limit potential wait times during
|
|
21
|
+
* interactive authentication, but must still be long enough to allow time for
|
|
22
|
+
* the authorization code to be exchanged for an access token.
|
|
23
|
+
*
|
|
24
|
+
* Defaults to 1000 milliseconds
|
|
25
|
+
*/
|
|
26
|
+
timeout?: number;
|
|
18
27
|
};
|
|
19
28
|
export declare const DEFAULT_AUTHORIZE_ENDPOINT = "/oauth2-cli/authorize";
|
|
20
29
|
export interface WebServerInterface {
|
|
@@ -35,7 +44,7 @@ export declare class WebServer implements WebServerInterface {
|
|
|
35
44
|
protected readonly port: string;
|
|
36
45
|
readonly authorization_endpoint: PathString;
|
|
37
46
|
private server;
|
|
38
|
-
constructor({ session, views, authorize_endpoint }: WebServerOptions);
|
|
47
|
+
constructor({ session, views, authorize_endpoint, timeout }: WebServerOptions);
|
|
39
48
|
/**
|
|
40
49
|
* Set the path to folder of *.ejs templates
|
|
41
50
|
*
|
package/dist/WebServer.js
CHANGED
|
@@ -23,7 +23,8 @@ export class WebServer {
|
|
|
23
23
|
port;
|
|
24
24
|
authorization_endpoint;
|
|
25
25
|
server;
|
|
26
|
-
constructor({ session, views, authorize_endpoint = DEFAULT_AUTHORIZE_ENDPOINT
|
|
26
|
+
constructor({ session, views, authorize_endpoint = DEFAULT_AUTHORIZE_ENDPOINT, timeout = 1000 // milliseconds
|
|
27
|
+
}) {
|
|
27
28
|
this.session = session;
|
|
28
29
|
this.authorization_endpoint = authorize_endpoint;
|
|
29
30
|
this.views = views;
|
|
@@ -37,6 +38,9 @@ export class WebServer {
|
|
|
37
38
|
app.get(this.authorization_endpoint, this.handleAuthorizationEndpoint.bind(this));
|
|
38
39
|
app.get(gcrtl.path(url), this.handleRedirect.bind(this));
|
|
39
40
|
this.server = app.listen(gcrtl.port(url));
|
|
41
|
+
this.server.timeout = timeout;
|
|
42
|
+
this.server.keepAliveTimeout = 0;
|
|
43
|
+
this.server.keepAliveTimeoutBuffer = 0;
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
42
46
|
* Set the path to folder of *.ejs templates
|
|
@@ -61,11 +65,12 @@ export class WebServer {
|
|
|
61
65
|
this.views = views;
|
|
62
66
|
}
|
|
63
67
|
async render(res, template, data = {}) {
|
|
68
|
+
const name = this.session.client.clientName();
|
|
64
69
|
async function attemptToRender(views) {
|
|
65
70
|
if (ejs && views) {
|
|
66
71
|
const viewPath = path.resolve(import.meta.dirname, views, template);
|
|
67
72
|
if (fs.existsSync(viewPath)) {
|
|
68
|
-
res.send(await ejs.renderFile(viewPath, data));
|
|
73
|
+
res.send(await ejs.renderFile(viewPath, { name, ...data }));
|
|
69
74
|
return true;
|
|
70
75
|
}
|
|
71
76
|
}
|
|
@@ -87,12 +92,15 @@ export class WebServer {
|
|
|
87
92
|
try {
|
|
88
93
|
await this.session.handleAuthorizationCodeRedirect(req);
|
|
89
94
|
if (!(await this.render(res, 'complete.ejs'))) {
|
|
90
|
-
res.send(
|
|
95
|
+
res.send(`${this.session.client.clientName()} authorization complete. You may close this window.`);
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
catch (error) {
|
|
94
99
|
if (!(await this.render(res, 'error.ejs', { error }))) {
|
|
95
|
-
res.send(
|
|
100
|
+
res.send({
|
|
101
|
+
client: this.session.client.clientName(),
|
|
102
|
+
error
|
|
103
|
+
});
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
}
|
|
@@ -101,7 +109,7 @@ export class WebServer {
|
|
|
101
109
|
return new Promise((resolve, reject) => {
|
|
102
110
|
this.server.close((cause) => {
|
|
103
111
|
if (cause) {
|
|
104
|
-
reject(new Error(
|
|
112
|
+
reject(new Error(`Error shutting down ${this.session.client.clientName()} out-of-band redirect web server`, {
|
|
105
113
|
cause
|
|
106
114
|
}));
|
|
107
115
|
}
|
package/package.json
CHANGED
package/views/complete.ejs
CHANGED
package/views/error.ejs
CHANGED
|
@@ -30,11 +30,12 @@
|
|
|
30
30
|
<body>
|
|
31
31
|
<div class="wrapper">
|
|
32
32
|
<div class="center container">
|
|
33
|
-
<h1>
|
|
33
|
+
<h1>
|
|
34
|
+
<%= name %>
|
|
35
|
+
Authorization Error
|
|
36
|
+
</h1>
|
|
34
37
|
<div class="alert alert-danger" role="alert">
|
|
35
|
-
|
|
36
|
-
<pre><%= JSON.stringify(error,null,2) %></pre>
|
|
37
|
-
<% } %>
|
|
38
|
+
<pre><%= JSON.stringify(error, null, 2) %></pre>
|
|
38
39
|
</div>
|
|
39
40
|
<p>You may close this window.</p>
|
|
40
41
|
</div>
|