UNPKG

logsene-cli

Version:
293 lines (248 loc) 9.32 kB
'use strict'; /* jshint node:true */ /* global module, process, console, require */ var async = require('async'), ask = require('asking').ask, choose = require('asking').choose, spinner = require('simple-spinner'), VError = require('verror'), stringify = require('safe-json-stringify'), map = require('lodash.map'), pick = require('lodash.pick'), forEach = require('lodash.foreach'), filter = require('lodash.filter'), size = require('lodash.size'), argv = require('../lib/util').argv, isEmpty = require('../lib/util').isEmpty, out = require('../lib/util').out, isNullOrUndefined = require('../lib/util').isNullOrUndefined, conf = require('../lib/config'), reg = require('../lib/region'), logsene = require('../lib/logsene-api'); /* We handle authentication framework-style: this function * gets called before every run method of each command. * * In order to do anything with Logsene CLI, user has to * have apiKey and appKey. * We help by allowing user to authenticate with user/pass. * The process of authentication writes apiKey in the user's * configuration file, located in ~/.config folder. * The configuration file name is either SSH tty name, * (if the user has SSHd into the box) or logsene-<username> * * List of all apps for that API key is retrieved from the * API server. In there are both, app names and app keys. * We write down both to the local configuration file. */ var apiKey, appKey, traceMsg = '\nTurn on tracing (logsene config set --trace) and try again.'; spinner.change_sequence(["◓", "◑", "◒", "◐"]); module.exports = function _auth(next) { // if the client is just turning tracing on, skip auth if (argv._.indexOf('set') > -1 && argv.trace) { return setTimeout(next, 50); } async.waterfall([ // functions are basically sync if keys are found locally // so we need to wrap them to be executed on the next tick async.ensureAsync(verifyApiKey), async.ensureAsync(verifyAppKey) ], // this one doesn't need to be async function _finally(err, result) { if (err) { return out.error('Error logging in ' + (err.message ? err.message : '') + traceMsg); } if (result) { setTimeout(next, 50); // all good - back to command or the next middleware } else { out.error('Unable to login.' + traceMsg); process.exit(1); } } ); }; /** * Checks the current session for the API key * If not found locally, asks the user to login. * Login, if successful, yields the API key * Which is then written to the current session. * @param cb */ function verifyApiKey(cb) { // try the config file apiKey = conf.getSync('apiKey'); if (!isEmpty(apiKey)) { out.trace('Got API key from local config: ', apiKey); return cb(null, apiKey); } if (isEmpty(apiKey)) { // if conf file doesn't deliver (first session or session timeout), ask the user to login getApiKeyWithCredentials(function(err, logseneApiKey) { if (err) return out.error('Unable to login: ' + err.message); out.trace('API key returned from the API server: ', logseneApiKey); conf.setSync('apiKey', logseneApiKey); return cb(null, logseneApiKey); }); } } /** * Verifies whether application key is available in the session. * If not, prompts the user to choose an app (upon fetching them from API) * and stores the choice (appKey and appName) in the session. * @param cb * @param {String} apiKey * @returns {Boolean} success */ function verifyAppKey(apiKey, cb) { if (isEmpty(apiKey)) { return cb(new Error('No API key in APP request.')); } // if we find appKey in conf, use it appKey = conf.getSync('appKey'); if (!isEmpty(appKey)) { out.trace('Got APP key from local config: ' + appKey); return cb(null, true); } else { // if appKey is not in local config, ask the API server for all Logsene apps // then ask the user to choose if more than one app exists out.trace('calling getApps API'); getApps(apiKey, conf.getSync('region'), function(err, logseneApps) { if (err) { out.error(err.message); out.error('Exiting'); process.exit(1); } out.trace('API server returned these apps: '); out.trace(stringify(logseneApps)); if (isNullOrUndefined(logseneApps)) { out.warn('There are no Logsene applications for your API key.\nExiting'); process.exit(0); } // from now on, work only with active apps var activeApps = filter(logseneApps, function(app) { return app.status.toUpperCase() === 'ACTIVE'; }); var activeAppsCnt = size(activeApps); out.trace('Total # of apps: ' + size(logseneApps)); out.trace('# of active apps: ' + activeAppsCnt); // keep only a subset of useful keys per each app var apps = map(logseneApps, function(a) { return pick(a, ['token', 'name']); }); out.trace('Picked only subset of app tokens: '); out.trace(stringify(apps)); if (activeAppsCnt === 0) { out.warn('There are no active Logsene apps for your apiKey.\nExiting...'); process.exit(0); } out.trace('Active apps:\n'); out.trace(stringify(activeApps)); if (activeAppsCnt > 1) { // prompt user to choose one of active apps chooseApp(activeApps, function(err, chosenApp) { // error handled in chooseApp out.trace('Back in chooseApp callback.'); conf.setSync('appKey', chosenApp.token); conf.setSync('appName', chosenApp.name); out.info('Successfuly established Logsene ' + chosenApp.name + ' application session.'); return cb(null, true); }); } else { // there's only one active Logsene app - use it without prompting the user conf.setSync('appKey', apps[0].token); conf.setSync('appName', apps[0].name); out.info('Successfuly established Logsene ' + apps[0].name + ' application session.'); return cb(null, true); } }); } } /** * Asks the client for username and password * Retrieves api key of that user * * @param {Function} cb * @api private */ function getApiKeyWithCredentials(cb) { out.info('No active sessions. Please log in using your Sematext account:'); ask('Enter your region, US or EU [US]: ', function _regionCb(errRegion, region) { var validRegion = reg.getValidRegionString(region); if (!validRegion) { out.warn('Unknown region \'' + stringify(region) + '\'! Try again.'); process.exit(1); } conf.setSync('region', validRegion); ask ('Enter your username: ', function _usernameCb(errUser, user) { if (!isEmpty(user)) { conf.setSync('username', user); // keep username around ask('Enter your password: ', {hidden: true}, function _passCb(errPass, pass) { if (!isEmpty(pass)) { spinner.start(); logsene.login(user, pass, validRegion, function _loginAPICb(errApi, key) { spinner.stop(); if (errApi) { out.error('Login was not successful' + stringify(errApi)); return cb( new VError('Login was not successful. Possibly wrong username or password.', errApi) ); } out.info('Successfully logged in and retrieved API key.'); cb(null, key); }) } else { out.warn('Password cannot be empty! Try again.'); //getApiKeyWithCredentials(cb); process.exit(1); // bail out, but stay quiet } }); } else { out.warn('Username cannot be empty! Try again.'); //getApiKeyWithCredentials(cb); process.exit(1); } }); }); } /** * Prompts the user with active apps to choose * which one she wants to work with * @param {Array} apps - contains active app objects * @param cb */ function chooseApp(apps, cb) { var appsPrompt = {}; forEach(apps, function(ap) { appsPrompt[ap['token']] = ap.name; }); out.trace('Prepared list of apps for user prompt:\n' + JSON.stringify(appsPrompt)); choose('Choose a Logsene app that you want to work with:\n', appsPrompt, function _chooseApp(err, name, token) { if (err) { out.error('Error occurred while prompting user to choose an app.'); out.error(err.message); process.exit(1); } else { out.trace('Chosen app\'s name: ' + name); out.trace('Chosen app\'s token: ' + token); cb(null, {'token': token, 'name': name}); } } ); } /** * Get logsene applications for the suplied API key * from the API server * @param apiKey * @param cb - second param is Array of Logsene app objects * @private */ function getApps(apiKey, region, cb) { logsene.getApps(apiKey, region, function _getAppsCall(err, apps) { if (err) return cb(new VError(err)); return cb(null, apps); }); }