From 8913fb515b0e0d65be23efd6e7767d7c0c8b122a Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Wed, 21 Oct 2015 08:18:14 -0400 Subject: [PATCH] Implement user/password login with 2FA support --- build/actions/auth.js | 76 +++++++++++++++++++++----------------- build/utils/helpers.js | 11 +++++- lib/actions/auth.coffee | 79 ++++++++++++++++++++-------------------- lib/utils/helpers.coffee | 7 ++++ package.json | 3 +- 5 files changed, 100 insertions(+), 76 deletions(-) diff --git a/build/actions/auth.js b/build/actions/auth.js index ae78287d..a6ba0e34 100644 --- a/build/actions/auth.js +++ b/build/actions/auth.js @@ -1,52 +1,65 @@ (function() { - var Promise, _, events, form, open, resin, url, validEmail, visuals; + var Promise, _, events, form, helpers, resin, visuals; Promise = require('bluebird'); - open = Promise.promisify(require('open')); - _ = require('lodash'); - url = require('url'); - resin = require('resin-sdk'); form = require('resin-cli-form'); visuals = require('resin-cli-visuals'); - validEmail = require('valid-email'); - events = require('resin-cli-events'); + helpers = require('../utils/helpers'); + exports.login = { - signature: 'login [token]', + signature: 'login', description: 'login to resin.io', - help: 'Use this command to login to your resin.io account.\n\nTo login, you need your token, which is accesible from the preferences page.\n\nExamples:\n\n $ resin login\n $ resin login "eyJ0eXAiOiJKV1Qi..."', + help: 'Use this command to login to your resin.io account.\n\nExamples:\n\n $ resin login', + options: [ + { + signature: 'email', + parameter: 'email', + description: 'email', + alias: ['e', 'u'] + }, { + signature: 'password', + parameter: 'password', + description: 'password', + alias: 'p' + } + ], primary: true, action: function(params, options, done) { - return resin.settings.get('dashboardUrl').then(function(dashboardUrl) { - return url.resolve(dashboardUrl, '/preferences'); - }).then(function(preferencesUrl) { - if (params.token != null) { - return params.token; + return form.run([ + { + message: 'Email:', + name: 'email', + type: 'input', + validate: helpers.validateEmail + }, { + message: 'Password:', + name: 'password', + type: 'password' } - console.info("To login to the Resin CLI, you need your unique token, which is accesible from\nthe preferences page at " + preferencesUrl + "\n\nAttempting to open a browser at that location..."); - return open(preferencesUrl)["catch"](function() { - return console.error("Unable to open a web browser in the current environment.\nPlease visit " + preferencesUrl + " manually."); - }).then(function() { - return form.ask({ - message: 'What\'s your token? (visible in the preferences page)', - type: 'input' + ], { + override: options + }).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) { + if (isTwoFactorAuthPassed) { + return; + } + return form.ask({ + message: 'Two factor auth challenge:', + name: 'code', + type: 'input' + }).then(resin.auth.twoFactor.challenge)["catch"](function() { + return resin.auth.logout().then(function() { + throw new Error('Invalid two factor authentication code'); }); }); - }).then(resin.auth.loginWithToken).then(function(token) { - return resin.auth.isLoggedIn().then(function(isLoggedIn) { - if (isLoggedIn) { - return token; - } - throw new Error('Authentication failed'); - }); }).then(resin.auth.whoami).tap(function(username) { console.info("Successfully logged in as: " + username); return events.send('user.login'); @@ -76,12 +89,7 @@ message: 'Email:', name: 'email', type: 'input', - validate: function(input) { - if (!validEmail(input)) { - return 'Email is not valid'; - } - return true; - } + validate: helpers.validateEmail }, { message: 'Username:', name: 'username', diff --git a/build/utils/helpers.js b/build/utils/helpers.js index 93a55f80..63dcdf90 100644 --- a/build/utils/helpers.js +++ b/build/utils/helpers.js @@ -1,5 +1,5 @@ (function() { - var Promise, _, capitano, chalk, child_process, os; + var Promise, _, capitano, chalk, child_process, os, validEmail; Promise = require('bluebird'); @@ -15,12 +15,21 @@ chalk = require('chalk'); + validEmail = require('valid-email'); + exports.getGroupDefaults = function(group) { return _.chain(group).get('options').map(function(question) { return [question.name, question["default"]]; }).object().value(); }; + exports.validateEmail = function(input) { + if (!validEmail(input)) { + return 'Email is not valid'; + } + return true; + }; + exports.getOperatingSystem = function() { var platform; platform = os.platform(); diff --git a/lib/actions/auth.coffee b/lib/actions/auth.coffee index f07c5e8d..3ed239a9 100644 --- a/lib/actions/auth.coffee +++ b/lib/actions/auth.coffee @@ -1,55 +1,60 @@ Promise = require('bluebird') -open = Promise.promisify(require('open')) _ = require('lodash') -url = require('url') resin = require('resin-sdk') form = require('resin-cli-form') visuals = require('resin-cli-visuals') -validEmail = require('valid-email') events = require('resin-cli-events') +helpers = require('../utils/helpers') exports.login = - signature: 'login [token]' + signature: 'login' description: 'login to resin.io' help: ''' Use this command to login to your resin.io account. - To login, you need your token, which is accesible from the preferences page. - Examples: $ resin login - $ resin login "eyJ0eXAiOiJKV1Qi..." ''' + options: [ + { + signature: 'email' + parameter: 'email' + description: 'email' + alias: [ 'e', 'u' ] + } + { + signature: 'password' + parameter: 'password' + description: 'password' + alias: 'p' + } + ] primary: true action: (params, options, done) -> - resin.settings.get('dashboardUrl').then (dashboardUrl) -> - return url.resolve(dashboardUrl, '/preferences') - .then (preferencesUrl) -> - return params.token if params.token? - - console.info """ - To login to the Resin CLI, you need your unique token, which is accesible from - the preferences page at #{preferencesUrl} - - Attempting to open a browser at that location... - """ - - open(preferencesUrl).catch -> - console.error """ - Unable to open a web browser in the current environment. - Please visit #{preferencesUrl} manually. - """ - .then -> - form.ask - message: 'What\'s your token? (visible in the preferences page)' - type: 'input' - - .then(resin.auth.loginWithToken) - .then (token) -> - resin.auth.isLoggedIn().then (isLoggedIn) -> - return token if isLoggedIn - throw new Error('Authentication failed') + form.run [ + message: 'Email:' + name: 'email' + type: 'input' + validate: helpers.validateEmail + , + message: 'Password:' + name: 'password' + type: 'password' + ], + override: options + .then(resin.auth.login) + .then(resin.auth.twoFactor.isPassed) + .then (isTwoFactorAuthPassed) -> + return if isTwoFactorAuthPassed + return form.ask + message: 'Two factor auth challenge:' + name: 'code' + type: 'input' + .then(resin.auth.twoFactor.challenge) + .catch -> + resin.auth.logout().then -> + throw new Error('Invalid two factor authentication code') .then(resin.auth.whoami) .tap (username) -> console.info("Successfully logged in as: #{username}") @@ -95,11 +100,7 @@ exports.signup = message: 'Email:' name: 'email' type: 'input' - validate: (input) -> - if not validEmail(input) - return 'Email is not valid' - - return true + validate: helpers.validateEmail , message: 'Username:' name: 'username' diff --git a/lib/utils/helpers.coffee b/lib/utils/helpers.coffee index 489feece..366f0c8c 100644 --- a/lib/utils/helpers.coffee +++ b/lib/utils/helpers.coffee @@ -5,6 +5,7 @@ _.str = require('underscore.string') child_process = require('child_process') os = require('os') chalk = require('chalk') +validEmail = require('valid-email') exports.getGroupDefaults = (group) -> return _.chain(group) @@ -14,6 +15,12 @@ exports.getGroupDefaults = (group) -> .object() .value() +exports.validateEmail = (input) -> + if not validEmail(input) + return 'Email is not valid' + + return true + exports.getOperatingSystem = -> platform = os.platform() platform = 'osx' if platform is 'darwin' diff --git a/package.json b/package.json index f483a3a5..f276cb7c 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "mkdirp": "~0.5.0", "nplugm": "^3.0.0", "npm": "^2.13.0", - "open": "0.0.5", "resin-cli-errors": "^1.0.0", "resin-cli-events": "^1.0.2", "resin-cli-form": "^1.3.0", @@ -58,7 +57,7 @@ "resin-image": "^1.1.4", "resin-image-manager": "^3.2.2", "resin-pine": "^1.3.0", - "resin-sdk": "^3.0.0", + "resin-sdk": "^4.0.0", "resin-settings-client": "^3.1.0", "resin-vcs": "^2.0.0", "rimraf": "^2.4.3",