From 8d709aea7da26102d7260bdf226c9ee930d1860e Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Tue, 12 Jan 2016 09:07:15 -0400 Subject: [PATCH] Implement purely interactive login command The new login command interactively asks the user if he wants to login using web/credentials/token. --- build/actions/auth.js | 63 ++++++++++++++++----------------- build/utils/patterns.js | 50 ++++++++++++++++++++++++++ doc/cli.markdown | 17 +++++---- lib/actions/auth.coffee | 74 ++++++++++++++++++--------------------- lib/utils/patterns.coffee | 41 ++++++++++++++++++++++ 5 files changed, 166 insertions(+), 79 deletions(-) diff --git a/build/actions/auth.js b/build/actions/auth.js index 5c0dcf36..a4a57a93 100644 --- a/build/actions/auth.js +++ b/build/actions/auth.js @@ -19,13 +19,18 @@ limitations under the License. exports.login = { signature: 'login', description: 'login to resin.io', - help: 'Use this command to login to your resin.io account.\n\nThis command will open your web browser and prompt you to authorize the CLI\nfrom the dashboard.\n\nIf you don\'t have access to a web browser (e.g: running in a headless server),\nyou can fetch your authentication token from the preferences page and pass\nthe token option.\n\nAlternatively, you can pass the `--credentials` boolean option to perform\na credential-based authentication, with optional `--email` and `--password`\noptions to avoid interactive behaviour (unless you have 2FA enabled).\n\nExamples:\n\n $ resin login\n $ resin login --token "..."\n $ resin login --credentials\n $ resin login --credentials --email johndoe@gmail.com --password secret', + help: 'Use this command to login to your resin.io account.\n\nThis command will prompt you to login using the following login types:\n\n- Web authorization: open your web browser and prompt you to authorize the CLI\nfrom the dashboard.\n\n- Credentials: using email/password and 2FA.\n\n- Token: using the authentication token from the preferences page.\n\nExamples:\n\n $ resin login\n $ resin login --web\n $ resin login --token "..."\n $ resin login --credentials\n $ resin login --credentials --email johndoe@gmail.com --password secret', options: [ { signature: 'token', description: 'auth token', parameter: 'token', alias: 't' + }, { + signature: 'web', + description: 'web-based login', + boolean: true, + alias: 'w' }, { signature: 'credentials', description: 'credential-based login', @@ -45,48 +50,40 @@ limitations under the License. ], primary: true, action: function(params, options, done) { - var Promise, auth, events, form, resin, validation; + var Promise, _, auth, events, form, login, patterns, resin; + _ = require('lodash'); Promise = require('bluebird'); resin = require('resin-sdk'); events = require('resin-cli-events'); - form = require('resin-cli-form'); auth = require('resin-cli-auth'); - validation = require('../utils/validation'); - return resin.settings.get('resinUrl').then(function(resinUrl) { - console.log("Logging in to " + resinUrl); + form = require('resin-cli-form'); + patterns = require('../utils/patterns'); + login = function(options) { if (options.token != null) { - return resin.auth.loginWithToken(options.token); - } else if (options.credentials) { - return form.run([ - { - message: 'Email:', - name: 'email', - type: 'input', - validate: validation.validateEmail - }, { - message: 'Password:', - name: 'password', - type: 'password' - } - ], { - override: options - }).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) { - if (isTwoFactorAuthPassed) { - return; + return Promise["try"](function() { + if (_.isString(options.token)) { + return options.token; } return form.ask({ - message: 'Two factor auth challenge:', - name: 'code', + message: 'Token (from the preferences page)', + name: 'token', 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); + } else if (options.credentials) { + return patterns.authenticate(options); + } else if (options.web) { + console.info('Connecting to the web dashboard'); + return auth.login(); } - console.info('Connecting to the web dashboard'); - return auth.login(); + return patterns.askLoginType().then(function(loginType) { + options[loginType] = true; + return login(options); + }); + }; + return resin.settings.get('resinUrl').then(function(resinUrl) { + console.log("Logging in to " + resinUrl); + return login(options); }).then(resin.auth.whoami).tap(function(username) { console.info("Successfully logged in as: " + username); return events.send('user.login'); diff --git a/build/utils/patterns.js b/build/utils/patterns.js index 16a3293a..12acf2b1 100644 --- a/build/utils/patterns.js +++ b/build/utils/patterns.js @@ -32,6 +32,56 @@ limitations under the License. validation = require('./validation'); + exports.authenticate = function(options) { + return form.run([ + { + message: 'Email:', + name: 'email', + type: 'input', + validate: validation.validateEmail + }, { + message: 'Password:', + name: 'password', + type: 'password' + } + ], { + 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'); + }); + }); + }); + }; + + exports.askLoginType = function() { + return form.ask({ + message: 'How would you like to login?', + name: 'loginType', + type: 'list', + choices: [ + { + name: 'Web authorization (recommended)', + value: 'web' + }, { + name: 'Credentials', + value: 'credentials' + }, { + name: 'Authentication token', + value: 'token' + } + ] + }); + }; + exports.selectDeviceType = function() { return resin.models.device.getSupportedDeviceTypes().then(function(deviceTypes) { return form.ask({ diff --git a/doc/cli.markdown b/doc/cli.markdown index 821d918c..55c8d8d4 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -165,20 +165,19 @@ confirm non interactively Use this command to login to your resin.io account. -This command will open your web browser and prompt you to authorize the CLI +This command will prompt you to login using the following login types: + +- Web authorization: open your web browser and prompt you to authorize the CLI from the dashboard. -If you don't have access to a web browser (e.g: running in a headless server), -you can fetch your authentication token from the preferences page and pass -the token option. +- Credentials: using email/password and 2FA. -Alternatively, you can pass the `--credentials` boolean option to perform -a credential-based authentication, with optional `--email` and `--password` -options to avoid interactive behaviour (unless you have 2FA enabled). +- Token: using the authentication token from the preferences page. Examples: $ resin login + $ resin login --web $ resin login --token "..." $ resin login --credentials $ resin login --credentials --email johndoe@gmail.com --password secret @@ -189,6 +188,10 @@ Examples: auth token +#### --web, -w + +web-based login + #### --credentials, -c credential-based login diff --git a/lib/actions/auth.coffee b/lib/actions/auth.coffee index b962039a..ec08b029 100644 --- a/lib/actions/auth.coffee +++ b/lib/actions/auth.coffee @@ -20,20 +20,19 @@ exports.login = help: ''' Use this command to login to your resin.io account. - This command will open your web browser and prompt you to authorize the CLI + This command will prompt you to login using the following login types: + + - Web authorization: open your web browser and prompt you to authorize the CLI from the dashboard. - If you don't have access to a web browser (e.g: running in a headless server), - you can fetch your authentication token from the preferences page and pass - the token option. + - Credentials: using email/password and 2FA. - Alternatively, you can pass the `--credentials` boolean option to perform - a credential-based authentication, with optional `--email` and `--password` - options to avoid interactive behaviour (unless you have 2FA enabled). + - Token: using the authentication token from the preferences page. Examples: $ resin login + $ resin login --web $ resin login --token "..." $ resin login --credentials $ resin login --credentials --email johndoe@gmail.com --password secret @@ -45,6 +44,12 @@ exports.login = parameter: 'token' alias: 't' } + { + signature: 'web' + description: 'web-based login' + boolean: true + alias: 'w' + } { signature: 'credentials' description: 'credential-based login' @@ -66,45 +71,36 @@ exports.login = ] primary: true action: (params, options, done) -> + _ = require('lodash') Promise = require('bluebird') resin = require('resin-sdk') events = require('resin-cli-events') - form = require('resin-cli-form') auth = require('resin-cli-auth') - validation = require('../utils/validation') + form = require('resin-cli-form') + patterns = require('../utils/patterns') + + login = (options) -> + if options.token? + return Promise.try -> + return options.token if _.isString(options.token) + return form.ask + message: 'Token (from the preferences page)' + name: 'token' + type: 'input' + .then(resin.auth.loginWithToken) + else if options.credentials + return patterns.authenticate(options) + else if options.web + console.info('Connecting to the web dashboard') + return auth.login() + + return patterns.askLoginType().then (loginType) -> + options[loginType] = true + return login(options) resin.settings.get('resinUrl').then (resinUrl) -> console.log("Logging in to #{resinUrl}") - - if options.token? - return resin.auth.loginWithToken(options.token) - else if options.credentials - return form.run [ - message: 'Email:' - name: 'email' - type: 'input' - validate: validation.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') - - console.info('Connecting to the web dashboard') - return auth.login() + return login(options) .then(resin.auth.whoami) .tap (username) -> console.info("Successfully logged in as: #{username}") diff --git a/lib/utils/patterns.coffee b/lib/utils/patterns.coffee index b4dd9e65..ce32e833 100644 --- a/lib/utils/patterns.coffee +++ b/lib/utils/patterns.coffee @@ -22,6 +22,47 @@ resin = require('resin-sdk') chalk = require('chalk') validation = require('./validation') +exports.authenticate = (options) -> + return form.run [ + message: 'Email:' + name: 'email' + type: 'input' + validate: validation.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') + +exports.askLoginType = -> + return form.ask + message: 'How would you like to login?' + name: 'loginType' + type: 'list' + choices: [ + name: 'Web authorization (recommended)' + value: 'web' + , + name: 'Credentials' + value: 'credentials' + , + name: 'Authentication token' + value: 'token' + ] + exports.selectDeviceType = -> resin.models.device.getSupportedDeviceTypes().then (deviceTypes) -> return form.ask