UNPKG

zcatalyst-cli

Version:

Command Line Tool for CATALYST

433 lines (432 loc) 20.9 kB
'use strict'; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.missingScopes = void 0; const ansi_colors_1 = require("ansi-colors"); const http_1 = require("http"); const server_js_1 = require("../util_modules/server.js"); const open_1 = __importDefault(require("open")); const path_1 = require("path"); const portfinder_1 = require("portfinder"); const url_1 = require("url"); const dc_js_1 = require("../util_modules/dc.js"); const error_js_1 = __importDefault(require("./error.js")); const api_js_1 = __importDefault(require("../internal/api.js")); const credential_js_1 = __importDefault(require("./credential.js")); const index_js_1 = __importDefault(require("../prompt/index.js")); const index_js_2 = __importDefault(require("../throbber/index.js")); const index_js_3 = require("../util_modules/constants/index.js"); const auth_js_1 = __importDefault(require("./constants/auth.js")); const dc_type_js_1 = __importDefault(require("../util_modules/constants/lib/dc-type.js")); const scopes_js_1 = __importDefault(require("./constants/scopes.js")); const ASYNC = __importStar(require("../util_modules/fs/lib/async.js")); const js_js_1 = require("../util_modules/js.js"); const index_1 = require("../util_modules/logger/index"); const option_js_1 = require("../util_modules/option.js"); const events_1 = __importDefault(require("events")); const index_js_4 = __importDefault(require("../error/index.js")); const crypto_1 = require("crypto"); exports.missingScopes = { [index_js_3.DC_TYPE.in.value]: getScopes(['dataverse']), [index_js_3.DC_TYPE.eu.value]: getScopes(['dataverse']), [index_js_3.DC_TYPE.au.value]: getScopes(['dataverse']), [index_js_3.DC_TYPE.ca.value]: getScopes(['quick_ml', 'dataverse']), [index_js_3.DC_TYPE.sa.value]: getScopes(['quick_ml', 'dataverse']), [index_js_3.DC_TYPE.jp.value]: getScopes(['dataverse']) }; function getScopes(keys) { return Object.keys(scopes_js_1.default).filter((scope) => keys.some((prefix) => scope.startsWith(prefix))); } class Login { constructor(localhost = true, user = true) { this.localhost = localhost; this.portPromise = (0, portfinder_1.getPortPromise)({ startPort: 9005, port: 9005, stopPort: 9009 }); this.salt = localhost ? 'w' : 'm'; this.user = user; } init() { return __awaiter(this, void 0, void 0, function* () { if (!this.localhost) { return this._loginWithoutLocalhost(); } const port = yield this.portPromise; return this._loginWithLocalhost(port); }); } _getCallbackUrl(port) { return 'http://localhost:' + port; } _getLoginUrl(callbackUrl, state) { return (index_js_3.ORIGIN.auth + '/oauth/v2/auth?' + js_js_1.JS.map({ client_id: auth_js_1.default.web.id, scope: js_js_1.JS.values(this._getScopes()).join(' '), response_type: 'code', access_type: 'offline', prompt: 'consent', redirect_uri: callbackUrl, state }, (v, k) => { return k + '=' + encodeURIComponent(v); }).join('&')); } _getTokenFromAuthorizationCode(code, callbackUrl) { return __awaiter(this, void 0, void 0, function* () { const authCodeResponse = yield new api_js_1.default() .post('/oauth/v2/token', { origin: index_js_3.ORIGIN.auth, qs: { code, client_id: auth_js_1.default.web.id, client_secret: auth_js_1.default.web.secret, redirect_uri: callbackUrl, grant_type: 'authorization_code' }, log: { skipQuery: true }, authNeeded: false }) .catch((err) => { throw new error_js_1.default('Error when getting the token from authorization code', { exit: 2, original: err }); }); if (!js_js_1.JS.hasIn(authCodeResponse, 'body.access_token') || !js_js_1.JS.hasIn(authCodeResponse, 'body.refresh_token')) { (0, index_1.debug)('Token Fetch Error: \n' + ' Status: ' + authCodeResponse.status + ', Body: ' + authCodeResponse.body); throw new error_js_1.default('Unable to get refresh_token from code. Kindly try again. \n', { exit: 2 }); } return js_js_1.JS.assign({ created_time: Date.now(), expires_at: Date.now() + parseInt(authCodeResponse.body.expires_in + '', 10) * 1000 }, authCodeResponse.body); }); } _getTokenFromDeviceCode(code, tkCtrl) { return __awaiter(this, void 0, void 0, function* () { const deviceCodeResponse = yield new api_js_1.default() .post('/oauth/v3/device/token', { origin: index_js_3.ORIGIN.auth, qs: { client_id: auth_js_1.default.mobile.id, client_secret: auth_js_1.default.mobile.secret, scope: js_js_1.JS.values(this._getScopes()).join(' '), grant_type: 'device_token', code }, log: { skipQuery: true }, authNeeded: false }) .catch((err) => { throw new error_js_1.default('Error when getting access token from device code', { exit: 2, original: err }); }); if (!js_js_1.JS.hasIn(deviceCodeResponse, 'body.access_token') || !js_js_1.JS.hasIn(deviceCodeResponse, 'body.refresh_token')) { (0, index_1.debug)('Token Fetch Error: \n' + ' Status: ' + deviceCodeResponse.status + ', Body: ' + JSON.stringify(deviceCodeResponse.body)); (0, index_1.debug)('> polling <'); if (++tkCtrl.retryCount > 30) { if ((0, option_js_1.getCurrentCommand)() === 'login') { throw new error_js_1.default('Unable to get refresh_token from code', { exit: 0, errorId: 'LOGIN-1' }); } else { throw new error_js_1.default('Unable to get refresh_token from code', { exit: 1, errorId: 'LOGIN-2', arg: [(0, ansi_colors_1.bold)('catalyst token:generate')] }); } } yield js_js_1.JS.sleep(2000); if (!tkCtrl.retry) { throw new error_js_1.default('Process Aborted', { exit: 0 }); } return this._getTokenFromDeviceCode(code, tkCtrl); } return js_js_1.JS.assign({ created_time: Date.now(), expires_at: Date.now() + deviceCodeResponse.body.expires_in * 1000 }, deviceCodeResponse.body); }); } _respondWithFile(req, res, statusCode, filename) { return __awaiter(this, void 0, void 0, function* () { const content = yield ASYNC.readFile((0, path_1.join)(__dirname, filename)); res.setHeader('Content-Type', 'text/html'); if (content !== undefined) { res.setHeader('Content-Length', content.length); } res.statusCode = statusCode; res.end(content, () => { req.socket.destroy(); }); }); } _getUserDetails() { return __awaiter(this, void 0, void 0, function* () { if (!this.user) { return undefined; } const userInfoResponse = yield new api_js_1.default() .get('/oauth/user/info', { origin: index_js_3.ORIGIN.auth, authNeeded: true }) .catch((err) => { throw new error_js_1.default('Error when trying to get the user details', { exit: 2, original: err }); }); if (!js_js_1.JS.hasIn(userInfoResponse, 'body.Email') && !js_js_1.JS.hasIn(userInfoResponse, 'body.ZUID')) { (0, index_1.debug)('User Details Fetch Error:\n' + ' Status: ' + userInfoResponse.response.statusCode + ', Body: ' + userInfoResponse.body); return undefined; } return userInfoResponse.body; }); } _loginWithoutLocalhost(err) { return __awaiter(this, void 0, void 0, function* () { if (err) { (0, index_1.debug)('Error while login with server : ' + err.message); } this.salt = 'm'; const throbber = index_js_2.default.getInstance(); const throbberName = 'authentication'; const deviceCodeResponse = yield new api_js_1.default() .post('/oauth/v3/device/code', { origin: index_js_3.ORIGIN.auth, qs: { client_id: auth_js_1.default.mobile.id, scope: js_js_1.JS.values(this._getScopes()).join(' '), grant_type: 'device_request', access_type: 'offline', prompt: 'consent' }, authNeeded: false }) .catch((err) => { throw new error_js_1.default('Error when getting code from zoho', { exit: 2, original: err }); }); if (!js_js_1.JS.hasIn(deviceCodeResponse, 'body.user_code') || !js_js_1.JS.hasIn(deviceCodeResponse, 'body.device_code')) { (0, index_1.debug)('Token Fetch Error: \n' + ' Status: ' + deviceCodeResponse.status + ', Body: ' + JSON.stringify(deviceCodeResponse.body)); throw new error_js_1.default('Unable to get code from zoho', { exit: 1, errorId: 'LOGIN-3' }); } (0, index_1.info)(); (0, index_1.info)('Visit this URL on any device: \n'); (0, index_1.info)(ansi_colors_1.bold.underline(js_js_1.JS.get(deviceCodeResponse, 'body.verification_url'))); (0, index_1.info)(); (0, index_1.info)('Enter the device verification code: ' + (0, ansi_colors_1.bold)(js_js_1.JS.get(deviceCodeResponse, 'body.user_code')) + ' and click verify to continue.'); const deviceCode = js_js_1.JS.get(deviceCodeResponse, 'body.device_code'); const tkCtrl = { retry: true, retryCount: 0 }; Login.loginEvents.on('exit', () => { tkCtrl.retry = false; }); yield js_js_1.JS.sleep(10000); throbber.add(throbberName, { text: 'checking if the code has been entered' }); try { const result = yield this._getTokenFromDeviceCode(deviceCode, tkCtrl); js_js_1.JS.set(result, 'token', this.salt + '_' + result.refresh_token); credential_js_1.default.globalSelf = null; credential_js_1.default.oneTimeToken = result.access_token; throbber.update(throbberName, { text: 'getting user details' }); const userDetails = yield this._getUserDetails(); throbber.succeed(throbberName, { text: 'authenticated' }); return { user: userDetails, token: result, scopes: js_js_1.JS.values(this._getScopes()), dc: (0, dc_js_1.getActiveDC)() }; } catch (e) { throbber.remove(throbberName); throw e; } }); } _loginWithLocalhost(port) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { var _a; const dcRef = (_a = (0, dc_js_1.getActiveDCType)()) === null || _a === void 0 ? void 0 : _a.ref; const callbackUrl = this._getCallbackUrl(port); const nonce = (0, crypto_1.randomBytes)(16).toString('base64'); const authUrl = this._getLoginUrl(callbackUrl, nonce); let reqCount = 0; const server = (0, http_1.createServer)((req, res) => __awaiter(this, void 0, void 0, function* () { try { let processReq = true; if (reqCount + 1 > 1 || req.url === '/favicon.ico' || req.url === undefined) { (0, index_1.debug)('unknown request received : ' + req.url); processReq = false; res.writeHead(404); res.end(); return; } reqCount += 1; const queryParamsObj = new url_1.URL(req.url, `http://${req.headers.host}`) .searchParams; if (queryParamsObj.get('state') !== nonce) { throw new index_js_4.default('Invalid state param received.', { exit: 2 }); } const code = queryParamsObj !== undefined && queryParamsObj.get('code'); const _location = queryParamsObj !== undefined && queryParamsObj.get('location'); const location = _location === 'dev' ? 'us' : _location; if (processReq && typeof code === 'string') { yield this._respondWithFile(req, res, 200, '../../templates/loginSuccess.html'); if (dcRef !== undefined && typeof location === 'string' && location !== dcRef) { (0, index_1.warning)('Your credentials does not match the DC opted!!!'); const ans = yield index_js_1.default.ask(index_js_1.default.question('DC', `Do you wish to continue with login by making ${location.toUpperCase()} DC as active ?`, { type: 'confirm', defaultAns: true })); if (!ans.DC) { yield destroyer.destroy(true).catch((err) => (0, index_1.debug)(err)); reject(new error_js_1.default('Aborted by user.\n', { exit: 1 })); return; } (0, dc_js_1.updateActiveDC)(dc_type_js_1.default[location].value); } const result = yield this._getTokenFromAuthorizationCode(code, callbackUrl); js_js_1.JS.set(result, 'token', this.salt + '_' + result.refresh_token); credential_js_1.default.globalSelf = null; credential_js_1.default.oneTimeToken = result.access_token; const userDetails = yield this._getUserDetails(); yield destroyer.destroy(true).catch((err) => (0, index_1.debug)(err)); yield new Promise((res) => setTimeout(res, 2000)); resolve({ user: userDetails, token: result, scopes: js_js_1.JS.values(this._getScopes()), dc: (0, dc_js_1.getActiveDC)() }); } else if (processReq) { yield this._respondWithFile(req, res, 400, '../../templates/loginFailure.html'); yield destroyer.destroy(true).catch((err) => (0, index_1.debug)(err)); reject(new error_js_1.default("Credentials doesn't seem to be valid.\n", { exit: 1 })); } } catch (e) { yield destroyer.destroy(true).catch((err) => (0, index_1.debug)(err)); reject(new error_js_1.default('Server crashed with error.\n', { exit: 2, original: error_js_1.default.getErrorInstance(e) })); } })); const destroyer = new server_js_1.ConnectionDestroyer(server); server.listen(port, '127.0.0.1', () => { (0, index_1.info)(); (0, index_1.info)('Visit this URL on this device to log in:'); (0, index_1.info)(ansi_colors_1.bold.underline(authUrl)); (0, index_1.info)(); (0, open_1.default)(authUrl).catch(); }); server.on('error', () => { this._loginWithoutLocalhost().then(resolve, reject); }); Login.loginEvents.once('abort', (err) => __awaiter(this, void 0, void 0, function* () { yield destroyer.destroy(true).catch((err) => (0, index_1.debug)(err)); reject(err instanceof Error ? Error : new error_js_1.default('Login aborted', { exit: 0 })); })); }); }); } _getScopes() { const dc = (0, dc_js_1.getActiveDC)(); if (dc in exports.missingScopes) { const devoidScopes = exports.missingScopes[dc]; const _scopes = Object.assign({}, scopes_js_1.default); devoidScopes.forEach((scope) => { delete _scopes[scope]; }); return _scopes; } return scopes_js_1.default; } } exports.default = Login; Login.loginEvents = new events_1.default();