zcatalyst-cli
Version:
Command Line Tool for CATALYST
433 lines (432 loc) • 20.9 kB
JavaScript
;
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();