1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const get_port_1 = require("get-port");
|
4 | const http = require("http");
|
5 | const axios_1 = require("axios");
|
6 |
|
7 | const opn = require("open");
|
8 | const crypto = require("crypto");
|
9 | const cli_ux_1 = require("cli-ux");
|
10 | const base_command_1 = require("../base-command");
|
11 | const helpers_1 = require("../utils/helpers");
|
12 | const constants_1 = require("../utils/constants");
|
13 | const prompts_1 = require("../utils/prompts");
|
14 | class Login extends base_command_1.default {
|
15 | constructor() {
|
16 | super(...arguments);
|
17 | this.on = (event, callback) => {
|
18 | this._listerners[event] = [...this._listerners[event], callback];
|
19 | };
|
20 | this.stopServer = () => {
|
21 | this.debug('stopping server');
|
22 | if (this._server) {
|
23 | this._server.close(() => {
|
24 | this.debug('server stopped');
|
25 | this._listerners['shutdown'].map(cb => cb());
|
26 | });
|
27 | }
|
28 | cli_ux_1.default.action.stop();
|
29 | };
|
30 | this.startServer = async () => {
|
31 | const port = await get_port_1.default({ port: constants_1.BEARER_LOGIN_PORT });
|
32 | return new Promise((resolve, reject) => {
|
33 | if (port !== constants_1.BEARER_LOGIN_PORT) {
|
34 | this.error(`bearer login requires port ${constants_1.BEARER_LOGIN_PORT} to be available`);
|
35 | reject();
|
36 | }
|
37 | this.debug('starting server');
|
38 | const server = http
|
39 | .createServer((request, response) => {
|
40 | let body = '';
|
41 | request.on('data', chunk => {
|
42 | body += chunk;
|
43 | });
|
44 | request.on('end', () => {
|
45 | try {
|
46 | const data = JSON.parse(body);
|
47 | this.debug(data);
|
48 | response.setHeader('Access-Control-Allow-Origin', process.env.LOGIN_ALLOWED_ORIGIN || this.constants.DeveloperPortalUrl);
|
49 | response.setHeader('Connection', 'close');
|
50 | response.write('OK');
|
51 | response.end();
|
52 | this.stopServer();
|
53 | this.getToken(data.code);
|
54 | }
|
55 | catch (e) {
|
56 | this.debug(e);
|
57 | }
|
58 | });
|
59 | })
|
60 | .listen(constants_1.BEARER_LOGIN_PORT, () => resolve(server));
|
61 | });
|
62 | };
|
63 | this.getToken = async (code) => {
|
64 | try {
|
65 | const { data: token } = await axios_1.default.post(`${this.constants.LoginDomain}/oauth/token`, {
|
66 | code,
|
67 | grant_type: 'authorization_code',
|
68 | client_id: `${constants_1.LOGIN_CLIENT_ID}`,
|
69 | code_verifier: `${this._verifier}`,
|
70 | redirect_uri: this.callbackUrl
|
71 | });
|
72 | this.debug(token);
|
73 | await this.bearerConfig.storeToken(token);
|
74 | this.success('Successfully logged in!! 🐻');
|
75 | this._listerners['success'].map(cb => cb());
|
76 | }
|
77 | catch (e) {
|
78 | this.error(e);
|
79 | }
|
80 | };
|
81 | }
|
82 | async run() {
|
83 | this._listerners = {
|
84 | success: [],
|
85 | error: [],
|
86 | shutdown: []
|
87 | };
|
88 | this._server = await this.startServer();
|
89 | this._verifier = base64URLEncode(crypto.randomBytes(32));
|
90 | this._challenge = base64URLEncode(sha256(this._verifier));
|
91 | cli_ux_1.default.action.start('Logging you in');
|
92 | const scopes = 'offline_access email openid';
|
93 | const audience = `cli-${constants_1.BEARER_ENV}`;
|
94 | const params = {
|
95 | audience,
|
96 | scope: scopes,
|
97 | response_type: 'code',
|
98 | client_id: constants_1.LOGIN_CLIENT_ID,
|
99 | code_challenge: this._challenge,
|
100 | code_challenge_method: 'S256',
|
101 | redirect_uri: this.callbackUrl
|
102 | };
|
103 | this.debug('authoriwe params %j', params);
|
104 | const url = `${this.constants.LoginDomain}/authorize?${helpers_1.toParams(params)}`;
|
105 | const spawned = await opn(url);
|
106 | await Promise.race([
|
107 | new Promise((resolve, reject) => {
|
108 | spawned.on('close', async (code, signal) => {
|
109 | if (code !== 0) {
|
110 | this.stopServer();
|
111 | this.warn(this.colors.yellow(`Unable to open a browser. If you want to retrieve a token please follow these steps\n`));
|
112 | this.log(this.colors.bold('1/ access the url below and follow the login process:\n\n'), url);
|
113 | this.log();
|
114 | this.log(this.colors.bold(`2/ when you access the success page copy the token and paste it here`));
|
115 | const token = await prompts_1.askForString('Token');
|
116 | await this.getToken(token);
|
117 | }
|
118 | });
|
119 | }),
|
120 | Promise.all([
|
121 | new Promise((resolve, reject) => {
|
122 | this.on('success', resolve);
|
123 | this.on('error', reject);
|
124 | }),
|
125 | new Promise((resolve, reject) => {
|
126 | this.on('shutdown', resolve);
|
127 | this.on('error', reject);
|
128 | })
|
129 | ])
|
130 | ]);
|
131 | }
|
132 | get callbackUrl() {
|
133 | return process.env.BEARER_LOGIN_CALLBACK_URL || `${this.constants.DeveloperPortalUrl}cli-login-callback`;
|
134 | }
|
135 | }
|
136 | Login.description = 'login using Bearer credentials';
|
137 | Login.flags = Object.assign({}, base_command_1.default.flags);
|
138 | Login.args = [];
|
139 | exports.default = Login;
|
140 | function base64URLEncode(str) {
|
141 | return str
|
142 | .toString('base64')
|
143 | .replace(/\+/g, '-')
|
144 | .replace(/\//g, '_')
|
145 | .replace(/=/g, '');
|
146 | }
|
147 | function sha256(str) {
|
148 | return crypto
|
149 | .createHash('sha256')
|
150 | .update(str)
|
151 | .digest();
|
152 | }
|