oauth2-cli 0.8.8 → 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.
package/dist/Client.js CHANGED
@@ -1,15 +1,11 @@
1
1
  import { Mutex } from 'async-mutex';
2
+ import * as gcrtl from 'gcrtl';
2
3
  import { EventEmitter } from 'node:events';
3
4
  import path from 'node:path';
4
5
  import * as OpenIDClient from 'openid-client';
5
- import * as requestish from 'requestish';
6
- import * as Scope from './Scope.js';
7
- import { Session } from './Session.js';
8
- /**
9
- * A generic `redirect_uri` to use if the server does not require pre-registered
10
- * `redirect_uri` values
11
- */
12
- export const DEFAULT_REDIRECT_URI = 'http://localhost:3000/oauth2-cli/redirect';
6
+ import { Body, Headers, URL, URLSearchParams } from 'requestish';
7
+ import * as Localhost from './Localhost/index.js';
8
+ import * as Token from './Token/index.js';
13
9
  /**
14
10
  * Wrap {@link https://www.npmjs.com/package/openid-client openid-client} in a
15
11
  * class instance specific to a particular OAuth/OpenID server credential-set,
@@ -19,86 +15,98 @@ export const DEFAULT_REDIRECT_URI = 'http://localhost:3000/oauth2-cli/redirect';
19
15
  */
20
16
  export class Client extends EventEmitter {
21
17
  static TokenEvent = 'token';
22
- name;
18
+ _name;
19
+ /** Human-readable name for client in messages */
20
+ get name() {
21
+ if (this._name && this._name.length > 0) {
22
+ return this._name;
23
+ }
24
+ return 'API';
25
+ }
26
+ /** Human-readable reason for authorization in messages */
27
+ reason;
28
+ /** Credentials for server access */
23
29
  credentials;
30
+ /** Base URL for all non-absolute requests */
24
31
  base_url;
32
+ /**
33
+ * `openid-client` configuration metadata (either dervied from
34
+ * {@link credentials}) or requested from the well-known OpenID configuration
35
+ * endpoint of the `issuer`
36
+ */
25
37
  config;
38
+ /** Optional request components to inject */
26
39
  inject;
27
- views;
40
+ /** Optional configuration options for web server listening for redirect */
41
+ localhostOptions;
42
+ /** Optional {@link TokenStorage} implementation to manage tokens */
43
+ storage;
44
+ /** Current response to an access token grant request, if available */
28
45
  token;
46
+ /** Mutex preventing multiple simultaneous access token grant requests */
29
47
  tokenLock = new Mutex();
30
- storage;
31
- constructor({ name, credentials, base_url, views, inject, storage }) {
48
+ /** @see {@link Options.Client} */
49
+ constructor({ name, reason, credentials, base_url, inject, storage, localhost }) {
32
50
  super();
33
- this.name = name;
51
+ this._name = name;
52
+ this.reason = reason;
34
53
  this.credentials = credentials;
35
54
  this.base_url = base_url;
36
- this.views = views;
55
+ this.localhostOptions = localhost;
37
56
  this.inject = inject;
38
57
  this.storage = storage;
39
58
  }
40
- clientName() {
41
- if (this.name && this.name.length > 0) {
42
- return this.name;
43
- }
44
- return 'oauth2-cli';
45
- }
46
- get redirect_uri() {
47
- return this.credentials.redirect_uri;
48
- }
49
59
  /**
50
- * @throws IndeterminateConfiguration if provided credentials combined with
51
- * OpenID discovery fail to generate a complete configuration
60
+ * Build a client configuration either via `issuer` discovery or from provided
61
+ * `credentials`
52
62
  */
53
63
  async getConfiguration() {
54
- let error = undefined;
64
+ let discovery = undefined;
55
65
  if (!this.config && this.credentials.issuer) {
56
66
  try {
57
- this.config = await OpenIDClient.discovery(requestish.URL.from(this.credentials.issuer), this.credentials.client_id, { client_secret: this.credentials.client_secret });
67
+ this.config = await OpenIDClient.discovery(URL.from(this.credentials.issuer), this.credentials.client_id, { client_secret: this.credentials.client_secret });
58
68
  }
59
- catch (e) {
60
- error = e;
69
+ catch (error) {
70
+ discovery = error;
61
71
  }
62
72
  }
63
73
  if (!this.config && this.credentials?.authorization_endpoint) {
64
74
  this.config = new OpenIDClient.Configuration({
65
- issuer: `https://${requestish.URL.from(this.credentials.authorization_endpoint).hostname}`,
66
- authorization_endpoint: requestish.URL.toString(this.credentials.authorization_endpoint),
67
- token_endpoint: requestish.URL.toString(this.credentials.token_endpoint ||
75
+ issuer: `https://${URL.from(this.credentials.authorization_endpoint).hostname}`,
76
+ authorization_endpoint: URL.toString(this.credentials.authorization_endpoint),
77
+ token_endpoint: URL.toString(this.credentials.token_endpoint ||
68
78
  this.credentials.authorization_endpoint)
69
79
  }, this.credentials.client_id, { client_secret: this.credentials.client_secret });
70
80
  }
71
81
  if (!this.config) {
72
- throw new Error(`The ${this.clientName()} configuration could not be constructed from provided credentials.`, {
82
+ throw new Error(`Client configuration for ${this.name} could not be discovered or derived from credentials`, {
73
83
  cause: {
74
84
  credentials: this.credentials,
75
- 'OpenID configuration result': error
85
+ discovery
76
86
  }
77
87
  });
78
88
  }
79
89
  return this.config;
80
90
  }
81
- async getParameters(session) {
82
- const params = requestish.URLSearchParams.merge(this.inject?.search, session.inject?.search) || new URLSearchParams();
83
- params.set('redirect_uri', requestish.URL.toString(this.credentials.redirect_uri));
91
+ /**
92
+ * Build a URL to redirect the user-agent to, in order to request
93
+ * authorization at the Authorization Server
94
+ *
95
+ * @param session Contains the current `state` and `code_verifier` for the
96
+ * Authorization Code flow session
97
+ */
98
+ async buildAuthorizationUrl(session) {
99
+ const params = URLSearchParams.from(this.inject?.search);
100
+ params.set('redirect_uri', URL.toString(this.credentials.redirect_uri));
84
101
  params.set('code_challenge', await OpenIDClient.calculatePKCECodeChallenge(session.code_verifier));
85
102
  params.set('code_challenge_method', 'S256');
86
103
  params.set('state', session.state);
87
104
  if (this.credentials.scope) {
88
- params.set('scope', Scope.toString(this.credentials.scope));
105
+ params.set('scope', Token.Scope.toString(this.credentials.scope));
89
106
  }
90
- return params;
91
- }
92
- async getAuthorizationUrl(session) {
93
- return OpenIDClient.buildAuthorizationUrl(await this.getConfiguration(), await this.getParameters(session));
94
- }
95
- createSession({ views, ...options }) {
96
- return new Session({
97
- client: this,
98
- views: views || this.views,
99
- ...options
100
- });
107
+ return OpenIDClient.buildAuthorizationUrl(await this.getConfiguration(), params);
101
108
  }
109
+ /** Does the client hold or have access to an unexpired API access token? */
102
110
  async isAuthorized() {
103
111
  if (this.token?.expiresIn()) {
104
112
  return true;
@@ -109,68 +117,118 @@ export class Client extends EventEmitter {
109
117
  });
110
118
  }
111
119
  }
112
- async authorize(options = {}) {
113
- const session = this.createSession(options);
120
+ /** Start interactive authorization for API access with the user */
121
+ async authorize() {
122
+ return await this.tokenLock.runExclusive(async () => {
123
+ console.log('running authorize from external call');
124
+ return await this._authorize();
125
+ });
126
+ }
127
+ /**
128
+ * Start interactive authorization for API access with the user _without_
129
+ * checking for tokenLock mutex
130
+ *
131
+ * Should be called _only_ from within a `tokenLock.runExclusive()` callback
132
+ */
133
+ async _authorize() {
134
+ const session = new Localhost.Server({
135
+ client: this,
136
+ reason: this.reason,
137
+ ...this.localhostOptions
138
+ });
114
139
  const token = await this.save(await session.authorizationCodeGrant());
115
140
  return token;
116
141
  }
117
- async handleAuthorizationCodeRedirect(req, session) {
142
+ /**
143
+ * Validate the authorization response and then executes the !"Authorization
144
+ * Code Grant" at the Authorization Server's token endpoint to obtain an
145
+ * access token. ID Token and Refresh Token are also optionally issued by the
146
+ * server.
147
+ *
148
+ * @param request Authorization Server's request to the Localhost redirect
149
+ * server
150
+ * @param session Contains the current `state` and `code_verifier` for the
151
+ * Authorization Code flow session
152
+ */
153
+ async handleAuthorizationCodeRedirect(request, session) {
118
154
  try {
119
- /**
120
- * Do _NOT_ await this promise: the WebServer needs to send the
121
- * authorization complete response asynchronously before this can resolve,
122
- * and awaiting session.resolve() will block that response.
123
- */
124
- session.resolve(await OpenIDClient.authorizationCodeGrant(await this.getConfiguration(), new URL(req.url, this.redirect_uri), {
155
+ return await OpenIDClient.authorizationCodeGrant(await this.getConfiguration(), gcrtl.expand(request.url, this.credentials.redirect_uri), {
125
156
  pkceCodeVerifier: session.code_verifier,
126
157
  expectedState: session.state
127
158
  }, this.inject?.search
128
- ? requestish.URLSearchParams.from(this.inject.search)
129
- : undefined));
159
+ ? URLSearchParams.from(this.inject.search)
160
+ : undefined);
130
161
  }
131
162
  catch (cause) {
132
- session.reject(new Error(`Error making ${this.clientName()} Authorization Code Grant request`, { cause }));
163
+ throw new Error(`${this.name} authorization code grant failed.`, {
164
+ cause
165
+ });
133
166
  }
134
167
  }
135
- async refreshTokenGrant({ refresh_token = this.token?.refresh_token, inject: request } = {}) {
168
+ /**
169
+ * Perform an OAuth 2.0 Refresh Token Grant at the Authorization Server's
170
+ * token endpoint, allowing the client to obtain a new access token using a
171
+ * valid `refresh_token`.
172
+ *
173
+ * @see {@link Options.Refresh}
174
+ */
175
+ async refreshTokenGrant({ refresh_token = this.token?.refresh_token, inject } = {}) {
136
176
  if (!refresh_token && !this.token && this.storage) {
137
177
  refresh_token = await this.storage.load();
138
178
  }
139
179
  if (!refresh_token || refresh_token === '') {
140
180
  return undefined;
141
181
  }
142
- const token = await OpenIDClient.refreshTokenGrant(await this.getConfiguration(), refresh_token, this.inject?.search
143
- ? requestish.URLSearchParams.from(this.inject.search)
144
- : undefined, {
145
- // @ts-expect-error 2322 undocumented arg pass-through to oauth4webapi
146
- headers: requestish.Headers.merge(this.headers, request?.headers)
147
- });
148
- return await this.save(token);
182
+ try {
183
+ const token = await OpenIDClient.refreshTokenGrant(await this.getConfiguration(), refresh_token, this.inject?.search
184
+ ? URLSearchParams.from(this.inject.search)
185
+ : undefined, {
186
+ // @ts-expect-error 2322 undocumented arg pass-through to oauth4webapi
187
+ headers: Headers.merge(this.headers, inject?.headers)
188
+ });
189
+ return await this.save(token);
190
+ }
191
+ catch (cause) {
192
+ throw new Error(`Could not refresh access to ${this.name}`, { cause });
193
+ }
149
194
  }
150
195
  /**
151
196
  * Get an unexpired access token
152
197
  *
153
198
  * Depending on provided and/or stored access token and refresh token values,
154
199
  * this may require interactive authorization
200
+ *
201
+ * @see {@link Options.GetToken}
155
202
  */
156
203
  async getToken({ token, inject: request } = {}) {
157
204
  return await this.tokenLock.runExclusive(async () => {
158
205
  token = token || this.token;
159
206
  if (!this.token?.expiresIn() && this.storage) {
160
- this.token = await this.refreshTokenGrant({ inject: request });
207
+ try {
208
+ this.token = await this.refreshTokenGrant({ inject: request });
209
+ }
210
+ catch (_) {
211
+ // token definitely expired and refrehing it failed
212
+ this.token = undefined;
213
+ }
161
214
  }
162
215
  if (!this.token) {
163
- this.token = await this.authorize({ inject: request });
216
+ this.token = await this._authorize();
164
217
  }
165
218
  return this.token;
166
219
  });
167
220
  }
168
- /** @throws MissingAccessToken If response does not include `access_token` */
169
- async save(token) {
170
- this.token = token;
171
- if (!token.access_token) {
172
- throw new Error(`No access_token in response to ${this.clientName()}.`, {
173
- cause: token
221
+ /**
222
+ * Persist `refresh_token` if Token.Storage is configured and `refresh_token`
223
+ * provided
224
+ *
225
+ * @throws If `response` does not include `access_token` property
226
+ */
227
+ async save(response) {
228
+ this.token = response;
229
+ if (!response.access_token) {
230
+ throw new Error(`${this.name} token response does not contain access_token`, {
231
+ cause: response
174
232
  });
175
233
  }
176
234
  if (this.storage && this.token.refresh_token) {
@@ -180,25 +238,30 @@ export class Client extends EventEmitter {
180
238
  return this.token;
181
239
  }
182
240
  /**
183
- * @param url If an `issuer` has been defined, `url` accepts paths relative to
184
- * the `issuer` URL as well as absolute URLs
241
+ * Request a protected resource using the client's access token.
242
+ *
243
+ * This ensures that the access token is unexpired, and interactively requests
244
+ * user authorization if it has not yet been provided.
245
+ *
246
+ * @param url If an `base_url` or `issuer` has been defined, `url` accepts
247
+ * paths relative to the `issuer` URL as well as absolute URLs
185
248
  * @param method Optional, defaults to `GET` unless otherwise specified
186
249
  * @param body Optional
187
250
  * @param headers Optional
188
- * @param dPoPOptions Optional
251
+ * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
189
252
  */
190
253
  async request(url, method = 'GET', body, headers = {}, dPoPOptions) {
191
254
  try {
192
- url = requestish.URL.from(url);
255
+ url = URL.from(url);
193
256
  }
194
257
  catch (error) {
195
258
  if (this.base_url || this.credentials.issuer) {
196
259
  url = path.join(
197
260
  // @ts-expect-error 2345 TS, I _just_ tested this!
198
- requestish.URL.toString(this.base_url || this.credentials.issuer), requestish.URL.toString(url).replace(/^\/?/, ''));
261
+ URL.toString(this.base_url || this.credentials.issuer), URL.toString(url).replace(/^\/?/, ''));
199
262
  }
200
263
  else {
201
- throw new Error(`Invalid request URL "${url}" to ${this.clientName()}`, {
264
+ throw new Error(`${this.name} request url invalid`, {
202
265
  cause: {
203
266
  base_url: this.base_url,
204
267
  issuer: this.credentials.issuer,
@@ -207,37 +270,35 @@ export class Client extends EventEmitter {
207
270
  });
208
271
  }
209
272
  }
210
- const request = async () => await OpenIDClient.fetchProtectedResource(await this.getConfiguration(), (await this.getToken()).access_token, requestish.URL.from(requestish.URLSearchParams.appendTo(url, this.inject?.search || {})), method, body, requestish.Headers.merge(this.inject?.headers, headers), dPoPOptions);
273
+ const request = async () => await OpenIDClient.fetchProtectedResource(await this.getConfiguration(), (await this.getToken()).access_token, URL.from(URLSearchParams.appendTo(url, this.inject?.search || {})), method, body, Headers.merge(this.inject?.headers, headers), dPoPOptions);
211
274
  try {
212
275
  return await request();
213
276
  }
214
- catch (error) {
215
- if (typeof error === 'object' &&
216
- error !== null &&
217
- 'status' in error &&
218
- error.status === 401) {
277
+ catch (cause) {
278
+ if (Error.isError(cause) && 'status' in cause && cause.status === 401) {
219
279
  await this.authorize();
220
280
  return await request();
221
281
  }
222
282
  else {
223
- throw error;
283
+ throw new Error(`${this.name} request failed`, { cause });
224
284
  }
225
285
  }
226
286
  }
287
+ /** Parse a fetch response as JSON, typing it as J */
227
288
  async toJSON(response) {
228
289
  if (response.ok) {
229
- return (await response.json());
290
+ try {
291
+ return (await response.json());
292
+ }
293
+ catch (cause) {
294
+ throw new Error(`${this.name} response could not be parsed as JSON`, {
295
+ cause
296
+ });
297
+ }
230
298
  }
231
299
  else {
232
- throw new Error(`The response could not be parsed as JSON by ${this.clientName()}.`, {
233
- cause: {
234
- response: {
235
- status: response.status,
236
- statusText: response.statusText,
237
- headers: Object.fromEntries(response.headers?.entries() || []),
238
- body: response.text ? await response.text() : response.body
239
- }
240
- }
300
+ throw new Error(`${this.name} response status not ok`, {
301
+ cause: { response }
241
302
  });
242
303
  }
243
304
  }
@@ -248,9 +309,25 @@ export class Client extends EventEmitter {
248
309
  async requestJSON(url, method = 'GET', body, headers = {}, dPoPOptions) {
249
310
  return await this.toJSON(await this.request(url, method, body, headers, dPoPOptions));
250
311
  }
312
+ /**
313
+ * Request a protected resource using the client's access token.
314
+ *
315
+ * This ensures that the access token is unexpired, and interactively requests
316
+ * user authorization if it has not yet been provided.
317
+ *
318
+ * @param input If a `base_url` or `issuer` has been defined, `url` accepts
319
+ * paths relative to the `issuer` URL as well as absolute URLs
320
+ * @param init Optional
321
+ * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
322
+ * @see {@link request} for which this is an alias for {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Fetch API}-style requests
323
+ */
251
324
  async fetch(input, init, dPoPOptions) {
252
- return await this.request(input, init?.method, await requestish.Body.from(init?.body), requestish.Headers.from(init?.headers), dPoPOptions);
325
+ return await this.request(input, init?.method, await Body.from(init?.body), Headers.from(init?.headers), dPoPOptions);
253
326
  }
327
+ /**
328
+ * Returns the result of {@link fetch} as a parsed JSON object, optionally
329
+ * typed as `J`
330
+ */
254
331
  async fetchJSON(input, init, dPoPOptions) {
255
332
  return await this.toJSON(await this.fetch(input, init, dPoPOptions));
256
333
  }
@@ -1,16 +1,48 @@
1
- import * as requestish from 'requestish';
2
- import * as Scope from './Scope.js';
1
+ import { URL } from 'requestish';
2
+ import { Scope } from './Token/index.js';
3
3
  export type Credentials = {
4
+ /** OAuth 2.0 / OpenID Connect `client_id` value */
4
5
  client_id: string;
6
+ /** OAuth 2.0 / OpenID Connect `client_secret` value */
5
7
  client_secret: string;
6
- redirect_uri: requestish.URL.ish;
8
+ /**
9
+ * OAuth 2.0 / OpenID Connect `redirect_uri` value
10
+ *
11
+ * This must either be a URL of the form `http://localhost` or redirect to
12
+ * `http://localhost` for the client to be able to provide support for it. If
13
+ * SSL encryption is required, you must provide that configuration
14
+ * independently.
15
+ */
16
+ redirect_uri: URL.ish;
17
+ /** OAuth 2.0 / OpenID Connect access `scope` value */
7
18
  scope?: Scope.ish;
8
19
  } & ({
9
- issuer?: requestish.URL.ish;
10
- authorization_endpoint: requestish.URL.ish;
11
- token_endpoint: requestish.URL.ish;
20
+ /**
21
+ * Optional OpenID Connect `issuer` to use for the well-known URL
22
+ * discovery process
23
+ */
24
+ issuer?: URL.ish;
25
+ /**
26
+ * OAuth 2.0 `authorization_endpoint` URL to for during Authorization Code
27
+ * flow
28
+ */
29
+ authorization_endpoint: URL.ish;
30
+ /**
31
+ * OAuth 2.0 `authorization_endpoint` URL to for during Authorization Code
32
+ * flow
33
+ */
34
+ token_endpoint: URL.ish;
12
35
  } | {
13
- issuer: requestish.URL.ish;
14
- authorization_endpoint?: requestish.URL.ish;
15
- token_endpoint?: requestish.URL.ish;
36
+ /** OpenID Connect `issuer` to use for the well-known URL discovery process */
37
+ issuer: URL.ish;
38
+ /**
39
+ * Optional OAuth 2.0 `authorization_endpoint` URL to for during
40
+ * Authorization Code flow
41
+ */
42
+ authorization_endpoint?: URL.ish;
43
+ /**
44
+ * Optional OAuth 2.0 `authorization_endpoint` URL to for during
45
+ * Authorization Code flow
46
+ */
47
+ token_endpoint?: URL.ish;
16
48
  });
@@ -1,21 +1,22 @@
1
- import * as requestish from 'requestish';
2
- import * as Scope from './Scope.js';
1
+ import { Body, Headers, URLSearchParams } from 'requestish';
2
+ import { Scope } from './Token/index.js';
3
+ /** Request components to inject when communicating with the API server */
3
4
  export type Injection = {
4
5
  /**
5
6
  * Search query parameters to include in server request (may be ovewritten by
6
7
  * computed values such as `state` or `challenge_code`)
7
8
  */
8
- search?: requestish.URLSearchParams.ish;
9
+ search?: URLSearchParams.ish;
9
10
  /**
10
11
  * HTTP headers to include in server request (may be overwritten by computed
11
12
  * values such as `Authorization: Bearer <token>`)
12
13
  */
13
- headers?: requestish.Headers.ish;
14
+ headers?: Headers.ish;
14
15
  /**
15
16
  * HTTP request body parameters to include in server request (if request
16
17
  * method allows)
17
18
  */
18
- body?: requestish.Body.ish;
19
+ body?: Body.ish;
19
20
  /** Specific scope or scopes */
20
21
  scope?: Scope.ish;
21
22
  };
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_LAUNCH_ENDPOINT = "/oauth2-cli/authorize";
@@ -0,0 +1 @@
1
+ export const DEFAULT_LAUNCH_ENDPOINT = '/oauth2-cli/authorize';
@@ -0,0 +1,28 @@
1
+ import { PathString } from '@battis/descriptive-types';
2
+ import { Client } from '../index.js';
3
+ export type Options = {
4
+ client: Client;
5
+ /** Human-readable name of the app requesting API authorization */
6
+ reason?: string;
7
+ /** See {@link Session.setViews setViews()} */
8
+ views?: PathString;
9
+ /**
10
+ * Local web server launch endpoint
11
+ *
12
+ * This is separate and distinct from the OpenID/OAuth server's authorization
13
+ * endpoint. This endpoint is the first path that the user is directed to in
14
+ * their browser. It can present an explanation of what is being authorized
15
+ * and why. By default it redirects to the OpenID/OAuth server's authorization
16
+ * URL, the first step in the Authorization Code Grant flow.
17
+ */
18
+ launch_endpoint?: PathString;
19
+ /**
20
+ * The number of milliseconds of inactivity before a socket is presumed to
21
+ * have timed out. This can be reduced to limit potential wait times during
22
+ * interactive authentication, but must still be long enough to allow time for
23
+ * the authorization code to be exchanged for an access token.
24
+ *
25
+ * Defaults to 1000 milliseconds
26
+ */
27
+ timeout?: number;
28
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { PathString } from '@battis/descriptive-types';
2
+ import { Request, Response } from 'express';
3
+ import * as Token from '../Token/index.js';
4
+ import { Options } from './Options.js';
5
+ /**
6
+ * Minimal HTTP server running on localhost to handle the redirect step of
7
+ * OpenID/OAuth flows
8
+ */
9
+ export declare class Server {
10
+ private static activePorts;
11
+ /** PKCE code_verifier */
12
+ readonly code_verifier: string;
13
+ /** OAuth 2.0 state (if PKCE is not supported) */
14
+ readonly state: string;
15
+ private client;
16
+ private reason;
17
+ private views?;
18
+ private packageViews;
19
+ private spinner;
20
+ protected readonly port: string;
21
+ readonly launch_endpoint: PathString;
22
+ private server;
23
+ private resolveAuthorizationCodeFlow?;
24
+ private rejectAuthorizationCodeFlow?;
25
+ constructor({ reason, client, views, launch_endpoint, timeout }: Options);
26
+ authorizationCodeGrant(): Promise<Token.Response>;
27
+ /**
28
+ * Set the path to folder of *.ejs templates
29
+ *
30
+ * Expected templates include:
31
+ *
32
+ * - `launch.ejs` presents information prior to the authorization to the user,
33
+ * and the user must follow `authorize_url` data property to interactively
34
+ * launch authorization
35
+ * - `complete.ejs` presented to user upon successful completion of
36
+ * authorization flow
37
+ * - `error.ejs` presented to user upon receipt of an error from the server,
38
+ * includes `error` as data
39
+ *
40
+ * `complete.ejs` and `error.ejs` are included with oauth2-cli and those
41
+ * templates will be used if `ejs` is imported but no replacement templates
42
+ * are found.
43
+ *
44
+ * All views receive a data property `name` which is the human-readable name
45
+ * of the Client managing the authorization code flow, and `reason` indicating
46
+ * the human-readable reason (i.e. name of the app) requesting authorization,
47
+ * which should be displayed in messages for transparency.
48
+ *
49
+ * @param views Should be an absolute path
50
+ */
51
+ setViews(views: PathString): void;
52
+ protected render(res: Response, template: string, data?: Record<string, unknown>): Promise<boolean>;
53
+ /** Handles request to `/authorize` */
54
+ protected handleAuthorizationEndpoint(req: Request, res: Response): Promise<void>;
55
+ /** Handles request to `redirect_uri` */
56
+ protected handleRedirect(req: Request, res: Response): Promise<void>;
57
+ /** Shut down web server */
58
+ close(): Promise<void>;
59
+ }