@oauth2-cli/qui-cli 0.5.11 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +19 -3
- package/dist/Client.d.ts +6 -7
- package/dist/Client.js +2 -2
- package/dist/{OAuth2.d.ts → OAuth2Plugin.d.ts} +26 -32
- package/dist/OAuth2Plugin.js +198 -0
- package/dist/Token/EnvironmentStorage.d.ts +7 -0
- package/dist/Token/EnvironmentStorage.js +13 -0
- package/dist/Token/index.d.ts +2 -0
- package/dist/Token/index.js +2 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +6 -2
- package/package.json +10 -12
- package/dist/EnvironmentStorage.d.ts +0 -7
- package/dist/EnvironmentStorage.js +0 -24
- package/dist/Module.d.ts +0 -11
- package/dist/Module.js +0 -13
- package/dist/OAuth2.js +0 -196
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
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.6.0](https://github.com/battis/oauth2-cli/compare/qui-cli-plugin/0.5.11...qui-cli-plugin/0.6.0) (2026-02-15)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* limit TokenStorage to storing _only_ refresh_tokens
|
|
11
|
+
* rewritten from scratch to be more easily maintainable
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* limit TokenStorage to storing _only_ refresh_tokens ([3de9c96](https://github.com/battis/oauth2-cli/commit/3de9c96510d15eebd51a0be7d8df278614541f95))
|
|
16
|
+
* rewritten from scratch to be more easily maintainable ([0f764d3](https://github.com/battis/oauth2-cli/commit/0f764d333135ae70bd9b3c4433b7c4a2ef757979))
|
|
17
|
+
|
|
5
18
|
## [0.5.11](https://github.com/battis/oauth2-cli/compare/qui-cli-plugin/0.5.10...qui-cli-plugin/0.5.11) (2026-01-20)
|
|
6
19
|
|
|
7
20
|
|
package/README.md
CHANGED
|
@@ -13,8 +13,24 @@ npm install @qui-cli/core @oauth2-cli/qui-cli
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
`index.ts`
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
```ts
|
|
19
|
+
import { OAuth2 } from '@oauth2-cli/qui-cli';
|
|
20
|
+
import { Core } from '@qui-cli/core';
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
// initialize the `qui-cli` framework, ideally loading
|
|
23
|
+
await Core.run();
|
|
24
|
+
|
|
25
|
+
console.log(
|
|
26
|
+
await OAuth2.requestJSON('https://api.github.com/repos/battis/oauth2-cli')
|
|
27
|
+
);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Without additional configuration, `OAuth2` will look for `ISSUER`, `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `AUTHORIZATION_ENDPOINT`, and `TOKEN_ENDPOINT` values in the environment and will attempt to configure the client using whichever subset of those values are present.
|
|
31
|
+
|
|
32
|
+
Refer to [`oauth2-cli`](https://www.npmjs.com/package/oauth2-cli) for more information about configuring that tool in more nuanced ways.
|
|
33
|
+
|
|
34
|
+
Refer to [`qui-cli`](https://github.com/battis/qui-cli#readme) for more information about using those tools to build command line apps.
|
|
35
|
+
|
|
36
|
+
Specific examples of usage of this plugin are available in the [examples](https://github.com/battis/oauth2-cli/tree/main/examples/qui-cli#readme) directory of the repo.
|
package/dist/Client.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { JSONValue } from '@battis/typescript-tricks';
|
|
2
1
|
import * as OAuth2CLI from 'oauth2-cli';
|
|
3
|
-
import { Configuration, DPoPOptions, FetchBody } from 'openid-client';
|
|
2
|
+
import type { Configuration, DPoPOptions, FetchBody, JsonValue } from 'openid-client';
|
|
4
3
|
export declare class Client extends OAuth2CLI.Client {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
protected
|
|
8
|
-
request(url: URL
|
|
9
|
-
requestJSON<T extends
|
|
4
|
+
getConfiguration(): Promise<Configuration>;
|
|
5
|
+
authorize(): Promise<OAuth2CLI.Token.Response>;
|
|
6
|
+
protected refreshTokenGrant(token: OAuth2CLI.Token.Response): Promise<OAuth2CLI.Token.Response | undefined>;
|
|
7
|
+
request(url: OAuth2CLI.Request.URL.ish, method?: string, body?: FetchBody, headers?: OAuth2CLI.Request.Headers.ish, dPoPOptions?: DPoPOptions): Promise<Response>;
|
|
8
|
+
requestJSON<T extends JsonValue = JsonValue>(url: OAuth2CLI.Request.URL.ish, method?: string, body?: FetchBody, headers?: OAuth2CLI.Request.Headers.ish, dPoPOptions?: DPoPOptions): Promise<T>;
|
|
10
9
|
}
|
package/dist/Client.js
CHANGED
|
@@ -10,9 +10,9 @@ export class Client extends OAuth2CLI.Client {
|
|
|
10
10
|
Log.debug('Authorizing new access token');
|
|
11
11
|
return await super.authorize();
|
|
12
12
|
}
|
|
13
|
-
async
|
|
13
|
+
async refreshTokenGrant(token) {
|
|
14
14
|
Log.debug('Refreshing expired access token', { token });
|
|
15
|
-
const refreshed = await super.
|
|
15
|
+
const refreshed = await super.refreshTokenGrant(token);
|
|
16
16
|
Log.debug('Received refreshed access token', { token: refreshed });
|
|
17
17
|
return refreshed;
|
|
18
18
|
}
|
|
@@ -1,42 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { URLString } from '@battis/descriptive-types';
|
|
2
2
|
import { JSONValue } from '@battis/typescript-tricks';
|
|
3
3
|
import * as Plugin from '@qui-cli/plugin';
|
|
4
4
|
import * as OAuth2CLI from 'oauth2-cli';
|
|
5
5
|
import { Client } from './Client.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type
|
|
10
|
-
type
|
|
11
|
-
type
|
|
12
|
-
type Hints = Record<ParamNames, string>;
|
|
13
|
-
type OptionNames = Record<ParamNames, string>;
|
|
14
|
-
type OptionSuppression = Record<ParamNames, boolean>;
|
|
6
|
+
type CredentialKey = 'issuer' | 'client_id' | 'client_secret' | 'redirect_uri' | 'authorization_endpoint' | 'token_endpoint' | 'scope';
|
|
7
|
+
type EnvironmentVars = Record<CredentialKey, string>;
|
|
8
|
+
type SupportUrls = Record<CredentialKey, URLString>;
|
|
9
|
+
type Hints = Record<CredentialKey, string>;
|
|
10
|
+
type OptionNames = Record<CredentialKey, string>;
|
|
11
|
+
type OptionSuppression = Record<CredentialKey, boolean>;
|
|
15
12
|
type Usage = {
|
|
16
13
|
heading: string;
|
|
17
14
|
text?: string[];
|
|
18
15
|
};
|
|
19
16
|
export type Configuration = Plugin.Configuration & {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
authorizationEndpoint?: URLString;
|
|
26
|
-
tokenEndpoint?: URLString;
|
|
27
|
-
store?: OAuth2CLI.TokenStorage;
|
|
28
|
-
tokenPath?: PathString;
|
|
29
|
-
opt: OptionNames;
|
|
17
|
+
credentials?: Partial<OAuth2CLI.Credentials.Combined>;
|
|
18
|
+
inject?: OAuth2CLI.Request.Injection;
|
|
19
|
+
storage?: OAuth2CLI.Token.TokenStorage;
|
|
20
|
+
man?: Usage;
|
|
21
|
+
opt?: Partial<OptionNames>;
|
|
30
22
|
url?: Partial<SupportUrls>;
|
|
31
23
|
hint?: Partial<Hints>;
|
|
32
|
-
env: EnvironmentVars;
|
|
33
|
-
man: Usage;
|
|
34
|
-
suppress?: Partial<OptionSuppression>;
|
|
35
|
-
};
|
|
36
|
-
export type ConfigurationProposal = Partial<Omit<Configuration, 'opt' | 'env' | 'man'>> & {
|
|
37
|
-
opt?: Partial<OptionNames>;
|
|
38
24
|
env?: Partial<EnvironmentVars>;
|
|
39
|
-
|
|
25
|
+
suppress?: Partial<OptionSuppression>;
|
|
40
26
|
};
|
|
41
27
|
export declare class OAuth2Plugin<C extends Client = Client> {
|
|
42
28
|
readonly name: string;
|
|
@@ -44,16 +30,24 @@ export declare class OAuth2Plugin<C extends Client = Client> {
|
|
|
44
30
|
private static names;
|
|
45
31
|
private static ports;
|
|
46
32
|
constructor(name?: string);
|
|
47
|
-
private
|
|
48
|
-
private
|
|
49
|
-
|
|
33
|
+
private credentials?;
|
|
34
|
+
private man;
|
|
35
|
+
private opt;
|
|
36
|
+
private url;
|
|
37
|
+
private hint;
|
|
38
|
+
private env;
|
|
39
|
+
private suppress;
|
|
40
|
+
private inject;
|
|
41
|
+
private storage?;
|
|
42
|
+
private _client;
|
|
43
|
+
configure(proposal?: Configuration): void;
|
|
50
44
|
options(): Plugin.Options;
|
|
51
45
|
init({ values }: Plugin.ExpectedArguments<typeof this.options>): Promise<void>;
|
|
52
46
|
protected instantiateClient(...args: ConstructorParameters<typeof OAuth2CLI.Client>): C;
|
|
53
|
-
|
|
54
|
-
getToken(): Promise<OAuth2CLI.Token | undefined>;
|
|
47
|
+
get client(): C;
|
|
55
48
|
request(...args: Parameters<OAuth2CLI.Client['request']>): Promise<Response>;
|
|
56
49
|
requestJSON<T extends JSONValue>(...args: Parameters<OAuth2CLI.Client['requestJSON']>): Promise<T>;
|
|
57
50
|
fetch(...args: Parameters<OAuth2CLI.Client['fetch']>): Promise<Response>;
|
|
58
51
|
fetchJSON<T extends JSONValue>(...args: Parameters<OAuth2CLI.Client['fetchJSON']>): Promise<T>;
|
|
59
52
|
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Colors } from '@qui-cli/colors';
|
|
2
|
+
import { Env } from '@qui-cli/env';
|
|
3
|
+
import { Log } from '@qui-cli/log';
|
|
4
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
5
|
+
import * as OAuth2CLI from 'oauth2-cli';
|
|
6
|
+
export class OAuth2Plugin {
|
|
7
|
+
name;
|
|
8
|
+
static names = [];
|
|
9
|
+
static ports = {};
|
|
10
|
+
constructor(name = '@oauth2-cli/qui-cli') {
|
|
11
|
+
this.name = name;
|
|
12
|
+
if (OAuth2Plugin.names.includes(name)) {
|
|
13
|
+
throw new Error(`A @qui-cli/plugin named ${Colors.value(name)} has already been instantiated.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
credentials;
|
|
17
|
+
man = {
|
|
18
|
+
heading: 'OAuth 2.0 client options'
|
|
19
|
+
};
|
|
20
|
+
opt = {
|
|
21
|
+
issuer: 'issuer',
|
|
22
|
+
client_id: 'clientId',
|
|
23
|
+
client_secret: 'clientSecret',
|
|
24
|
+
scope: 'scope',
|
|
25
|
+
redirect_uri: 'redirectUri',
|
|
26
|
+
authorization_endpoint: 'authorizationEndpoint',
|
|
27
|
+
token_endpoint: 'tokenEndpoint'
|
|
28
|
+
};
|
|
29
|
+
url = undefined;
|
|
30
|
+
hint = {
|
|
31
|
+
redirect_uri: Colors.quotedValue(`"http://localhost:3000/redirect"`)
|
|
32
|
+
};
|
|
33
|
+
env = {
|
|
34
|
+
issuer: 'ISSUER',
|
|
35
|
+
client_id: 'CLIENT_ID',
|
|
36
|
+
client_secret: 'CLIENT_SECRET',
|
|
37
|
+
scope: 'SCOPE',
|
|
38
|
+
redirect_uri: 'REDIRECT_URI',
|
|
39
|
+
authorization_endpoint: 'AUTHORIZATION_ENDPOINT',
|
|
40
|
+
token_endpoint: 'TOKEN_ENDPOINT'
|
|
41
|
+
};
|
|
42
|
+
suppress = undefined;
|
|
43
|
+
inject = undefined;
|
|
44
|
+
storage = undefined;
|
|
45
|
+
_client = undefined;
|
|
46
|
+
configure(proposal = {}) {
|
|
47
|
+
function hydrate(p, c) {
|
|
48
|
+
if (p) {
|
|
49
|
+
for (const k of Object.keys(p)) {
|
|
50
|
+
if (p[k] !== undefined) {
|
|
51
|
+
if (!c) {
|
|
52
|
+
c = {};
|
|
53
|
+
}
|
|
54
|
+
c[k] = p[k];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return c;
|
|
59
|
+
}
|
|
60
|
+
this.credentials = hydrate(proposal.credentials, this.credentials);
|
|
61
|
+
this.storage = Plugin.hydrate(proposal.storage, this.storage);
|
|
62
|
+
this.inject = hydrate(proposal.inject, this.inject);
|
|
63
|
+
this.man = Plugin.hydrate(proposal.man, this.man);
|
|
64
|
+
this.opt = hydrate(proposal.opt, this.opt);
|
|
65
|
+
this.url = hydrate(proposal.url, this.url);
|
|
66
|
+
this.hint = hydrate(proposal.hint, this.hint);
|
|
67
|
+
this.env = hydrate(proposal.env, this.env);
|
|
68
|
+
this.suppress = hydrate(proposal.suppress, this.suppress);
|
|
69
|
+
if (this.credentials?.redirect_uri) {
|
|
70
|
+
const url = OAuth2CLI.Request.URL.from(this.credentials.redirect_uri);
|
|
71
|
+
if (url.hostname !== 'localhost' &&
|
|
72
|
+
!/^\/https?\/localhost(:\d+)?\//.test(url.pathname)) {
|
|
73
|
+
Log.warning(`The ${Colors.optionArg(this.opt.redirect_uri)} value ${Colors.url(this.credentials.redirect_uri)} may not work: it ${Colors.keyword('must')} redirect to ${Colors.url('localhost')}`);
|
|
74
|
+
}
|
|
75
|
+
if (url.protocol !== 'http:' &&
|
|
76
|
+
!/^\/http\/localhost(:\d+)?\//.test(url.pathname)) {
|
|
77
|
+
Log.warning(`The ${Colors.url(url.protocol)} protocol may not work without additional configuration. The ` +
|
|
78
|
+
`server listening for the redirect is not automatically ` +
|
|
79
|
+
`provisioned with an SSL certificate`);
|
|
80
|
+
}
|
|
81
|
+
if (OAuth2Plugin.ports[url.port] &&
|
|
82
|
+
OAuth2Plugin.ports[url.port] !== this.name) {
|
|
83
|
+
Log.warning(`The port ${Colors.value(url.port)} has already been registered to another instance of this plugin ` +
|
|
84
|
+
`named ${Colors.value(OAuth2Plugin.ports[url.port])}. This will likely cause a failure if both instances of the ` +
|
|
85
|
+
`plugin are listening for redirects at relatively proximate ` +
|
|
86
|
+
`moments in time.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
options() {
|
|
91
|
+
const descriptions = {
|
|
92
|
+
issuer: `OpenID issuer URL. Defaults to environment variable ` +
|
|
93
|
+
`${Colors.varName(this.env.issuer)}, if present.`,
|
|
94
|
+
client_id: `OAuth 2.0 client ID. Defaults to environment variable ` +
|
|
95
|
+
`${Colors.varName(this.env.client_id)}, if present.`,
|
|
96
|
+
client_secret: `OAuth 2.0 client secret. Defaults to environment variable ` +
|
|
97
|
+
`${Colors.varName(this.env.client_secret)}, if present.`,
|
|
98
|
+
scope: `OAuth 2.0 scope. Defaults to environment variable ` +
|
|
99
|
+
`${Colors.varName(this.env.scope)}, if present.`,
|
|
100
|
+
redirect_uri: `OAuth 2.0 redirect URI, must be to host ${Colors.url('localhost')}. ` +
|
|
101
|
+
`Defaults to environment variable ` +
|
|
102
|
+
`${Colors.varName(this.env.redirect_uri)}, if present.`,
|
|
103
|
+
authorization_endpoint: `OAuth 2.0 authorization endpoint. Defaults to environment variable ` +
|
|
104
|
+
`${Colors.varName(this.env.authorization_endpoint)}, if present.`,
|
|
105
|
+
token_endpoint: `OAuth 2.0 token endpoint, will fall back to ` +
|
|
106
|
+
`${Colors.optionArg(`--${this.opt.authorization_endpoint}`)} if ` +
|
|
107
|
+
`not provided. Defaults to environment variable ` +
|
|
108
|
+
`${Colors.varName(this.env.token_endpoint)}, if present.`
|
|
109
|
+
};
|
|
110
|
+
const opt = {};
|
|
111
|
+
for (const paramName of Object.keys(descriptions)) {
|
|
112
|
+
if (!this.suppress || !this.suppress[paramName]) {
|
|
113
|
+
const option = { description: descriptions[paramName] };
|
|
114
|
+
if (this.url && this.url[paramName]) {
|
|
115
|
+
option.description = `${option.description} See ${Colors.url(this.url[paramName])} for more information.`;
|
|
116
|
+
}
|
|
117
|
+
if (this.hint[paramName]) {
|
|
118
|
+
option.hint = this.hint[paramName];
|
|
119
|
+
}
|
|
120
|
+
switch (paramName) {
|
|
121
|
+
case 'client_id':
|
|
122
|
+
case 'client_secret':
|
|
123
|
+
option.secret = true;
|
|
124
|
+
}
|
|
125
|
+
const param = this.credentials
|
|
126
|
+
? this.credentials[paramName]
|
|
127
|
+
: undefined;
|
|
128
|
+
if (typeof param === 'string') {
|
|
129
|
+
option.default = param;
|
|
130
|
+
}
|
|
131
|
+
opt[this.opt[paramName]] = option;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
man: [
|
|
136
|
+
{ level: 1, text: this.man.heading },
|
|
137
|
+
...(this.man.text || []).map((t) => ({ text: t }))
|
|
138
|
+
],
|
|
139
|
+
opt
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async init({ values }) {
|
|
143
|
+
const credentials = {};
|
|
144
|
+
for (const key of Object.keys(this.opt)) {
|
|
145
|
+
credentials[key] =
|
|
146
|
+
values[this.opt[key]] ||
|
|
147
|
+
(this.credentials ? this.credentials[key] : undefined) ||
|
|
148
|
+
(await Env.get({ key: this.env[key] }));
|
|
149
|
+
}
|
|
150
|
+
this.configure({ credentials });
|
|
151
|
+
}
|
|
152
|
+
instantiateClient(...args) {
|
|
153
|
+
return new OAuth2CLI.Client(...args);
|
|
154
|
+
}
|
|
155
|
+
get client() {
|
|
156
|
+
if (!this._client) {
|
|
157
|
+
if (!this.credentials?.client_id) {
|
|
158
|
+
throw new Error(`A ${Colors.optionArg(this.opt.client_id)} ${Colors.keyword('must')} be configured.`);
|
|
159
|
+
}
|
|
160
|
+
if (!this.credentials?.client_secret) {
|
|
161
|
+
throw new Error(`A ${Colors.optionArg(this.opt.client_secret)} ${Colors.keyword('must')} be configured.`);
|
|
162
|
+
}
|
|
163
|
+
if (!this.credentials?.redirect_uri) {
|
|
164
|
+
throw new Error(`A ${Colors.optionArg(this.opt.redirect_uri)} ${Colors.keyword('must')} be configured.`);
|
|
165
|
+
}
|
|
166
|
+
if (!this.credentials?.issuer) {
|
|
167
|
+
if (!this.credentials?.authorization_endpoint) {
|
|
168
|
+
throw new Error(`Either an ${Colors.optionArg(this.opt.issuer)} or ` +
|
|
169
|
+
`${Colors.optionArg(this.opt.authorization_endpoint)} ` +
|
|
170
|
+
`${Colors.keyword('must')} be configured.`);
|
|
171
|
+
}
|
|
172
|
+
if (!this.credentials?.token_endpoint) {
|
|
173
|
+
throw new Error(`Either an ${Colors.optionArg(this.opt.issuer)} or ` +
|
|
174
|
+
`${Colors.optionArg(this.opt.token_endpoint)} ` +
|
|
175
|
+
`${Colors.keyword('must')} be configured.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this._client = this.instantiateClient({
|
|
179
|
+
credentials: this.credentials,
|
|
180
|
+
headers: this.inject?.headers,
|
|
181
|
+
storage: this.storage
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return this._client;
|
|
185
|
+
}
|
|
186
|
+
request(...args) {
|
|
187
|
+
return this.client.request(...args);
|
|
188
|
+
}
|
|
189
|
+
requestJSON(...args) {
|
|
190
|
+
return this.client.requestJSON(...args);
|
|
191
|
+
}
|
|
192
|
+
fetch(...args) {
|
|
193
|
+
return this.client.fetch(...args);
|
|
194
|
+
}
|
|
195
|
+
fetchJSON(...args) {
|
|
196
|
+
return this.client.fetchJSON(...args);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Env } from '@qui-cli/env';
|
|
2
|
+
export class EnvironmentStorage {
|
|
3
|
+
tokenEnvVar;
|
|
4
|
+
constructor(tokenEnvVar = 'REFRESH_TOKEN') {
|
|
5
|
+
this.tokenEnvVar = tokenEnvVar;
|
|
6
|
+
}
|
|
7
|
+
async load() {
|
|
8
|
+
return await Env.get({ key: this.tokenEnvVar });
|
|
9
|
+
}
|
|
10
|
+
async save(refresh_token) {
|
|
11
|
+
await Env.set({ key: this.tokenEnvVar, value: refresh_token });
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
export
|
|
1
|
+
import { OAuth2Plugin } from './OAuth2Plugin.js';
|
|
2
|
+
export declare const OAuth2: OAuth2Plugin<import("./Client.js").Client>;
|
|
3
|
+
export { Credentials, Errors, WebServer } from 'oauth2-cli';
|
|
4
|
+
export * from './Client.js';
|
|
5
|
+
export * from './OAuth2Plugin.js';
|
|
6
|
+
export * as Token from './Token/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { register } from '@qui-cli/plugin';
|
|
2
|
-
import
|
|
3
|
-
export
|
|
2
|
+
import { OAuth2Plugin } from './OAuth2Plugin.js';
|
|
3
|
+
export const OAuth2 = new OAuth2Plugin();
|
|
4
|
+
export { Credentials, Errors, WebServer } from 'oauth2-cli';
|
|
5
|
+
export * from './Client.js';
|
|
6
|
+
export * from './OAuth2Plugin.js';
|
|
7
|
+
export * as Token from './Token/index.js';
|
|
4
8
|
await register(OAuth2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oauth2-cli/qui-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "@qui-cli/plugin wrapper for oauth2-cli",
|
|
5
5
|
"homepage": "https://github.com/battis/oauth2-cli/tree/main/packages/qui-cli#readme",
|
|
6
6
|
"repository": {
|
|
@@ -16,28 +16,26 @@
|
|
|
16
16
|
"main": "./dist/index.js",
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@qui-cli/colors": "^3.2.
|
|
20
|
-
"
|
|
21
|
-
"oauth2-cli": "0.5.1"
|
|
19
|
+
"@qui-cli/colors": "^3.2.3",
|
|
20
|
+
"oauth2-cli": "0.6.0"
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
24
23
|
"@battis/descriptive-types": "^0.2.6",
|
|
25
|
-
"@battis/typescript-tricks": "^0.7.
|
|
26
|
-
"@qui-cli/env
|
|
27
|
-
"@qui-cli/log": "^4.0.
|
|
28
|
-
"@qui-cli/plugin": "^4.
|
|
29
|
-
"@
|
|
30
|
-
"@tsconfig/node24": "^24.0.3",
|
|
24
|
+
"@battis/typescript-tricks": "^0.7.7",
|
|
25
|
+
"@qui-cli/env": "^5.1.1",
|
|
26
|
+
"@qui-cli/log": "^4.0.3",
|
|
27
|
+
"@qui-cli/plugin": "^4.1.0",
|
|
28
|
+
"@tsconfig/node24": "^24.0.4",
|
|
31
29
|
"commit-and-tag-version": "^12.6.1",
|
|
32
30
|
"del-cli": "^7.0.0",
|
|
33
31
|
"npm-run-all": "^4.1.5",
|
|
32
|
+
"openid-client": "^6.8.2",
|
|
34
33
|
"typescript": "^5.9.3"
|
|
35
34
|
},
|
|
36
35
|
"peerDependencies": {
|
|
37
36
|
"@qui-cli/env-1password": ">=1",
|
|
38
37
|
"@qui-cli/log": ">=3",
|
|
39
|
-
"@qui-cli/plugin": ">=3"
|
|
40
|
-
"@qui-cli/root": ">=3"
|
|
38
|
+
"@qui-cli/plugin": ">=3"
|
|
41
39
|
},
|
|
42
40
|
"scripts": {
|
|
43
41
|
"clean": "del ./dist",
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Env } from '@qui-cli/env-1password';
|
|
2
|
-
import { Token } from 'oauth2-cli';
|
|
3
|
-
export class EnvironmentStorage {
|
|
4
|
-
tokenEnvVar;
|
|
5
|
-
constructor(tokenEnvVar = 'ACCESS_TOKEN') {
|
|
6
|
-
this.tokenEnvVar = tokenEnvVar;
|
|
7
|
-
}
|
|
8
|
-
async load() {
|
|
9
|
-
try {
|
|
10
|
-
const data = JSON.parse(await Env.get({ key: this.tokenEnvVar }));
|
|
11
|
-
if (!data.access_token) {
|
|
12
|
-
throw new Error('No access token');
|
|
13
|
-
}
|
|
14
|
-
return Token.fromResponse(data);
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
16
|
-
}
|
|
17
|
-
catch (_) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
async save(tokens) {
|
|
22
|
-
await Env.set({ key: this.tokenEnvVar, value: JSON.stringify(tokens) });
|
|
23
|
-
}
|
|
24
|
-
}
|
package/dist/Module.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export * from './OAuth2.js';
|
|
2
|
-
export declare const name: string;
|
|
3
|
-
export declare const configure: (proposal?: import("./OAuth2.js").ConfigurationProposal) => void;
|
|
4
|
-
export declare const options: () => import("@qui-cli/plugin").Options;
|
|
5
|
-
export declare const init: ({ values }: import("@qui-cli/plugin").ExpectedArguments<typeof this.options>) => Promise<void>;
|
|
6
|
-
export declare const getToken: () => Promise<import("oauth2-cli/dist/Token.js").Token | undefined>;
|
|
7
|
-
export declare const getClient: () => import("./Client.js").Client;
|
|
8
|
-
export declare const request: (url: string | URL, method?: string | undefined, body?: import("openid-client").FetchBody, headers?: Headers | undefined, dPoPOptions?: import("openid-client").DPoPOptions | undefined) => Promise<Response>;
|
|
9
|
-
export declare const requestJSON: <T extends import("@battis/typescript-tricks").JSONValue>(url: string | URL, method?: string | undefined, body?: import("openid-client").FetchBody, headers?: Headers | undefined, dPoPOptions?: import("openid-client").DPoPOptions | undefined) => Promise<T>;
|
|
10
|
-
export declare const fetch: (endpoint: string | URL | Request, init?: RequestInit | undefined) => Promise<Response>;
|
|
11
|
-
export declare const fetchJSON: <T extends import("@battis/typescript-tricks").JSONValue>(endpoint: string | URL | Request, init?: RequestInit | undefined) => Promise<T>;
|
package/dist/Module.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { OAuth2Plugin } from './OAuth2.js';
|
|
2
|
-
export * from './OAuth2.js';
|
|
3
|
-
const oauth2 = new OAuth2Plugin();
|
|
4
|
-
export const name = oauth2.name;
|
|
5
|
-
export const configure = oauth2.configure.bind(oauth2);
|
|
6
|
-
export const options = oauth2.options.bind(oauth2);
|
|
7
|
-
export const init = oauth2.init.bind(oauth2);
|
|
8
|
-
export const getToken = oauth2.getToken.bind(oauth2);
|
|
9
|
-
export const getClient = oauth2.getClient.bind(oauth2);
|
|
10
|
-
export const request = oauth2.request.bind(oauth2);
|
|
11
|
-
export const requestJSON = oauth2.requestJSON.bind(oauth2);
|
|
12
|
-
export const fetch = oauth2.fetch.bind(oauth2);
|
|
13
|
-
export const fetchJSON = oauth2.fetchJSON.bind(oauth2);
|
package/dist/OAuth2.js
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { Colors } from '@qui-cli/colors';
|
|
2
|
-
import { Env } from '@qui-cli/env-1password';
|
|
3
|
-
import { Log } from '@qui-cli/log';
|
|
4
|
-
import { Root } from '@qui-cli/root';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import * as OAuth2CLI from 'oauth2-cli';
|
|
7
|
-
import { EnvironmentStorage } from './EnvironmentStorage.js';
|
|
8
|
-
export { FileStorage, Token } from 'oauth2-cli';
|
|
9
|
-
export * from './Client.js';
|
|
10
|
-
export * from './EnvironmentStorage.js';
|
|
11
|
-
export class OAuth2Plugin {
|
|
12
|
-
name;
|
|
13
|
-
static names = [];
|
|
14
|
-
static ports = {};
|
|
15
|
-
constructor(name = '@oauth2-cli/qui-cli') {
|
|
16
|
-
this.name = name;
|
|
17
|
-
if (OAuth2Plugin.names.includes(name)) {
|
|
18
|
-
throw new Error(`A @qui-cli/plugin named ${Colors.value(name)} has already been instantiated.`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
cliConfig = {
|
|
22
|
-
opt: {
|
|
23
|
-
clientId: 'clientId',
|
|
24
|
-
clientSecret: 'clientSecret',
|
|
25
|
-
scope: 'scope',
|
|
26
|
-
redirectUri: 'redirectUri',
|
|
27
|
-
authorizationEndpoint: 'authorizationEndpoint',
|
|
28
|
-
tokenEndpoint: 'tokenEndpoint',
|
|
29
|
-
tokenPath: 'tokenPath',
|
|
30
|
-
accessToken: 'accessToken'
|
|
31
|
-
},
|
|
32
|
-
hint: {
|
|
33
|
-
redirectUri: Colors.quotedValue(`"https://localhost:3000/redirect"`)
|
|
34
|
-
},
|
|
35
|
-
env: {
|
|
36
|
-
clientId: 'CLIENT_ID',
|
|
37
|
-
clientSecret: 'CLIENT_SECRET',
|
|
38
|
-
scope: 'SCOPE',
|
|
39
|
-
redirectUri: 'REDIRECT_URI',
|
|
40
|
-
authorizationEndpoint: 'AUTHORIZATION_ENDPOINT',
|
|
41
|
-
tokenEndpoint: 'TOKEN_ENDPOINT',
|
|
42
|
-
tokenPath: 'TOKEN_PATH',
|
|
43
|
-
accessToken: 'ACCESS_TOKEN'
|
|
44
|
-
},
|
|
45
|
-
man: {
|
|
46
|
-
heading: 'OAuth 2.0 client options'
|
|
47
|
-
},
|
|
48
|
-
suppress: {
|
|
49
|
-
tokenPath: true,
|
|
50
|
-
accessToken: true
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
client = undefined;
|
|
54
|
-
configure(proposal = {}) {
|
|
55
|
-
for (const key in proposal) {
|
|
56
|
-
if (proposal[key] !== undefined) {
|
|
57
|
-
this.cliConfig[key] = proposal[key];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (!this.cliConfig.store) {
|
|
61
|
-
if (this.cliConfig.tokenPath) {
|
|
62
|
-
this.cliConfig.store = new OAuth2CLI.FileStorage(path.resolve(Root.path(), this.cliConfig.tokenPath));
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
this.cliConfig.store = new EnvironmentStorage(this.cliConfig.env.accessToken);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (this.cliConfig.redirectUri) {
|
|
69
|
-
const url = new URL(this.cliConfig.redirectUri);
|
|
70
|
-
if (url.hostname !== 'localhost') {
|
|
71
|
-
Log.warning(`The ${Colors.varName('redirect_uri')} value ${Colors.url(this.cliConfig.redirect_uri)} may not work: it ${Colors.keyword('must')} point to ${Colors.url('localhost')}`);
|
|
72
|
-
}
|
|
73
|
-
if (url.protocol !== 'http:') {
|
|
74
|
-
Log.warning(`The ${Colors.url(url.protocol)} protocol may not work without additional configuration. The ` +
|
|
75
|
-
`server listening for the redirect will be running at ${Colors.url(`http://localhost:${url.port}`)}`);
|
|
76
|
-
}
|
|
77
|
-
if (OAuth2Plugin.ports[url.port] &&
|
|
78
|
-
OAuth2Plugin.ports[url.port] !== this.name) {
|
|
79
|
-
Log.warning(`The port ${Colors.value(url.port)} has already been registered to another instance of this plugin ` +
|
|
80
|
-
`named ${Colors.value(OAuth2Plugin.ports[url.port])}. This will likely cause a failure if both instances of the ` +
|
|
81
|
-
`plugin are listening for redirects at relatively proximate ` +
|
|
82
|
-
`moments in time.`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
options() {
|
|
87
|
-
const descriptions = {
|
|
88
|
-
clientId: `OAuth 2.0 client ID. Defaults to environment variable ` +
|
|
89
|
-
`${Colors.value(this.cliConfig.env.clientId)}, if present.`,
|
|
90
|
-
clientSecret: `OAuth 2.0 client secret. Defaults to environment variable ` +
|
|
91
|
-
`${Colors.value(this.cliConfig.env.clientSecret)}, if present.`,
|
|
92
|
-
scope: `OAuth 2.0 scope. Defaults to environment variable ` +
|
|
93
|
-
`${Colors.varName(this.cliConfig.env.scope)}, if present.`,
|
|
94
|
-
redirectUri: `OAuth 2.0 redirect URI, must be to host ${Colors.url('localhost')}. ` +
|
|
95
|
-
`Defaults to environment variable ` +
|
|
96
|
-
`${Colors.value(this.cliConfig.env.redirectUri)}, if present.`,
|
|
97
|
-
authorizationEndpoint: `OAuth 2.0 authorization endpoint. Defaults to environment variable ` +
|
|
98
|
-
`${Colors.value(this.cliConfig.env.authorizationEndpoint)}, if present.`,
|
|
99
|
-
tokenEndpoint: `OAuth 2.0 token endpoint, will fall back to ` +
|
|
100
|
-
`${Colors.optionArg(`--${this.cliConfig.opt['authorizationEndpoint']}`)} if ` +
|
|
101
|
-
`not provided. Defaults to environment ariable ` +
|
|
102
|
-
`${Colors.value(this.cliConfig.env.tokenEndpoint)}, if present.`,
|
|
103
|
-
tokenPath: `Path to token storage JSON file. Defaults to environent variable ` +
|
|
104
|
-
`${Colors.value(this.cliConfig.env.tokenPath)}, if present.`,
|
|
105
|
-
accessToken: `Access token JSON object value. Defaults to environment variable ` +
|
|
106
|
-
`${Colors.value(this.cliConfig.env.accessToken)}, if present.`
|
|
107
|
-
};
|
|
108
|
-
const opt = {};
|
|
109
|
-
for (const paramName of Object.keys(descriptions)) {
|
|
110
|
-
if (!this.cliConfig.suppress || !this.cliConfig.suppress[paramName]) {
|
|
111
|
-
const option = { description: descriptions[paramName] };
|
|
112
|
-
if (this.cliConfig.url && this.cliConfig.url[paramName]) {
|
|
113
|
-
option.description = `${option.description} See ${Colors.url(this.cliConfig.url[paramName])} for more information.`;
|
|
114
|
-
}
|
|
115
|
-
if (this.cliConfig.hint && this.cliConfig.hint[paramName]) {
|
|
116
|
-
option.hint = this.cliConfig.hint[paramName];
|
|
117
|
-
}
|
|
118
|
-
switch (paramName) {
|
|
119
|
-
case 'clientId':
|
|
120
|
-
case 'clientSecret':
|
|
121
|
-
case 'accessToken':
|
|
122
|
-
option.secret = true;
|
|
123
|
-
}
|
|
124
|
-
const param = this.cliConfig[paramName];
|
|
125
|
-
if (typeof param === 'string') {
|
|
126
|
-
option.default = param;
|
|
127
|
-
}
|
|
128
|
-
opt[this.cliConfig.opt[paramName]] = option;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
man: [
|
|
133
|
-
{ level: 1, text: this.cliConfig.man.heading },
|
|
134
|
-
...(this.cliConfig.man.text || []).map((t) => ({ text: t }))
|
|
135
|
-
],
|
|
136
|
-
opt
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async init({ values }) {
|
|
140
|
-
const proposal = {};
|
|
141
|
-
let paramName;
|
|
142
|
-
for (paramName of Object.keys(this.cliConfig.opt)) {
|
|
143
|
-
proposal[paramName] =
|
|
144
|
-
values[this.cliConfig.opt[paramName]] ||
|
|
145
|
-
this.cliConfig[paramName] ||
|
|
146
|
-
(await Env.get({ key: this.cliConfig.env[paramName] }));
|
|
147
|
-
}
|
|
148
|
-
this.configure(proposal);
|
|
149
|
-
}
|
|
150
|
-
instantiateClient(...args) {
|
|
151
|
-
return new OAuth2CLI.Client(...args);
|
|
152
|
-
}
|
|
153
|
-
getClient() {
|
|
154
|
-
if (!this.client) {
|
|
155
|
-
const { clientId: client_id, clientSecret: client_secret, scope, redirectUri: redirect_uri, authorizationEndpoint: authorization_endpoint, tokenEndpoint: token_endpoint, headers, store } = this.cliConfig;
|
|
156
|
-
if (!client_id) {
|
|
157
|
-
throw new Error('OAuth 2.0 client ID not defined');
|
|
158
|
-
}
|
|
159
|
-
if (!client_secret) {
|
|
160
|
-
throw new Error('OAuth 2.0 client secret not defined');
|
|
161
|
-
}
|
|
162
|
-
if (!redirect_uri) {
|
|
163
|
-
throw new Error('OAuth 2.0 redirect URI not defined');
|
|
164
|
-
}
|
|
165
|
-
if (!authorization_endpoint) {
|
|
166
|
-
throw new Error('OAuth 2.0 authorization endpoint not defined');
|
|
167
|
-
}
|
|
168
|
-
this.client = this.instantiateClient({
|
|
169
|
-
client_id,
|
|
170
|
-
client_secret,
|
|
171
|
-
scope,
|
|
172
|
-
redirect_uri,
|
|
173
|
-
authorization_endpoint,
|
|
174
|
-
token_endpoint,
|
|
175
|
-
headers,
|
|
176
|
-
store
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return this.client;
|
|
180
|
-
}
|
|
181
|
-
getToken() {
|
|
182
|
-
return this.getClient().getToken();
|
|
183
|
-
}
|
|
184
|
-
request(...args) {
|
|
185
|
-
return this.getClient().request(...args);
|
|
186
|
-
}
|
|
187
|
-
requestJSON(...args) {
|
|
188
|
-
return this.getClient().requestJSON(...args);
|
|
189
|
-
}
|
|
190
|
-
fetch(...args) {
|
|
191
|
-
return this.getClient().fetch(...args);
|
|
192
|
-
}
|
|
193
|
-
fetchJSON(...args) {
|
|
194
|
-
return this.getClient().fetchJSON(...args);
|
|
195
|
-
}
|
|
196
|
-
}
|