Merge pull request #242 from resin-io/jviotti/feature/us-pw-login

Implement user/password login with 2FA support
This commit is contained in:
Juan Cruz Viotti 2015-10-21 09:32:17 -04:00
commit ec72f93480
5 changed files with 100 additions and 76 deletions

View File

@ -1,52 +1,65 @@
(function() { (function() {
var Promise, _, events, form, open, resin, url, validEmail, visuals; var Promise, _, events, form, helpers, resin, visuals;
Promise = require('bluebird'); Promise = require('bluebird');
open = Promise.promisify(require('open'));
_ = require('lodash'); _ = require('lodash');
url = require('url');
resin = require('resin-sdk'); resin = require('resin-sdk');
form = require('resin-cli-form'); form = require('resin-cli-form');
visuals = require('resin-cli-visuals'); visuals = require('resin-cli-visuals');
validEmail = require('valid-email');
events = require('resin-cli-events'); events = require('resin-cli-events');
helpers = require('../utils/helpers');
exports.login = { exports.login = {
signature: 'login [token]', signature: 'login',
description: 'login to resin.io', 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, primary: true,
action: function(params, options, done) { action: function(params, options, done) {
return resin.settings.get('dashboardUrl').then(function(dashboardUrl) { return form.run([
return url.resolve(dashboardUrl, '/preferences'); {
}).then(function(preferencesUrl) { message: 'Email:',
if (params.token != null) { name: 'email',
return params.token; type: 'input',
validate: helpers.validateEmail
}, {
message: 'Password:',
name: 'password',
type: 'password'
}
], {
override: options
}).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) {
if (isTwoFactorAuthPassed) {
return;
} }
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({ return form.ask({
message: 'What\'s your token? (visible in the preferences page)', message: 'Two factor auth challenge:',
name: 'code',
type: 'input' 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) { }).then(resin.auth.whoami).tap(function(username) {
console.info("Successfully logged in as: " + username); console.info("Successfully logged in as: " + username);
return events.send('user.login'); return events.send('user.login');
@ -76,12 +89,7 @@
message: 'Email:', message: 'Email:',
name: 'email', name: 'email',
type: 'input', type: 'input',
validate: function(input) { validate: helpers.validateEmail
if (!validEmail(input)) {
return 'Email is not valid';
}
return true;
}
}, { }, {
message: 'Username:', message: 'Username:',
name: 'username', name: 'username',

View File

@ -1,5 +1,5 @@
(function() { (function() {
var Promise, _, capitano, chalk, child_process, os; var Promise, _, capitano, chalk, child_process, os, validEmail;
Promise = require('bluebird'); Promise = require('bluebird');
@ -15,12 +15,21 @@
chalk = require('chalk'); chalk = require('chalk');
validEmail = require('valid-email');
exports.getGroupDefaults = function(group) { exports.getGroupDefaults = function(group) {
return _.chain(group).get('options').map(function(question) { return _.chain(group).get('options').map(function(question) {
return [question.name, question["default"]]; return [question.name, question["default"]];
}).object().value(); }).object().value();
}; };
exports.validateEmail = function(input) {
if (!validEmail(input)) {
return 'Email is not valid';
}
return true;
};
exports.getOperatingSystem = function() { exports.getOperatingSystem = function() {
var platform; var platform;
platform = os.platform(); platform = os.platform();

View File

@ -1,55 +1,60 @@
Promise = require('bluebird') Promise = require('bluebird')
open = Promise.promisify(require('open'))
_ = require('lodash') _ = require('lodash')
url = require('url')
resin = require('resin-sdk') resin = require('resin-sdk')
form = require('resin-cli-form') form = require('resin-cli-form')
visuals = require('resin-cli-visuals') visuals = require('resin-cli-visuals')
validEmail = require('valid-email')
events = require('resin-cli-events') events = require('resin-cli-events')
helpers = require('../utils/helpers')
exports.login = exports.login =
signature: 'login [token]' signature: 'login'
description: 'login to resin.io' description: 'login to resin.io'
help: ''' help: '''
Use this command to login to your resin.io account. Use this command to login to your resin.io account.
To login, you need your token, which is accesible from the preferences page.
Examples: Examples:
$ resin login $ 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 primary: true
action: (params, options, done) -> action: (params, options, done) ->
resin.settings.get('dashboardUrl').then (dashboardUrl) -> form.run [
return url.resolve(dashboardUrl, '/preferences') message: 'Email:'
.then (preferencesUrl) -> name: 'email'
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' type: 'input'
validate: helpers.validateEmail
.then(resin.auth.loginWithToken) ,
.then (token) -> message: 'Password:'
resin.auth.isLoggedIn().then (isLoggedIn) -> name: 'password'
return token if isLoggedIn type: 'password'
throw new Error('Authentication failed') ],
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) .then(resin.auth.whoami)
.tap (username) -> .tap (username) ->
console.info("Successfully logged in as: #{username}") console.info("Successfully logged in as: #{username}")
@ -95,11 +100,7 @@ exports.signup =
message: 'Email:' message: 'Email:'
name: 'email' name: 'email'
type: 'input' type: 'input'
validate: (input) -> validate: helpers.validateEmail
if not validEmail(input)
return 'Email is not valid'
return true
, ,
message: 'Username:' message: 'Username:'
name: 'username' name: 'username'

View File

@ -5,6 +5,7 @@ _.str = require('underscore.string')
child_process = require('child_process') child_process = require('child_process')
os = require('os') os = require('os')
chalk = require('chalk') chalk = require('chalk')
validEmail = require('valid-email')
exports.getGroupDefaults = (group) -> exports.getGroupDefaults = (group) ->
return _.chain(group) return _.chain(group)
@ -14,6 +15,12 @@ exports.getGroupDefaults = (group) ->
.object() .object()
.value() .value()
exports.validateEmail = (input) ->
if not validEmail(input)
return 'Email is not valid'
return true
exports.getOperatingSystem = -> exports.getOperatingSystem = ->
platform = os.platform() platform = os.platform()
platform = 'osx' if platform is 'darwin' platform = 'osx' if platform is 'darwin'

View File

@ -48,7 +48,6 @@
"mkdirp": "~0.5.0", "mkdirp": "~0.5.0",
"nplugm": "^3.0.0", "nplugm": "^3.0.0",
"npm": "^2.13.0", "npm": "^2.13.0",
"open": "0.0.5",
"resin-cli-errors": "^1.0.0", "resin-cli-errors": "^1.0.0",
"resin-cli-events": "^1.0.2", "resin-cli-events": "^1.0.2",
"resin-cli-form": "^1.3.0", "resin-cli-form": "^1.3.0",
@ -58,7 +57,7 @@
"resin-image": "^1.1.4", "resin-image": "^1.1.4",
"resin-image-manager": "^3.2.2", "resin-image-manager": "^3.2.2",
"resin-pine": "^1.3.0", "resin-pine": "^1.3.0",
"resin-sdk": "^3.0.0", "resin-sdk": "^4.0.0",
"resin-settings-client": "^3.1.0", "resin-settings-client": "^3.1.0",
"resin-vcs": "^2.0.0", "resin-vcs": "^2.0.0",
"rimraf": "^2.4.3", "rimraf": "^2.4.3",