@oauth2-cli/qui-cli 0.7.16 → 1.0.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.
@@ -0,0 +1,287 @@
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
+ import { URL } from 'requestish';
7
+ export class OAuth2Plugin {
8
+ name;
9
+ static names = [];
10
+ static registeredPorts = {};
11
+ overrideName;
12
+ reason;
13
+ /**
14
+ * @param name Human-readable name for client in messages. Must also be a
15
+ * unique qui-cli plugin name.
16
+ */
17
+ constructor(name = '@oauth2-cli/qui-cli') {
18
+ this.name = name;
19
+ if (OAuth2Plugin.names.includes(name)) {
20
+ throw new Error(`A @qui-cli/plugin named ${Colors.value(name)} has already been instantiated.`);
21
+ }
22
+ }
23
+ /** Configured client credentials */
24
+ credentials;
25
+ /** Configured client base_url */
26
+ base_url;
27
+ /** Configured usage information */
28
+ man = undefined;
29
+ /** Configured reference URLs for credentials */
30
+ url = undefined;
31
+ /** Configured hint values for credentials */
32
+ hint = {
33
+ redirect_uri: Colors.quotedValue(`"http://localhost:3000/redirect"`)
34
+ };
35
+ /** Configured environment variable names for credentials */
36
+ env = {
37
+ issuer: 'ISSUER',
38
+ client_id: 'CLIENT_ID',
39
+ client_secret: 'CLIENT_SECRET',
40
+ redirect_uri: 'REDIRECT_URI',
41
+ authorization_endpoint: 'AUTHORIZATION_ENDPOINT',
42
+ token_endpoint: 'TOKEN_ENDPOINT',
43
+ base_url: 'BASE_URL',
44
+ scope: 'SCOPE'
45
+ };
46
+ /** Configured credentials to suppress in usage and init */
47
+ suppress = undefined;
48
+ /** Configured request components for client to inject */
49
+ inject = undefined;
50
+ /** Configured {@link OAuth2CLI.Localhost.Options} to pass to client */
51
+ localhost;
52
+ /** Configured client refresh token storage strategy */
53
+ storage = undefined;
54
+ _client = undefined;
55
+ /**
56
+ * Configured oauth2-cli client
57
+ *
58
+ * Do _not_ access until intitialization is complete -- the client will be
59
+ * configured upon first access based on available configuration known at that
60
+ * time
61
+ */
62
+ get client() {
63
+ if (!this._client) {
64
+ if (!this.credentials?.client_id) {
65
+ throw new Error(`A ${Colors.varName(this.env.client_id)} ${Colors.keyword('must')} ` +
66
+ `be configured for ${this.overrideName || this.name}.`);
67
+ }
68
+ if (!this.credentials?.client_secret) {
69
+ throw new Error(`A ${Colors.varName(this.env.client_secret)} ${Colors.keyword('must')} ` +
70
+ `be configured for ${this.overrideName || this.name}.`);
71
+ }
72
+ if (!this.credentials?.redirect_uri) {
73
+ throw new Error(`A ${Colors.varName(this.env.redirect_uri)} ${Colors.keyword('must')} ` +
74
+ `be configured for ${this.overrideName || this.name}.`);
75
+ }
76
+ if (!this.credentials?.issuer) {
77
+ if (!this.credentials?.authorization_endpoint) {
78
+ throw new Error(`Either an ${Colors.varName(this.env.issuer)} or ` +
79
+ `${Colors.varName(this.env.authorization_endpoint)} ` +
80
+ `${Colors.keyword('must')} be configured for ` +
81
+ `${this.overrideName || this.name}.`);
82
+ }
83
+ if (!this.credentials?.token_endpoint) {
84
+ throw new Error(`Either an ${Colors.varName(this.env.issuer)} or ` +
85
+ `${Colors.varName(this.env.token_endpoint)} ` +
86
+ `${Colors.keyword('must')} be configured for ` +
87
+ `${this.overrideName || this.name}.`);
88
+ }
89
+ }
90
+ this._client = this.instantiateClient({
91
+ name: this.overrideName || this.name,
92
+ reason: this.reason,
93
+ credentials: this.credentials,
94
+ base_url: this.base_url,
95
+ inject: this.inject,
96
+ localhost: this.localhost,
97
+ storage: this.storage
98
+ });
99
+ }
100
+ return this._client;
101
+ }
102
+ /**
103
+ * Configure plugin for use
104
+ *
105
+ * May be called repeatedly, overlaying different options as they become
106
+ * available
107
+ *
108
+ * Invoked automatically by
109
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
110
+ * initialization
111
+ *
112
+ * @see {@link Configuration}
113
+ */
114
+ configure(proposal = {}) {
115
+ function hydrate(p, c) {
116
+ if (p) {
117
+ for (const k of Object.keys(p)) {
118
+ if (p[k] !== undefined) {
119
+ if (!c) {
120
+ c = {};
121
+ }
122
+ c[k] = p[k];
123
+ }
124
+ }
125
+ }
126
+ return c;
127
+ }
128
+ this.overrideName = Plugin.hydrate(proposal.name, this.overrideName);
129
+ this.reason = Plugin.hydrate(proposal.reason, this.reason);
130
+ this.credentials = hydrate(proposal.credentials, this.credentials);
131
+ this.base_url = Plugin.hydrate(proposal.base_url, this.base_url);
132
+ this.storage = Plugin.hydrate(proposal.storage, this.storage);
133
+ this.inject = hydrate(proposal.inject, this.inject);
134
+ this.man = Plugin.hydrate(proposal.man, this.man);
135
+ this.url = hydrate(proposal.url, this.url);
136
+ this.hint = hydrate(proposal.hint, this.hint);
137
+ this.env = hydrate(proposal.env, this.env);
138
+ this.suppress = hydrate(proposal.suppress, this.suppress);
139
+ this.localhost = hydrate(proposal.localhost, this.localhost);
140
+ if (this.credentials?.redirect_uri) {
141
+ const url = URL.from(this.credentials.redirect_uri);
142
+ if (url.hostname !== 'localhost' &&
143
+ !/^\/https?\/localhost(:\d+)?\//.test(url.pathname)) {
144
+ Log.warning(`The ${Colors.varName(this.env.redirect_uri)} value ${Colors.url(this.credentials.redirect_uri)} for ${this.overrideName || this.name} may not work: it ` +
145
+ `${Colors.keyword('must')} redirect to ${Colors.url('localhost')}`);
146
+ }
147
+ if (url.protocol !== 'http:' &&
148
+ !/^\/http\/localhost(:\d+)?\//.test(url.pathname)) {
149
+ Log.warning(`The ${Colors.url(url.protocol)} protocol may not work without additional configuration. The out-` +
150
+ `of-band server listening for the ` +
151
+ `${this.overrideName || this.name} redirect is not automatically ` +
152
+ `provisioned with an SSL certificate`);
153
+ }
154
+ if (OAuth2Plugin.registeredPorts[url.port] &&
155
+ OAuth2Plugin.registeredPorts[url.port] !== this.name) {
156
+ Log.warning(`The port ${Colors.value(url.port)} has already been registered to another instance of this plugin ` +
157
+ `named ${Colors.value(OAuth2Plugin.registeredPorts[url.port])}. This will likely cause a failure if both ` +
158
+ `${this.overrideName || this.name} and ` +
159
+ `${OAuth2Plugin.registeredPorts[url.port]} are listening for ` +
160
+ `redirects at relatively proximate moments in time.`);
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Provide usage options to
166
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} for display to user
167
+ * on command line
168
+ *
169
+ * Invoked automatically by
170
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
171
+ * initialization
172
+ */
173
+ options() {
174
+ const descriptions = {
175
+ issuer: `The OpenID ${Colors.keyword('issuer')} URL is set from the ` +
176
+ `environment variable ${Colors.varName(this.env.issuer)}, if present. ` +
177
+ `The ${Colors.varName(this.env.issuer)} is also used as a base URL for ` +
178
+ `any relative URL in API requests, unless ` +
179
+ `${Colors.varName(this.env.base_url)} is defined.`,
180
+ client_id: `The OAuth 2.0 ${Colors.keyword('client_id')} is set from the ` +
181
+ `environment variable ${Colors.varName(this.env.client_id)}, if ` +
182
+ `present.`,
183
+ client_secret: `The OAuth 2.0 ${Colors.keyword('client_secret')} is set from the ` +
184
+ `environment variable ${Colors.varName(this.env.client_secret)}, if ` +
185
+ `present.`,
186
+ scope: `The OAuth 2.0 ${Colors.keyword('scope')} is set from the environment ` +
187
+ `variable ${Colors.varName(this.env.scope)}, if present.`,
188
+ redirect_uri: `The OAuth 2.0 ${Colors.keyword('redirect_uri')}, which must at least ` +
189
+ `redirect to ${Colors.url('localhost')}, is set from the environment ` +
190
+ `variable ${Colors.varName(this.env.redirect_uri)}, if present.`,
191
+ authorization_endpoint: `The OAuth 2.0 ${Colors.keyword('authorization_endpoint')} is set ` +
192
+ `from the environment variable ` +
193
+ `${Colors.varName(this.env.authorization_endpoint)}, if present.`,
194
+ token_endpoint: `The OAuth 2.0 ${Colors.keyword('token_endpoint')} is set from the ` +
195
+ `environment variable ${Colors.varName(this.env.token_endpoint)}, if ` +
196
+ `present and will fall back to ` +
197
+ `${Colors.varName(this.env.authorization_endpoint)} if not provided.`,
198
+ base_url: `The base URL to use for API requests is set from the ` +
199
+ `environment variable ${Colors.varName(this.env.base_url)}, if ` +
200
+ `present. If ${Colors.varName(this.env.base_url)} is not defined, ` +
201
+ `${Colors.varName(this.env.issuer)} will be used as a base URL for ` +
202
+ `relative URL requests.`
203
+ };
204
+ return {
205
+ man: [
206
+ {
207
+ level: 1,
208
+ text: this.man?.heading || `${this.overrideName || this.name} options`
209
+ },
210
+ ...Object.keys(descriptions)
211
+ .filter((key) => !this.suppress || !this.suppress[key])
212
+ .map((key) => ({
213
+ text: descriptions[key] +
214
+ (this.hint[key] ? ` (e.g. ${this.hint[key]})` : '') +
215
+ (this.url && this.url[key]
216
+ ? ` See ${Colors.url(this.url[key])} for more information.`
217
+ : '')
218
+ })),
219
+ ...(this.man?.text || []).map((t) => ({ text: t }))
220
+ ]
221
+ };
222
+ }
223
+ /**
224
+ * Intialize plugin configuration from command line options and environment
225
+ *
226
+ * Invoked automatically by
227
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
228
+ * initialization
229
+ */
230
+ async init(_) {
231
+ const credentials = {};
232
+ const base_url = this.base_url ||
233
+ (await Env.get({
234
+ key: this.env.base_url
235
+ }));
236
+ for (const key of Object.keys(this.env)) {
237
+ if (key !== 'base_url') {
238
+ // FIXME better typing
239
+ // @ts-expect-error 2322
240
+ credentials[key] =
241
+ (this.credentials ? this.credentials[key] : undefined) ||
242
+ (await Env.get({ key: this.env[key] }));
243
+ }
244
+ }
245
+ this.configure({ credentials, base_url });
246
+ }
247
+ /**
248
+ * Instantiate the `oauth2-cli` client
249
+ *
250
+ * Available hook for custom configurations in plugin development
251
+ */
252
+ instantiateClient(options) {
253
+ return new OAuth2CLI.Client(options);
254
+ }
255
+ /**
256
+ * Convenience method
257
+ *
258
+ * @see {@link OAuth2CLI.Client.request}
259
+ */
260
+ request(...args) {
261
+ return this.client.request(...args);
262
+ }
263
+ /**
264
+ * Convenience method
265
+ *
266
+ * @see {@link OAuth2CLI.Client.requestJSON}
267
+ */
268
+ requestJSON(...args) {
269
+ return this.client.requestJSON(...args);
270
+ }
271
+ /**
272
+ * Convenience method
273
+ *
274
+ * @see {@link OAuth2CLI.Client.fetch}
275
+ */
276
+ fetch(...args) {
277
+ return this.client.fetch(...args);
278
+ }
279
+ /**
280
+ * Convenience method
281
+ *
282
+ * @see {@link OAuth2CLI.Client.fetchJSON}
283
+ */
284
+ fetchJSON(...args) {
285
+ return this.client.fetchJSON(...args);
286
+ }
287
+ }
@@ -0,0 +1,21 @@
1
+ import { Token } from 'oauth2-cli';
2
+ /**
3
+ * Persist a refresh token in the local environment
4
+ *
5
+ * Care should be taken when using this persistence strategy to:
6
+ *
7
+ * 1. Ideally encrypt or otherwise secure the environment value (see
8
+ * {@link https://github.com/battis/oauth2-cli/tree/main/examples/qui-cli/04%201password-integration#readme 1password-integration}
9
+ * example for one approach)
10
+ * 2. Do not commit `.env` files to a public repo
11
+ */
12
+ export declare class EnvironmentStorage implements Token.Storage {
13
+ private tokenEnvVar;
14
+ /**
15
+ * @param tokenEnvVar Name of the environment variable containing the refresh
16
+ * token, defaults to `REFRESH_TOKEN`
17
+ */
18
+ constructor(tokenEnvVar?: string);
19
+ load(): Promise<string | undefined>;
20
+ save(refresh_token: string): Promise<void>;
21
+ }
@@ -0,0 +1,27 @@
1
+ import { Env } from '@qui-cli/env';
2
+ /**
3
+ * Persist a refresh token in the local environment
4
+ *
5
+ * Care should be taken when using this persistence strategy to:
6
+ *
7
+ * 1. Ideally encrypt or otherwise secure the environment value (see
8
+ * {@link https://github.com/battis/oauth2-cli/tree/main/examples/qui-cli/04%201password-integration#readme 1password-integration}
9
+ * example for one approach)
10
+ * 2. Do not commit `.env` files to a public repo
11
+ */
12
+ export class EnvironmentStorage {
13
+ tokenEnvVar;
14
+ /**
15
+ * @param tokenEnvVar Name of the environment variable containing the refresh
16
+ * token, defaults to `REFRESH_TOKEN`
17
+ */
18
+ constructor(tokenEnvVar = 'REFRESH_TOKEN') {
19
+ this.tokenEnvVar = tokenEnvVar;
20
+ }
21
+ async load() {
22
+ return await Env.get({ key: this.tokenEnvVar });
23
+ }
24
+ async save(refresh_token) {
25
+ await Env.set({ key: this.tokenEnvVar, value: refresh_token });
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from 'oauth2-cli/dist/Token/index.js';
2
+ export * from './EnvironmentStorage.js';
@@ -0,0 +1,2 @@
1
+ export * from 'oauth2-cli/dist/Token/index.js';
2
+ export * from './EnvironmentStorage.js';
@@ -0,0 +1,4 @@
1
+ export { Credentials, Injection, Localhost, Options } from 'oauth2-cli';
2
+ export * from './Client.js';
3
+ export * from './OAuth2Plugin.js';
4
+ export * as Token from './Token/index.js';
@@ -0,0 +1,4 @@
1
+ export { Localhost } from 'oauth2-cli';
2
+ export * from './Client.js';
3
+ export * from './OAuth2Plugin.js';
4
+ export * as Token from './Token/index.js';
@@ -0,0 +1 @@
1
+ export * from "./extendable.js";
@@ -0,0 +1 @@
1
+ export * from './extendable.js';
@@ -0,0 +1,5 @@
1
+ {
2
+ "type": "module",
3
+ "main": "index.js",
4
+ "types": "index.d.ts"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "type": "module",
3
+ "main": "index.js",
4
+ "types": "index.d.ts"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oauth2-cli/qui-cli",
3
- "version": "0.7.16",
3
+ "version": "1.0.1",
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": {
@@ -17,8 +17,8 @@
17
17
  "types": "./dist/index.d.ts",
18
18
  "dependencies": {
19
19
  "@qui-cli/colors": "^3.2.3",
20
- "requestish": "0.1.1",
21
- "oauth2-cli": "0.8.9"
20
+ "oauth2-cli": "1.0.0",
21
+ "requestish": "0.1.3"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@battis/descriptive-types": "^0.2.6",
@@ -28,6 +28,7 @@
28
28
  "@qui-cli/plugin": "^4.1.0",
29
29
  "@tsconfig/node24": "^24.0.4",
30
30
  "commit-and-tag-version": "^12.6.1",
31
+ "cpy-cli": "^7.0.0",
31
32
  "del-cli": "^7.0.0",
32
33
  "npm-run-all": "^4.1.5",
33
34
  "openid-client": "^6.8.2",
@@ -39,10 +40,14 @@
39
40
  "@qui-cli/plugin": ">=3"
40
41
  },
41
42
  "scripts": {
42
- "clean": "del ./dist",
43
- "build": "run-s build:*",
43
+ "clean": "run-s clean:*",
44
+ "clean:dist": "del ./dist",
45
+ "clean:extendable": "del ./extendable",
46
+ "build": "run-s build:**",
44
47
  "build:clean": "run-s clean",
45
- "build:compile": "tsc",
48
+ "build:dist": "tsc",
49
+ "build:extendable:compile": "tsc --project tsconfig.extendable.json",
50
+ "build:extendable:package": "cpy ./package.extendable.json ./extendable/package.json",
46
51
  "release": "commit-and-tag-version"
47
52
  }
48
53
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@tsconfig/node24/tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "declaration": true,
6
+ "outDir": "./extendable"
7
+ },
8
+ "include": ["./src"],
9
+ "exclude": ["./src/index.ts", "./src/OAuth2.ts"]
10
+ }