@oauth2-cli/qui-cli 0.5.10 → 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 CHANGED
@@ -2,6 +2,26 @@
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
+
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)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * further token managment debugging output ([fda83da](https://github.com/battis/oauth2-cli/commit/fda83da8794d2f7b7221224619b34148ae0a8fe1))
24
+
5
25
  ## [0.5.10](https://github.com/battis/oauth2-cli/compare/qui-cli-plugin/0.5.9...qui-cli-plugin/0.5.10) (2026-01-18)
6
26
 
7
27
 
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
- See [@qui-cli](https://github.com/battis/qui-cli#readme) for more information about quickly building CLI apps.
16
+ `index.ts`
17
17
 
18
- See [examples/qui-cli-plugin](../../examples/qui-cli-plugin) for a sample use of this plugin with an arbitrary API.
18
+ ```ts
19
+ import { OAuth2 } from '@oauth2-cli/qui-cli';
20
+ import { Core } from '@qui-cli/core';
19
21
 
20
- See [@oauth2-cli/canvas](https://github.com/groton-school/canvas-cli/tree/main/packages/oauth2-cli/canvas#readme) for an example of extending this plugin for a specific API.
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,7 +1,9 @@
1
- import { JSONValue } from '@battis/typescript-tricks';
2
1
  import * as OAuth2CLI from 'oauth2-cli';
3
- import { 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
- request(url: URL | string, method?: string, body?: FetchBody, headers?: Headers, dPoPOptions?: DPoPOptions): Promise<Response>;
6
- requestJSON<T extends JSONValue = JSONValue>(url: URL | string, method?: string, body?: FetchBody, headers?: Headers, dPoPOptions?: DPoPOptions): Promise<T>;
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>;
7
9
  }
package/dist/Client.js CHANGED
@@ -1,6 +1,21 @@
1
1
  import { Log } from '@qui-cli/log';
2
2
  import * as OAuth2CLI from 'oauth2-cli';
3
3
  export class Client extends OAuth2CLI.Client {
4
+ async getConfiguration() {
5
+ const config = await super.getConfiguration();
6
+ Log.debug('OAuth 2.0 configuration', config);
7
+ return config;
8
+ }
9
+ async authorize() {
10
+ Log.debug('Authorizing new access token');
11
+ return await super.authorize();
12
+ }
13
+ async refreshTokenGrant(token) {
14
+ Log.debug('Refreshing expired access token', { token });
15
+ const refreshed = await super.refreshTokenGrant(token);
16
+ Log.debug('Received refreshed access token', { token: refreshed });
17
+ return refreshed;
18
+ }
4
19
  async request(url, method, body, headers, dPoPOptions) {
5
20
  Log.debug({ request: { method, url, headers, body, dPoPOptions } });
6
21
  const response = await super.request(url, method, body, headers, dPoPOptions);
@@ -1,42 +1,28 @@
1
- import { PathString, URLString } from '@battis/descriptive-types';
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
- export { Credentials, FileStorage, Token, TokenStorage } from 'oauth2-cli';
7
- export * from './Client.js';
8
- export * from './EnvironmentStorage.js';
9
- type ParamNames = 'clientId' | 'clientSecret' | 'scope' | 'redirectUri' | 'authorizationEndpoint' | 'tokenEndpoint' | 'tokenPath' | 'accessToken';
10
- type EnvironmentVars = Record<ParamNames, string>;
11
- type SupportUrls = Record<ParamNames, URLString>;
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
- clientId?: string;
21
- clientSecret?: string;
22
- scope?: string;
23
- redirectUri?: URLString;
24
- headers?: Record<string, string>;
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
- man?: Partial<Usage>;
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 cliConfig;
48
- private client;
49
- configure(proposal?: ConfigurationProposal): void;
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
- getClient(): C;
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,7 @@
1
+ import { Token } from 'oauth2-cli';
2
+ export declare class EnvironmentStorage implements Token.TokenStorage {
3
+ private tokenEnvVar;
4
+ constructor(tokenEnvVar?: string);
5
+ load(): Promise<string | undefined>;
6
+ save(refresh_token: string): Promise<void>;
7
+ }
@@ -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
+ }
@@ -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';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- import * as OAuth2 from './Module.js';
2
- export { OAuth2 };
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 * as OAuth2 from './Module.js';
3
- export { OAuth2 };
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.5.10",
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.1",
20
- "openid-client": "^6.8.1",
21
- "oauth2-cli": "0.5.0"
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.6",
26
- "@qui-cli/env-1password": "^1.2.6",
27
- "@qui-cli/log": "^4.0.2",
28
- "@qui-cli/plugin": "^4.0.0",
29
- "@qui-cli/root": "^3.0.6",
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,7 +0,0 @@
1
- import { Token, TokenStorage } from 'oauth2-cli';
2
- export declare class EnvironmentStorage implements TokenStorage {
3
- private tokenEnvVar;
4
- constructor(tokenEnvVar?: string);
5
- load(): Promise<Token | undefined>;
6
- save(tokens: Token): Promise<void>;
7
- }
@@ -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
- }