1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | const readline = require('readline');
|
14 | const path = require('path');
|
15 | const { fetch } = require('@adobe/helix-fetch');
|
16 | const fse = require('fs-extra');
|
17 | const chalk = require('chalk');
|
18 | const opn = require('open');
|
19 | const AbstractCommand = require('./abstract.cmd.js');
|
20 | const LoginServer = require('./auth-login-server.js');
|
21 | const cliutils = require('./cli-util');
|
22 |
|
23 | const HELIX_APP_HOST = 'https://app.project-helix.io';
|
24 |
|
25 | class AuthCommand extends AbstractCommand {
|
26 | constructor(logger) {
|
27 | super(logger);
|
28 | this._token = '';
|
29 | this._showToken = true;
|
30 | this._openBrowser = true;
|
31 |
|
32 | this._stdout = process.stdout;
|
33 | this._stdin = process.stdin;
|
34 | this._askAdd = null;
|
35 | this._askFile = null;
|
36 | }
|
37 |
|
38 |
|
39 | get requireConfigFile() {
|
40 | return false;
|
41 | }
|
42 |
|
43 | withOpenBrowser(value) {
|
44 | this._openBrowser = value;
|
45 | return this;
|
46 | }
|
47 |
|
48 | |
49 |
|
50 |
|
51 | async init() {
|
52 | await super.init();
|
53 |
|
54 | if (!this._stdout.isTTY) {
|
55 | throw Error('Interactive authentication tool not supported on non interactive consoles.');
|
56 | }
|
57 | }
|
58 |
|
59 | async receiveToken() {
|
60 | const srv = new LoginServer()
|
61 | .withLogger(this.log)
|
62 | .withOrigin(HELIX_APP_HOST);
|
63 | await srv.start();
|
64 | this.emit('server-start', srv.port);
|
65 | this._loginUrl = `${HELIX_APP_HOST}/login?p=${srv.port}`;
|
66 |
|
67 | this._stdout.write(`
|
68 | Thank you for choosing the automatic Helix Bot Authentication process.
|
69 | I will shortly open a browser with the following url. In case the
|
70 | browser doesn't open automatically, please navigate to the url manually:
|
71 |
|
72 | ${chalk.cyan(this._loginUrl)}
|
73 |
|
74 | Oh, and I started local server, bound to 127.0.0.1:${srv.port} and am
|
75 | awaiting the completion of the process. `);
|
76 |
|
77 | if (this._openBrowser) {
|
78 | setTimeout(() => {
|
79 | opn(this._loginUrl, { wait: false, url: true });
|
80 | }, 1000);
|
81 | }
|
82 | const spinner = cliutils.createSpinner().start();
|
83 | this._token = await srv.waitForToken();
|
84 | spinner.stop();
|
85 | await srv.stop();
|
86 | this.emit('server-stop');
|
87 | this._stdout.write(`
|
88 |
|
89 | The authentication process has completed. Thank you.
|
90 | You can now close the browser window if it didn't close automatically.
|
91 |
|
92 | I received an access token that I can use to access the Helix Bot on your behalf:
|
93 | `);
|
94 | }
|
95 |
|
96 | async showUserInfo() {
|
97 |
|
98 | const response = await fetch('https://api.github.com/user', {
|
99 | headers: {
|
100 | 'User-Agent': 'HelixBot',
|
101 | Authorization: `token ${this._token}`,
|
102 | },
|
103 | json: true,
|
104 | });
|
105 |
|
106 | if (!response.ok) {
|
107 | const e = new Error(`${response.status} - "${await response.text()}"`);
|
108 | e.statusCode = response.status;
|
109 | throw e;
|
110 | }
|
111 |
|
112 | const userInfo = await response.json();
|
113 |
|
114 | this._stdout.write(`\n${chalk.gray(' Name: ')}${userInfo.name}\n`);
|
115 | this._stdout.write(`${chalk.gray(' Login: ')}${userInfo.login}\n\n`);
|
116 | }
|
117 |
|
118 | async run() {
|
119 | await this.init();
|
120 |
|
121 | await this.receiveToken();
|
122 | await this.showUserInfo();
|
123 |
|
124 | const rl = readline.createInterface({
|
125 | input: this._stdin,
|
126 | output: this._stdout,
|
127 | terminal: true,
|
128 | });
|
129 |
|
130 | const answer = this._askAdd !== null ? this._askAdd : (await cliutils.prompt(rl, 'If you wish, I can add it to a file? [Y/n]: ')).toLowerCase();
|
131 | if (answer === '' || answer === 'y' || answer === 'yes') {
|
132 | const file = this._askFile !== null ? this._askFile : (await cliutils.prompt(rl, 'which file? [.env]: ')) || '.env';
|
133 | const p = path.resolve(this.directory, file);
|
134 | let env = '';
|
135 | if (await fse.pathExists(p)) {
|
136 | env = await fse.readFile(p, 'utf-8');
|
137 | }
|
138 |
|
139 | if (env.indexOf('HLX_GITHUB_TOKEN') >= 0) {
|
140 | env = env.replace(/HLX_GITHUB_TOKEN\s*=\s*[^\s]+/, `HLX_GITHUB_TOKEN=${this._token}`);
|
141 | } else {
|
142 | if (env && !env.endsWith('\n')) {
|
143 | env += '\n';
|
144 | }
|
145 | env = `${env}HLX_GITHUB_TOKEN=${this._token}\n`;
|
146 | }
|
147 | await fse.writeFile(p, env, 'utf-8');
|
148 | this._showToken = false;
|
149 | this._stdout.write(`\nAdded github token to: ${chalk.cyan(file)}\n\n`);
|
150 | }
|
151 |
|
152 | rl.close();
|
153 |
|
154 | if (this._showToken) {
|
155 | this._stdout.write(`
|
156 | Ok, you can add it manually, then. Here it is:
|
157 |
|
158 | ${chalk.grey(`HLX_GITHUB_TOKEN=${this._token}`)}
|
159 |
|
160 | `);
|
161 | }
|
162 | }
|
163 | }
|
164 |
|
165 | module.exports = AuthCommand;
|