@oauth2-cli/qui-cli 0.7.15 → 1.0.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.
@@ -3,12 +3,13 @@ import { Env } from '@qui-cli/env';
3
3
  import { Log } from '@qui-cli/log';
4
4
  import * as Plugin from '@qui-cli/plugin';
5
5
  import * as OAuth2CLI from 'oauth2-cli';
6
- import * as requestish from 'requestish';
6
+ import { URL } from 'requestish';
7
7
  export class OAuth2Plugin {
8
8
  name;
9
9
  static names = [];
10
10
  static registeredPorts = {};
11
11
  overrideName;
12
+ reason;
12
13
  /**
13
14
  * @param name Human-readable name for client in messages. Must also be a
14
15
  * unique qui-cli plugin name.
@@ -19,15 +20,21 @@ export class OAuth2Plugin {
19
20
  throw new Error(`A @qui-cli/plugin named ${Colors.value(name)} has already been instantiated.`);
20
21
  }
21
22
  }
23
+ /** Configured client credentials */
22
24
  credentials;
25
+ /** Configured client base_url */
23
26
  base_url;
27
+ /** Configured usage information */
24
28
  man = {
25
29
  heading: 'OAuth 2.0 / Open ID Connect client options'
26
30
  };
31
+ /** Configured reference URLs for credentials */
27
32
  url = undefined;
33
+ /** Configured hint values for credentials */
28
34
  hint = {
29
35
  redirect_uri: Colors.quotedValue(`"http://localhost:3000/redirect"`)
30
36
  };
37
+ /** Configured environment variable names for credentials */
31
38
  env = {
32
39
  issuer: 'ISSUER',
33
40
  client_id: 'CLIENT_ID',
@@ -38,13 +45,76 @@ export class OAuth2Plugin {
38
45
  base_url: 'BASE_URL',
39
46
  scope: 'SCOPE'
40
47
  };
48
+ /** Configured credentials to suppress in usage and init */
41
49
  suppress = {
42
50
  scope: true
43
51
  };
52
+ /** Configured request components for client to inject */
44
53
  inject = undefined;
45
- views = undefined;
54
+ /** Configured {@link OAuth2CLI.Localhost.Options} to pass to client */
55
+ localhost;
56
+ /** Configured client refresh token storage strategy */
46
57
  storage = undefined;
47
58
  _client = undefined;
59
+ /**
60
+ * Configured oauth2-cli client
61
+ *
62
+ * Do _not_ access until intitialization is complete -- the client will be
63
+ * configured upon first access based on available configuration known at that
64
+ * time
65
+ */
66
+ get client() {
67
+ if (!this._client) {
68
+ if (!this.credentials?.client_id) {
69
+ throw new Error(`A ${Colors.varName(this.env.client_id)} ${Colors.keyword('must')} ` +
70
+ `be configured for ${this.overrideName || this.name}.`);
71
+ }
72
+ if (!this.credentials?.client_secret) {
73
+ throw new Error(`A ${Colors.varName(this.env.client_secret)} ${Colors.keyword('must')} ` +
74
+ `be configured for ${this.overrideName || this.name}.`);
75
+ }
76
+ if (!this.credentials?.redirect_uri) {
77
+ throw new Error(`A ${Colors.varName(this.env.redirect_uri)} ${Colors.keyword('must')} ` +
78
+ `be configured for ${this.overrideName || this.name}.`);
79
+ }
80
+ if (!this.credentials?.issuer) {
81
+ if (!this.credentials?.authorization_endpoint) {
82
+ throw new Error(`Either an ${Colors.varName(this.env.issuer)} or ` +
83
+ `${Colors.varName(this.env.authorization_endpoint)} ` +
84
+ `${Colors.keyword('must')} be configured for ` +
85
+ `${this.overrideName || this.name}.`);
86
+ }
87
+ if (!this.credentials?.token_endpoint) {
88
+ throw new Error(`Either an ${Colors.varName(this.env.issuer)} or ` +
89
+ `${Colors.varName(this.env.token_endpoint)} ` +
90
+ `${Colors.keyword('must')} be configured for ` +
91
+ `${this.overrideName || this.name}.`);
92
+ }
93
+ }
94
+ this._client = this.instantiateClient({
95
+ name: this.overrideName || this.name,
96
+ reason: this.reason,
97
+ credentials: this.credentials,
98
+ base_url: this.base_url,
99
+ inject: this.inject,
100
+ localhost: this.localhost,
101
+ storage: this.storage
102
+ });
103
+ }
104
+ return this._client;
105
+ }
106
+ /**
107
+ * Configure plugin for use
108
+ *
109
+ * May be called repeatedly, overlaying different options as they become
110
+ * available
111
+ *
112
+ * Invoked automatically by
113
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
114
+ * initialization
115
+ *
116
+ * @see {@link Configuration}
117
+ */
48
118
  configure(proposal = {}) {
49
119
  function hydrate(p, c) {
50
120
  if (p) {
@@ -60,6 +130,7 @@ export class OAuth2Plugin {
60
130
  return c;
61
131
  }
62
132
  this.overrideName = Plugin.hydrate(proposal.name, this.overrideName);
133
+ this.reason = Plugin.hydrate(proposal.reason, this.reason);
63
134
  this.credentials = hydrate(proposal.credentials, this.credentials);
64
135
  this.base_url = Plugin.hydrate(proposal.base_url, this.base_url);
65
136
  this.storage = Plugin.hydrate(proposal.storage, this.storage);
@@ -69,8 +140,9 @@ export class OAuth2Plugin {
69
140
  this.hint = hydrate(proposal.hint, this.hint);
70
141
  this.env = hydrate(proposal.env, this.env);
71
142
  this.suppress = hydrate(proposal.suppress, this.suppress);
143
+ this.localhost = hydrate(proposal.localhost, this.localhost);
72
144
  if (this.credentials?.redirect_uri) {
73
- const url = requestish.URL.from(this.credentials.redirect_uri);
145
+ const url = URL.from(this.credentials.redirect_uri);
74
146
  if (url.hostname !== 'localhost' &&
75
147
  !/^\/https?\/localhost(:\d+)?\//.test(url.pathname)) {
76
148
  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 ` +
@@ -93,6 +165,15 @@ export class OAuth2Plugin {
93
165
  }
94
166
  }
95
167
  }
168
+ /**
169
+ * Provide usage options to
170
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} for display to user
171
+ * on command line
172
+ *
173
+ * Invoked automatically by
174
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
175
+ * initialization
176
+ */
96
177
  options() {
97
178
  const descriptions = {
98
179
  issuer: `The OpenID ${Colors.keyword('issuer')} URL is set from the ` +
@@ -140,6 +221,13 @@ export class OAuth2Plugin {
140
221
  ]
141
222
  };
142
223
  }
224
+ /**
225
+ * Intialize plugin configuration from command line options and environment
226
+ *
227
+ * Invoked automatically by
228
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
229
+ * initialization
230
+ */
143
231
  async init(_) {
144
232
  const credentials = {};
145
233
  const base_url = this.base_url ||
@@ -157,57 +245,43 @@ export class OAuth2Plugin {
157
245
  }
158
246
  this.configure({ credentials, base_url });
159
247
  }
248
+ /**
249
+ * Instantiate the `oauth2-cli` client
250
+ *
251
+ * Available hook for custom configurations in plugin development
252
+ */
160
253
  instantiateClient(options) {
161
254
  return new OAuth2CLI.Client(options);
162
255
  }
163
- get client() {
164
- if (!this._client) {
165
- if (!this.credentials?.client_id) {
166
- throw new Error(`A ${Colors.varName(this.env.client_id)} ${Colors.keyword('must')} ` +
167
- `be configured for ${this.overrideName || this.name}.`);
168
- }
169
- if (!this.credentials?.client_secret) {
170
- throw new Error(`A ${Colors.varName(this.env.client_secret)} ${Colors.keyword('must')} ` +
171
- `be configured for ${this.overrideName || this.name}.`);
172
- }
173
- if (!this.credentials?.redirect_uri) {
174
- throw new Error(`A ${Colors.varName(this.env.redirect_uri)} ${Colors.keyword('must')} ` +
175
- `be configured for ${this.overrideName || this.name}.`);
176
- }
177
- if (!this.credentials?.issuer) {
178
- if (!this.credentials?.authorization_endpoint) {
179
- throw new Error(`Either an ${Colors.varName(this.env.issuer)} or ` +
180
- `${Colors.varName(this.env.authorization_endpoint)} ` +
181
- `${Colors.keyword('must')} be configured for ` +
182
- `${this.overrideName || this.name}.`);
183
- }
184
- if (!this.credentials?.token_endpoint) {
185
- throw new Error(`Either an ${Colors.varName(this.env.issuer)} or ` +
186
- `${Colors.varName(this.env.token_endpoint)} ` +
187
- `${Colors.keyword('must')} be configured for ` +
188
- `${this.overrideName || this.name}.`);
189
- }
190
- }
191
- this._client = this.instantiateClient({
192
- name: this.overrideName || this.name,
193
- credentials: this.credentials,
194
- base_url: this.base_url,
195
- inject: this.inject,
196
- views: this.views,
197
- storage: this.storage
198
- });
199
- }
200
- return this._client;
201
- }
256
+ /**
257
+ * Convenience method
258
+ *
259
+ * @see {@link OAuth2CLI.Client.request}
260
+ */
202
261
  request(...args) {
203
262
  return this.client.request(...args);
204
263
  }
264
+ /**
265
+ * Convenience method
266
+ *
267
+ * @see {@link OAuth2CLI.Client.requestJSON}
268
+ */
205
269
  requestJSON(...args) {
206
270
  return this.client.requestJSON(...args);
207
271
  }
272
+ /**
273
+ * Convenience method
274
+ *
275
+ * @see {@link OAuth2CLI.Client.fetch}
276
+ */
208
277
  fetch(...args) {
209
278
  return this.client.fetch(...args);
210
279
  }
280
+ /**
281
+ * Convenience method
282
+ *
283
+ * @see {@link OAuth2CLI.Client.fetchJSON}
284
+ */
211
285
  fetchJSON(...args) {
212
286
  return this.client.fetchJSON(...args);
213
287
  }
@@ -1,6 +1,20 @@
1
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
+ */
2
12
  export declare class EnvironmentStorage implements Token.Storage {
3
13
  private tokenEnvVar;
14
+ /**
15
+ * @param tokenEnvVar Name of the environment variable containing the refresh
16
+ * token, defaults to `REFRESH_TOKEN`
17
+ */
4
18
  constructor(tokenEnvVar?: string);
5
19
  load(): Promise<string | undefined>;
6
20
  save(refresh_token: string): Promise<void>;
@@ -1,6 +1,20 @@
1
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
+ */
2
12
  export class EnvironmentStorage {
3
13
  tokenEnvVar;
14
+ /**
15
+ * @param tokenEnvVar Name of the environment variable containing the refresh
16
+ * token, defaults to `REFRESH_TOKEN`
17
+ */
4
18
  constructor(tokenEnvVar = 'REFRESH_TOKEN') {
5
19
  this.tokenEnvVar = tokenEnvVar;
6
20
  }
@@ -1,4 +1,4 @@
1
- export { ClientOptions, Credentials, Injection, Scope, WebServer } from 'oauth2-cli';
1
+ export { Credentials, Injection, Localhost, Options } from 'oauth2-cli';
2
2
  export * from './Client.js';
3
3
  export * from './OAuth2Plugin.js';
4
4
  export * as Token from './Token/index.js';
@@ -1,4 +1,4 @@
1
- export { Scope, WebServer } from 'oauth2-cli';
1
+ export { Localhost } from 'oauth2-cli';
2
2
  export * from './Client.js';
3
3
  export * from './OAuth2Plugin.js';
4
4
  export * as Token from './Token/index.js';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
+ export * from './extendable.js';
1
2
  export * as OAuth2 from './OAuth2.js';
2
- export * from './Unregistered.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
+ export * from './extendable.js';
1
2
  export * as OAuth2 from './OAuth2.js';
2
- export * from './Unregistered.js';
@@ -0,0 +1,24 @@
1
+ import { JSONValue } from '@battis/typescript-tricks';
2
+ import { Request } from 'express';
3
+ import * as OAuth2CLI from 'oauth2-cli';
4
+ import type * as OpenIDClient from 'openid-client';
5
+ import * as requestish from 'requestish';
6
+ /**
7
+ * Wrap {@link https://www.npmjs.com/package/openid-client openid-client} in a
8
+ * class instance specific to a particular OAuth/OpenID server credential-set,
9
+ * abstracting away most flows into {@link getToken}
10
+ *
11
+ * Emits {@link Client.TokenEvent} whenever a new access token is received
12
+ *
13
+ * Provides optional debug logging output using
14
+ * {@link https://www.npmjs.com/package/@qui-cli/env @qui-cli/env}
15
+ */
16
+ export declare class Client<C extends OAuth2CLI.Credentials = OAuth2CLI.Credentials> extends OAuth2CLI.Client<C> {
17
+ getConfiguration(): Promise<OpenIDClient.Configuration>;
18
+ authorize(): Promise<OAuth2CLI.Token.Response>;
19
+ handleAuthorizationCodeRedirect(request: Request, session: OAuth2CLI.Localhost.Server): Promise<OAuth2CLI.Token.Response>;
20
+ protected refreshTokenGrant(token: OAuth2CLI.Token.Response): Promise<OAuth2CLI.Token.Response | undefined>;
21
+ protected save(response: OAuth2CLI.Token.Response): Promise<OAuth2CLI.Token.Response>;
22
+ request(url: requestish.URL.ish, method?: string, body?: OpenIDClient.FetchBody, headers?: requestish.Headers.ish, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<Response>;
23
+ requestJSON<J extends JSONValue = JSONValue>(url: requestish.URL.ish, method?: string, body?: OpenIDClient.FetchBody, headers?: requestish.Headers.ish, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<J>;
24
+ }
@@ -0,0 +1,71 @@
1
+ import { Log } from '@qui-cli/log';
2
+ import * as OAuth2CLI from 'oauth2-cli';
3
+ /**
4
+ * Wrap {@link https://www.npmjs.com/package/openid-client openid-client} in a
5
+ * class instance specific to a particular OAuth/OpenID server credential-set,
6
+ * abstracting away most flows into {@link getToken}
7
+ *
8
+ * Emits {@link Client.TokenEvent} whenever a new access token is received
9
+ *
10
+ * Provides optional debug logging output using
11
+ * {@link https://www.npmjs.com/package/@qui-cli/env @qui-cli/env}
12
+ */
13
+ export class Client extends OAuth2CLI.Client {
14
+ async getConfiguration() {
15
+ const creating = !this.config;
16
+ const config = await super.getConfiguration();
17
+ if (creating) {
18
+ Log.debug(`${this.name} OAuth 2.0 configuration created`, {
19
+ credentials: this.credentials,
20
+ config: {
21
+ serverMetadata: config.serverMetadata(),
22
+ clientMetadata: config.clientMetadata()
23
+ }
24
+ });
25
+ }
26
+ return config;
27
+ }
28
+ async authorize() {
29
+ Log.debug(`Authorizing ${this.name} new access token`);
30
+ const response = await super.authorize();
31
+ Log.debug(`Authorized ${this.name} new access token`, { response });
32
+ return response;
33
+ }
34
+ async handleAuthorizationCodeRedirect(request, session) {
35
+ Log.debug(`Handling ${this.name} authorization code redirect`, { request });
36
+ const response = await super.handleAuthorizationCodeRedirect(request, session);
37
+ Log.debug(`Received ${this.name} authorization code response`, {
38
+ response
39
+ });
40
+ return response;
41
+ }
42
+ async refreshTokenGrant(token) {
43
+ Log.debug(`Refreshing expired ${this.name} access token`, {
44
+ token
45
+ });
46
+ const refreshed = await super.refreshTokenGrant(token);
47
+ Log.debug(`Received refreshed ${this.name} access token`, {
48
+ token: refreshed
49
+ });
50
+ return refreshed;
51
+ }
52
+ async save(response) {
53
+ Log.debug(`Persisting ${this.name} refresh token, if present and storage configured`, {
54
+ response
55
+ });
56
+ return await super.save(response);
57
+ }
58
+ async request(url, method, body, headers, dPoPOptions) {
59
+ Log.debug(`Sending request to ${this.name}`, {
60
+ request: { method, url, headers, body, dPoPOptions }
61
+ });
62
+ const response = await super.request(url, method, body, headers, dPoPOptions);
63
+ Log.debug(`Received response from ${this.name}`, { response });
64
+ return response;
65
+ }
66
+ async requestJSON(url, method, body, headers, dPoPOptions) {
67
+ const json = await super.requestJSON(url, method, body, headers, dPoPOptions);
68
+ Log.debug(`Parsed JSON from ${this.name} response`, { json });
69
+ return json;
70
+ }
71
+ }
@@ -0,0 +1,154 @@
1
+ import { URLString } from '@battis/descriptive-types';
2
+ import { JSONValue } from '@battis/typescript-tricks';
3
+ import * as Plugin from '@qui-cli/plugin';
4
+ import * as OAuth2CLI from 'oauth2-cli';
5
+ import { URL } from 'requestish';
6
+ import { Client } from './Client.js';
7
+ /** Key for value to be read from the local environment */
8
+ type EnvironmentKey = 'issuer' | 'client_id' | 'client_secret' | 'redirect_uri' | 'authorization_endpoint' | 'token_endpoint' | 'scope' | 'base_url';
9
+ /** Names of environment variables */
10
+ type EnvironmentVars = Record<EnvironmentKey, string>;
11
+ /** Reference URLs for environment variables */
12
+ type SupportUrls = Record<EnvironmentKey, URLString>;
13
+ /** Hint values for environment variables */
14
+ type Hints = Record<EnvironmentKey, string>;
15
+ /** Environment variables to ignore and supporess in usage documentation */
16
+ type EnvVarSuppression = Record<EnvironmentKey, boolean>;
17
+ /** Custom usage information to present to user */
18
+ type Usage = {
19
+ heading: string;
20
+ text?: string[];
21
+ };
22
+ /** Plugin configuration */
23
+ export type Configuration<C extends OAuth2CLI.Credentials = OAuth2CLI.Credentials> = Plugin.Configuration & {
24
+ /** Human-readable name for client in messages (e.g. the name of the API) */
25
+ name?: string;
26
+ /**
27
+ * Human-readable for authorizing access in messages (e.g. the name of the
28
+ * app)
29
+ */
30
+ reason?: string;
31
+ /** OAuth 2.0/OpenID Connect credential set */
32
+ credentials?: Partial<C>;
33
+ /** Base URL for all non-absolute requests */
34
+ base_url?: URL.ish;
35
+ /** Request components to inject into server requests */
36
+ inject?: OAuth2CLI.Injection;
37
+ /** Refresh token storage service */
38
+ storage?: OAuth2CLI.Token.Storage;
39
+ /** CLI usage section header and text */
40
+ man?: Usage;
41
+ /** Reference URLs for particular credential values */
42
+ url?: Partial<SupportUrls>;
43
+ /** Hint values for particular credential values */
44
+ hint?: Partial<Hints>;
45
+ /** Actual environment variable names for each credential value */
46
+ env?: Partial<EnvironmentVars>;
47
+ /** Should a particular credential value _not_ be loaded from the environment? */
48
+ suppress?: Partial<EnvVarSuppression>;
49
+ localhost?: OAuth2CLI.Options['localhost'];
50
+ };
51
+ export declare class OAuth2Plugin<C extends OAuth2CLI.Credentials = OAuth2CLI.Credentials, L extends Client<C> = Client<C>> {
52
+ readonly name: string;
53
+ [key: string]: unknown;
54
+ private static names;
55
+ private static registeredPorts;
56
+ private overrideName?;
57
+ private reason?;
58
+ /**
59
+ * @param name Human-readable name for client in messages. Must also be a
60
+ * unique qui-cli plugin name.
61
+ */
62
+ constructor(name?: string);
63
+ /** Configured client credentials */
64
+ private credentials?;
65
+ /** Configured client base_url */
66
+ private base_url?;
67
+ /** Configured usage information */
68
+ private man;
69
+ /** Configured reference URLs for credentials */
70
+ private url?;
71
+ /** Configured hint values for credentials */
72
+ private hint;
73
+ /** Configured environment variable names for credentials */
74
+ private env;
75
+ /** Configured credentials to suppress in usage and init */
76
+ private suppress?;
77
+ /** Configured request components for client to inject */
78
+ private inject?;
79
+ /** Configured {@link OAuth2CLI.Localhost.Options} to pass to client */
80
+ private localhost?;
81
+ /** Configured client refresh token storage strategy */
82
+ private storage?;
83
+ private _client?;
84
+ /**
85
+ * Configured oauth2-cli client
86
+ *
87
+ * Do _not_ access until intitialization is complete -- the client will be
88
+ * configured upon first access based on available configuration known at that
89
+ * time
90
+ */
91
+ get client(): L;
92
+ /**
93
+ * Configure plugin for use
94
+ *
95
+ * May be called repeatedly, overlaying different options as they become
96
+ * available
97
+ *
98
+ * Invoked automatically by
99
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
100
+ * initialization
101
+ *
102
+ * @see {@link Configuration}
103
+ */
104
+ configure(proposal?: Configuration<C>): void;
105
+ /**
106
+ * Provide usage options to
107
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} for display to user
108
+ * on command line
109
+ *
110
+ * Invoked automatically by
111
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
112
+ * initialization
113
+ */
114
+ options(): Plugin.Options;
115
+ /**
116
+ * Intialize plugin configuration from command line options and environment
117
+ *
118
+ * Invoked automatically by
119
+ * {@link https://github.com/battis/qui-cli#readme qui-cli} during
120
+ * initialization
121
+ */
122
+ init(_: Plugin.ExpectedArguments<typeof this.options>): Promise<void>;
123
+ /**
124
+ * Instantiate the `oauth2-cli` client
125
+ *
126
+ * Available hook for custom configurations in plugin development
127
+ */
128
+ protected instantiateClient(options: OAuth2CLI.Options<C>): L;
129
+ /**
130
+ * Convenience method
131
+ *
132
+ * @see {@link OAuth2CLI.Client.request}
133
+ */
134
+ request(...args: Parameters<OAuth2CLI.Client<C>['request']>): Promise<Response>;
135
+ /**
136
+ * Convenience method
137
+ *
138
+ * @see {@link OAuth2CLI.Client.requestJSON}
139
+ */
140
+ requestJSON<T extends JSONValue>(...args: Parameters<OAuth2CLI.Client<C>['requestJSON']>): Promise<T>;
141
+ /**
142
+ * Convenience method
143
+ *
144
+ * @see {@link OAuth2CLI.Client.fetch}
145
+ */
146
+ fetch(...args: Parameters<OAuth2CLI.Client<C>['fetch']>): Promise<Response>;
147
+ /**
148
+ * Convenience method
149
+ *
150
+ * @see {@link OAuth2CLI.Client.fetchJSON}
151
+ */
152
+ fetchJSON<T extends JSONValue>(...args: Parameters<OAuth2CLI.Client<C>['fetchJSON']>): Promise<T>;
153
+ }
154
+ export {};