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.
@@ -0,0 +1,175 @@
1
+ import { Colors } from '@qui-cli/colors';
2
+ import express from 'express';
3
+ import * as gcrtl from 'gcrtl';
4
+ import fs from 'node:fs';
5
+ import open from 'open';
6
+ import * as OpenIDClient from 'openid-client';
7
+ import ora from 'ora';
8
+ import path from 'path';
9
+ import { URL } from 'requestish';
10
+ import { DEFAULT_LAUNCH_ENDPOINT } from './Defaults.js';
11
+ let ejs = undefined;
12
+ try {
13
+ ejs = (await import('ejs')).default;
14
+ }
15
+ catch (_) {
16
+ // ejs peer dependency not installed
17
+ }
18
+ /**
19
+ * Minimal HTTP server running on localhost to handle the redirect step of
20
+ * OpenID/OAuth flows
21
+ */
22
+ export class Server {
23
+ static activePorts = [];
24
+ /** PKCE code_verifier */
25
+ code_verifier = OpenIDClient.randomPKCECodeVerifier();
26
+ /** OAuth 2.0 state (if PKCE is not supported) */
27
+ state = OpenIDClient.randomState();
28
+ client;
29
+ reason = 'an unnamed oauth2-cli app';
30
+ views;
31
+ packageViews = '../../views';
32
+ spinner;
33
+ port;
34
+ launch_endpoint;
35
+ server;
36
+ resolveAuthorizationCodeFlow = undefined;
37
+ rejectAuthorizationCodeFlow = undefined;
38
+ constructor({ reason, client, views, launch_endpoint = DEFAULT_LAUNCH_ENDPOINT, timeout = 1000 // milliseconds
39
+ }) {
40
+ this.client = client;
41
+ this.reason = reason || this.reason;
42
+ this.spinner = ora(`Awaiting interactive authorization for ${this.client.name} access by ${this.reason}`).start();
43
+ this.launch_endpoint = launch_endpoint;
44
+ this.views = views;
45
+ const url = URL.from(this.client.credentials.redirect_uri);
46
+ this.port = url.port;
47
+ if (Server.activePorts.includes(this.port)) {
48
+ throw new Error(`Another process is already running at http://localhost:${url.port}.`, { cause: { activePorts: Server.activePorts } });
49
+ }
50
+ Server.activePorts.push(this.port);
51
+ const app = express();
52
+ app.get(this.launch_endpoint, this.handleAuthorizationEndpoint.bind(this));
53
+ app.get(gcrtl.path(url), this.handleRedirect.bind(this));
54
+ this.server = app.listen(gcrtl.port(url));
55
+ this.server.timeout = timeout;
56
+ this.server.keepAliveTimeout = 0;
57
+ this.server.keepAliveTimeoutBuffer = 0;
58
+ }
59
+ async authorizationCodeGrant() {
60
+ const response = new Promise((resolve, reject) => {
61
+ this.resolveAuthorizationCodeFlow = resolve;
62
+ this.rejectAuthorizationCodeFlow = reject;
63
+ });
64
+ const url = URL.toString(gcrtl.expand(this.launch_endpoint, this.client.credentials.redirect_uri));
65
+ open(url, { wait: false });
66
+ this.spinner.text = `Please continue interactive authorization of ${this.client.name} for ${this.reason} at ${Colors.url(url)} in your browser`;
67
+ await response;
68
+ this.spinner.text = `Waiting for ${this.client.name} localhost redirect server to shut down`;
69
+ await this.close();
70
+ return response;
71
+ }
72
+ /**
73
+ * Set the path to folder of *.ejs templates
74
+ *
75
+ * Expected templates include:
76
+ *
77
+ * - `launch.ejs` presents information prior to the authorization to the user,
78
+ * and the user must follow `authorize_url` data property to interactively
79
+ * launch authorization
80
+ * - `complete.ejs` presented to user upon successful completion of
81
+ * authorization flow
82
+ * - `error.ejs` presented to user upon receipt of an error from the server,
83
+ * includes `error` as data
84
+ *
85
+ * `complete.ejs` and `error.ejs` are included with oauth2-cli and those
86
+ * templates will be used if `ejs` is imported but no replacement templates
87
+ * are found.
88
+ *
89
+ * All views receive a data property `name` which is the human-readable name
90
+ * of the Client managing the authorization code flow, and `reason` indicating
91
+ * the human-readable reason (i.e. name of the app) requesting authorization,
92
+ * which should be displayed in messages for transparency.
93
+ *
94
+ * @param views Should be an absolute path
95
+ */
96
+ setViews(views) {
97
+ this.views = views;
98
+ }
99
+ async render(res, template, data = {}) {
100
+ const name = this.client.name;
101
+ const reason = this.reason;
102
+ async function attemptToRender(views) {
103
+ if (ejs && views) {
104
+ const viewPath = path.resolve(import.meta.dirname, views, template);
105
+ if (fs.existsSync(viewPath)) {
106
+ res.send(await ejs.renderFile(viewPath, {
107
+ name,
108
+ reason,
109
+ ...data
110
+ }));
111
+ return true;
112
+ }
113
+ }
114
+ return false;
115
+ }
116
+ return ((await attemptToRender(this.views)) ||
117
+ (await attemptToRender(this.packageViews)));
118
+ }
119
+ /** Handles request to `/authorize` */
120
+ async handleAuthorizationEndpoint(req, res) {
121
+ const authorization_url = URL.toString(await this.client.buildAuthorizationUrl(this));
122
+ if (!(await this.render(res, 'launch.ejs', { authorization_url }))) {
123
+ res.redirect(authorization_url);
124
+ res.end();
125
+ }
126
+ }
127
+ /** Handles request to `redirect_uri` */
128
+ async handleRedirect(req, res) {
129
+ this.spinner.text = `Exchanging authorization code for ${this.client.name} access token for ${this.reason}`;
130
+ try {
131
+ if (this.resolveAuthorizationCodeFlow) {
132
+ this.resolveAuthorizationCodeFlow(await this.client.handleAuthorizationCodeRedirect(req, this));
133
+ this.spinner.text = `${this.client.name} authorization complete and access token received for ${this.reason}`;
134
+ if (!(await this.render(res, 'complete.ejs'))) {
135
+ res.send(`${this.client.name} authorization complete for ${this.reason}. You may close this window.`);
136
+ }
137
+ }
138
+ else {
139
+ throw new Error(`${this.client.name} localhost redirect session resolver is ${this.resolveAuthorizationCodeFlow}`);
140
+ }
141
+ }
142
+ catch (error) {
143
+ if (this.rejectAuthorizationCodeFlow) {
144
+ this.rejectAuthorizationCodeFlow(new Error(`${this.client.name} localhost server could not handle redirect`, {
145
+ cause: error
146
+ }));
147
+ }
148
+ if (!(await this.render(res, 'error.ejs', { error }))) {
149
+ res.send({
150
+ client: this.client.name,
151
+ error
152
+ });
153
+ }
154
+ }
155
+ }
156
+ /** Shut down web server */
157
+ async close() {
158
+ return new Promise((resolve, reject) => {
159
+ this.server.close((cause) => {
160
+ if (cause) {
161
+ const message = `Error shutting down ${this.client.name} localhost redirect server`;
162
+ this.spinner.fail(`Error shutting down ${this.client.name} localhost redirect server`);
163
+ reject(new Error(message, {
164
+ cause
165
+ }));
166
+ }
167
+ else {
168
+ Server.activePorts.splice(Server.activePorts.indexOf(this.port), 1);
169
+ this.spinner.succeed(`${this.client.name} authorization complete for ${this.reason}`);
170
+ resolve();
171
+ }
172
+ });
173
+ });
174
+ }
175
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Defaults.js';
2
+ export * from './Options.js';
3
+ export * from './Server.js';
@@ -0,0 +1,3 @@
1
+ export * from './Defaults.js';
2
+ export * from './Options.js';
3
+ export * from './Server.js';
@@ -0,0 +1,54 @@
1
+ import { URL } from 'requestish';
2
+ import { Credentials } from './Credentials.js';
3
+ import { Injection } from './Injection.js';
4
+ import * as Localhost from './Localhost/index.js';
5
+ import * as Token from './Token/index.js';
6
+ /** For use only within the Client implementation */
7
+ export type LocalhostOptions = Omit<Localhost.Options, 'client' | 'reason'>;
8
+ export type Client<C extends Credentials = Credentials> = {
9
+ /** Human-readable name for client in messages (e.g. the name of the API) */
10
+ name?: string;
11
+ /**
12
+ * Human-readable reason for authorizing access in messages (e.g. the app
13
+ * name)
14
+ */
15
+ reason?: string;
16
+ /** Credentials for server access */
17
+ credentials: C;
18
+ /** Optional request components to inject */
19
+ inject?: Injection;
20
+ /** Base URL for all non-absolute requests */
21
+ base_url?: URL.ish;
22
+ /** Optional {@link TokenStorage} implementation to manage tokens */
23
+ storage?: Token.Storage;
24
+ /**
25
+ * Optional configuration for localhost web server listening for authorization
26
+ * code redirect
27
+ */
28
+ localhost?: LocalhostOptions;
29
+ };
30
+ export type Refresh = {
31
+ /**
32
+ * Optional refresh token
33
+ *
34
+ * If using {@link TokenStorage}, the refresh token should be stored with the
35
+ * access token and does not need to be separately managed and stored
36
+ */
37
+ refresh_token?: string;
38
+ /** Additional request injection for refresh grant flow */
39
+ inject?: Injection;
40
+ };
41
+ export type GetToken = {
42
+ /**
43
+ * Optional access token
44
+ *
45
+ * If using {@link TokenStorage}, the access token does not need to be
46
+ * separately managed and stored
47
+ */
48
+ token?: Token.Response;
49
+ /**
50
+ * Additional request injection for authorization code grant and/or refresh
51
+ * grant flows
52
+ */
53
+ inject?: Injection;
54
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,22 @@
1
1
  import { Storage } from './Storage.js';
2
+ /**
3
+ * Save a refresh token to a specific file
4
+ *
5
+ * Care _must_ be taken when using this persistence strategy that:
6
+ *
7
+ * 1. The token file is not publicly accessible
8
+ * 2. The token file is _not_ commited to a public repository
9
+ *
10
+ * This strategy is appropriate only in very limited set of use cases.
11
+ */
2
12
  export declare class FileStorage implements Storage {
13
+ /** Prevent multiple simultaneous, conflicting file accesses */
3
14
  private fileLock;
4
15
  private readonly filePath;
16
+ /**
17
+ * @param filePath The path to the token file (optionally relative to the
18
+ * current working directory)
19
+ */
5
20
  constructor(filePath: string);
6
21
  load(): Promise<string | undefined>;
7
22
  save(refresh_token: string): Promise<void>;
@@ -1,9 +1,24 @@
1
1
  import { Mutex } from 'async-mutex';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ /**
5
+ * Save a refresh token to a specific file
6
+ *
7
+ * Care _must_ be taken when using this persistence strategy that:
8
+ *
9
+ * 1. The token file is not publicly accessible
10
+ * 2. The token file is _not_ commited to a public repository
11
+ *
12
+ * This strategy is appropriate only in very limited set of use cases.
13
+ */
4
14
  export class FileStorage {
15
+ /** Prevent multiple simultaneous, conflicting file accesses */
5
16
  fileLock = new Mutex();
6
17
  filePath;
18
+ /**
19
+ * @param filePath The path to the token file (optionally relative to the
20
+ * current working directory)
21
+ */
7
22
  constructor(filePath) {
8
23
  this.filePath = path.resolve(process.cwd(), filePath);
9
24
  }
@@ -1,2 +1,3 @@
1
1
  import * as OpenIDClient from 'openid-client';
2
+ /** @see {@link https://github.com/panva/openid-client/blob/79386b7f120dc9bdc7606886a26e4ea7d011ee51/src/index.ts#L4268 `openid-client` grant request return type} */
2
3
  export type Response = OpenIDClient.TokenEndpointResponse & OpenIDClient.TokenEndpointResponseHelpers;
@@ -1,4 +1,7 @@
1
+ /** Persistent refresh token storage */
1
2
  export interface Storage {
3
+ /** Load the stored refresh token from persistent storage, if present */
2
4
  load(): Promise<string | undefined>;
5
+ /** Save a refresh token to persistent storage */
3
6
  save(refresh_token: string): Promise<void>;
4
7
  }
@@ -1,3 +1,4 @@
1
1
  export * from './FileStorage.js';
2
2
  export * from './Response.js';
3
+ export * as Scope from './Scope.js';
3
4
  export * from './Storage.js';
@@ -1,3 +1,4 @@
1
1
  export * from './FileStorage.js';
2
2
  export * from './Response.js';
3
+ export * as Scope from './Scope.js';
3
4
  export * from './Storage.js';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './Client.js';
2
2
  export * from './Credentials.js';
3
3
  export * from './Injection.js';
4
- export * as Scope from './Scope.js';
4
+ export * as Localhost from './Localhost/index.js';
5
+ export { Client as Options } from './Options.js';
5
6
  export * as Token from './Token/index.js';
6
- export * from './WebServer.js';
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from './Client.js';
2
2
  export * from './Credentials.js';
3
3
  export * from './Injection.js';
4
- export * as Scope from './Scope.js';
4
+ export * as Localhost from './Localhost/index.js';
5
5
  export * as Token from './Token/index.js';
6
- export * from './WebServer.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oauth2-cli",
3
- "version": "0.8.8",
4
- "description": "Acquire API access tokens via OAuth 2.0 within CLI tools",
3
+ "version": "1.0.0",
4
+ "description": "Acquire API access tokens via OAuth 2.0 / OpenID Connect within CLI tools",
5
5
  "homepage": "https://github.com/battis/oauth2-cli/tree/main/packages/oauth2-cli#readme",
6
6
  "repository": {
7
7
  "type": "git",
@@ -20,10 +20,11 @@
20
20
  "async-mutex": "^0.5.0",
21
21
  "express": "^5.2.1",
22
22
  "oauth4webapi": "^3.8.5",
23
+ "open": "^11.0.0",
23
24
  "openid-client": "^6.8.2",
24
25
  "ora": "^9.3.0",
25
- "gcrtl": "0.1.7",
26
- "requestish": "0.1.1"
26
+ "requestish": "0.1.3",
27
+ "gcrtl": "0.1.7"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@battis/descriptive-types": "^0.2.6",
@@ -10,37 +10,37 @@
10
10
  integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
11
11
  crossorigin="anonymous"
12
12
  />
13
- <style>
14
- .wrapper {
15
- display: grid;
16
- grid-template-columns: 1fr auto 1fr;
17
- grid-template-rows: 1fr auto 1.61803fr;
18
- position: absolute;
19
- width: 100vw;
20
- height: 100vh;
21
- top: 0;
22
- left: 0;
23
- }
24
- .center {
25
- grid-row: 2;
26
- grid-column: 2;
27
- }
28
- </style>
29
13
  </head>
30
14
  <body>
31
- <div class="wrapper">
32
- <div class="center container">
33
- <h1>
34
- <%= name %>
35
- Authorization Complete
36
- </h1>
37
- <p>You may close this window.</p>
15
+ <div
16
+ class="modal fade"
17
+ id="modal"
18
+ data-bs-backdrop="static"
19
+ data-bs-keyboard="false"
20
+ tabindex="-1"
21
+ aria-hidden="true"
22
+ >
23
+ <div class="modal-dialog">
24
+ <div class="modal-content">
25
+ <div class="modal-header">
26
+ <h1 class="modal-title fs-5"><%= name %> Authorization Complete</h1>
27
+ </div>
28
+ <div class="modal-body">
29
+ You have authorized access to <%= name %> for <%= reason %> using
30
+ your credentials.
31
+ </div>
32
+ <div class="modal-footer">You may close this window.</div>
33
+ </div>
38
34
  </div>
39
35
  </div>
36
+
40
37
  <script
41
38
  src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
42
39
  integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
43
40
  crossorigin="anonymous"
44
41
  ></script>
42
+ <script>
43
+ bootstrap.Modal.getOrCreateInstance('#modal').show();
44
+ </script>
45
45
  </body>
46
46
  </html>
package/views/error.ejs CHANGED
@@ -10,34 +10,35 @@
10
10
  integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
11
11
  crossorigin="anonymous"
12
12
  />
13
- <style>
14
- .wrapper {
15
- display: grid;
16
- grid-template-columns: 1fr auto 1fr;
17
- grid-template-rows: 1fr auto 1.61803fr;
18
- position: absolute;
19
- width: 100vw;
20
- height: 100vh;
21
- top: 0;
22
- left: 0;
23
- }
24
- .center {
25
- grid-row: 2;
26
- grid-column: 2;
27
- }
28
- </style>
29
13
  </head>
30
14
  <body>
31
- <div class="wrapper">
32
- <div class="center container">
33
- <h1>
34
- <%= name %>
35
- Authorization Error
36
- </h1>
37
- <div class="alert alert-danger" role="alert">
38
- <pre><%= JSON.stringify(error, null, 2) %></pre>
15
+ <div
16
+ class="modal fade"
17
+ id="modal"
18
+ data-bs-backdrop="static"
19
+ data-bs-keyboard="false"
20
+ tabindex="-1"
21
+ aria-hidden="true"
22
+ >
23
+ <div class="modal-dialog">
24
+ <div class="modal-content">
25
+ <div class="modal-header">
26
+ <h1 class="modal-title fs-5"><%= name %> Authorization Error</h1>
27
+ </div>
28
+ <div class="modal-body">
29
+ <p>
30
+ While authorizing access to
31
+ <%= name %>
32
+ for
33
+ <%= reason %>
34
+ an error was encountered:
35
+ </p>
36
+ <div class="alert alert-danger" role="alert">
37
+ <pre><%= JSON.stringify(error, null, 2) %></pre>
38
+ </div>
39
+ </div>
40
+ <div class="modal-footer">You may close this window.</div>
39
41
  </div>
40
- <p>You may close this window.</p>
41
42
  </div>
42
43
  </div>
43
44
  <script
@@ -45,5 +46,8 @@
45
46
  integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
46
47
  crossorigin="anonymous"
47
48
  ></script>
49
+ <script>
50
+ bootstrap.Modal.getOrCreateInstance('#modal').show();
51
+ </script>
48
52
  </body>
49
53
  </html>
package/dist/Session.d.ts DELETED
@@ -1,49 +0,0 @@
1
- import { PathString } from '@battis/descriptive-types';
2
- import { Request } from 'express';
3
- import { Client } from './Client.js';
4
- import { Injection } from './Injection.js';
5
- import * as Token from './Token/index.js';
6
- import * as WebServer from './WebServer.js';
7
- export type SessionOptions = {
8
- client: Client;
9
- /** See {@link WebServer.setViews Webserver.setViews()} */
10
- views?: PathString;
11
- /** Additional request injection for authorization code grant flow */
12
- inject?: Injection;
13
- };
14
- export type SessionResolver = (response: Token.Response) => void | Promise<void>;
15
- export declare class Session {
16
- readonly client: Client;
17
- private readonly outOfBandRedirectServer;
18
- /** PKCE code_verifier */
19
- readonly code_verifier: string;
20
- /** OAuth 2.0 state (if PKCE is not supported) */
21
- readonly state: string;
22
- /** Additional request injection for Authorization Code Grant request */
23
- readonly inject?: Injection;
24
- private spinner;
25
- private _resolve?;
26
- /**
27
- * Method that resolves or rejects the promise returned from the
28
- * {@link authorizationCodeGrant}
29
- */
30
- get resolve(): SessionResolver;
31
- reject(cause: unknown): void;
32
- constructor({ client, views, inject: request }: SessionOptions);
33
- /** Instantiate the web server that will listen for the out-of-band redirect */
34
- protected instantiateWebServer(options: Omit<WebServer.WebServerOptions, 'session'>): WebServer.WebServerInterface;
35
- /**
36
- * Trigger the start of the Authorization Code Grant flow, returnig a Promise
37
- * that will resolve into the eventual token. This will close the out-of-band
38
- * redirect server that creating the session started.
39
- */
40
- authorizationCodeGrant(): Promise<Token.Response>;
41
- /** OAuth 2.0 redirect_uri that this session is handling */
42
- get redirect_uri(): import("requestish/dist/URL.js").ish;
43
- getAuthorizationUrl(): Promise<string>;
44
- /**
45
- * Express RequestHandler for the out-of-band redirect in the Authorization
46
- * Code Grant flow
47
- */
48
- handleAuthorizationCodeRedirect(req: Request): Promise<void>;
49
- }
package/dist/Session.js DELETED
@@ -1,111 +0,0 @@
1
- import { Colors } from '@qui-cli/colors';
2
- import * as gcrtl from 'gcrtl';
3
- import * as OpenIDClient from 'openid-client';
4
- import ora from 'ora';
5
- import * as WebServer from './WebServer.js';
6
- export class Session {
7
- client;
8
- outOfBandRedirectServer;
9
- /** PKCE code_verifier */
10
- code_verifier = OpenIDClient.randomPKCECodeVerifier();
11
- /** OAuth 2.0 state (if PKCE is not supported) */
12
- state = OpenIDClient.randomState();
13
- /** Additional request injection for Authorization Code Grant request */
14
- inject;
15
- spinner;
16
- _resolve;
17
- /**
18
- * Method that resolves or rejects the promise returned from the
19
- * {@link authorizationCodeGrant}
20
- */
21
- get resolve() {
22
- if (!this._resolve) {
23
- throw new Error(`${this.client.clientName()}'s Session resolve method is ${this._resolve}`);
24
- }
25
- return this._resolve;
26
- }
27
- reject(cause) {
28
- throw new Error(`${this.client.clientName()}'s Session failed`, { cause });
29
- }
30
- constructor({ client, views, inject: request }) {
31
- this.client = client;
32
- this.spinner = ora(`${this.client.clientName()} awaiting interactive authorization`).start();
33
- this.inject = request;
34
- this.outOfBandRedirectServer = this.instantiateWebServer({ views });
35
- }
36
- /** Instantiate the web server that will listen for the out-of-band redirect */
37
- instantiateWebServer(options) {
38
- return new WebServer.WebServer({ session: this, ...options });
39
- }
40
- /**
41
- * Trigger the start of the Authorization Code Grant flow, returnig a Promise
42
- * that will resolve into the eventual token. This will close the out-of-band
43
- * redirect server that creating the session started.
44
- */
45
- async authorizationCodeGrant() {
46
- return await new Promise((resolve, reject) => {
47
- try {
48
- this._resolve = (response) => {
49
- let closed = false;
50
- this.spinner.text = `${this.client.clientName()} waiting for out-of-band redirect server to shut down`;
51
- this.outOfBandRedirectServer.close().then(() => {
52
- closed = true;
53
- this.spinner.succeed(`Interactive authorization for ${this.client.clientName()} complete`);
54
- resolve(response);
55
- });
56
- setTimeout(() => {
57
- if (!closed) {
58
- this.spinner.text =
59
- `Still waiting for out-of-band redirect server for ${this.client.clientName()} to shut down.\n` +
60
- ' Your browser may be holding the connection to the server open.\n\n' +
61
- ' Please close the "Authorization Complete" tab in your browser.';
62
- }
63
- }, 5000);
64
- setTimeout(() => {
65
- if (!closed) {
66
- this.spinner.text =
67
- `Still waiting for out-of-band redirect server for ${this.client.clientName()} to shut down.\n` +
68
- ' Your browser may be holding the connection to the server open.\n\n' +
69
- ' Please close the browser window.';
70
- }
71
- }, 10000);
72
- setTimeout(() => {
73
- if (!closed) {
74
- this.spinner.text =
75
- `Still waiting for out-of-band redirect server for ${this.client.clientName()} to shut down.\n` +
76
- ' Your browser may be holding the connection to the server open.\n\n' +
77
- ' Please quit the browser.';
78
- }
79
- }, 15000);
80
- };
81
- const url = gcrtl
82
- .expand(this.outOfBandRedirectServer.authorization_endpoint, this.client.redirect_uri)
83
- .toString();
84
- //open(url);
85
- this.spinner.text = `Please continue interactive authorization for ${this.client.clientName()} at ${Colors.url(url)} in your browser`;
86
- }
87
- catch (cause) {
88
- this.spinner.text = `Waiting for out-of-band redirect server for ${this.client.clientName()} to shut down`;
89
- this.outOfBandRedirectServer.close().then(() => {
90
- this.spinner.fail(`Interactive authorization for ${this.client.clientName()} failed`);
91
- reject(new Error(`Error in Authorization Code flow for ${this.client.clientName()}`, { cause }));
92
- });
93
- }
94
- });
95
- }
96
- /** OAuth 2.0 redirect_uri that this session is handling */
97
- get redirect_uri() {
98
- return this.client.redirect_uri;
99
- }
100
- async getAuthorizationUrl() {
101
- return (await this.client.getAuthorizationUrl(this)).toString();
102
- }
103
- /**
104
- * Express RequestHandler for the out-of-band redirect in the Authorization
105
- * Code Grant flow
106
- */
107
- async handleAuthorizationCodeRedirect(req) {
108
- this.spinner.text = `Completing access token request fro ${this.client.clientName()} with provided authorization code`;
109
- return await this.client.handleAuthorizationCodeRedirect(req, this);
110
- }
111
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Reaching into the bowels of openid-client to be able to fully de-serialize a
3
- * serialized token response
4
- */
5
- import * as oauth from 'oauth4webapi';
6
- import { TokenEndpointResponseHelpers } from 'openid-client';
7
- /** @see https://github.com/panva/openid-client/blob/79386b7f120dc9bdc7606886a26e4ea7d011ee51/src/index.ts#L2018-L2022 */
8
- export declare function addHelpers(response: oauth.TokenEndpointResponse): asserts response is typeof response & TokenEndpointResponseHelpers;