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