206 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| // process.stdout.isTTY
 | |
| var form = require('terminal-forms.js').create(process.stdin, process.stdout);
 | |
| //var dns = form.PromiseA.promisifyAll(require('dns'));
 | |
| var OAUTH3 = require('../oauth3.node.js');
 | |
| // TODO change to ._hooks
 | |
| OAUTH3._hooks = require('../oauth3.node.storage.js');
 | |
| // directives = { get, set }
 | |
| // sessions = { get, set }
 | |
| /*
 | |
| OAUTH3._hooks.directives.get = require('../oauth3.node.storage.js').directives.get;
 | |
| OAUTH3._hooks.directives.set = require('../oauth3.node.storage.js').directives.set;
 | |
| OAUTH3._hooks.session.get = require('../oauth3.node.storage.js').sessions.get;
 | |
| OAUTH3._hooks.session.set = require('../oauth3.node.storage.js').sessions.set;
 | |
| */
 | |
| 
 | |
| // opts = { email, providerUri }
 | |
| module.exports.login = function (options) {
 | |
|   options = options || {};
 | |
|   var url = require('url');
 | |
|   //console.log('stdin tty', process.stdin.isTTY);
 | |
|   //console.log('stdout tty', process.stdout.isTTY);
 | |
|   var oauth3;
 | |
|   var opts = {
 | |
|     email: options.email
 | |
|   , providerUri: options.providerUri
 | |
|   };
 | |
|   if (opts.form) {
 | |
|     form = opts.form;
 | |
|   }
 | |
|   var email;
 | |
|   var providerUrl;
 | |
|   var providerUri;
 | |
|   var sameProvider;
 | |
|   var username;
 | |
| 
 | |
|   function getSession() {
 | |
|     var username;
 | |
| 
 | |
|     // TODO lookup uuid locally before performing loginMeta
 | |
|     // TODO lookup token locally before performing loginMeta / otp
 | |
|     return OAUTH3.authn.loginMeta(oauth3._providerDirectives, { email: email }).then(function (/*result*/) {
 | |
|       return { node: email, type: 'email' };
 | |
|     }, function (/*err*/) {
 | |
|       // TODO require hashcash to create user account
 | |
|       function confirmCreateAccount() {
 | |
|         // TODO directives should specify private (invite-only) vs internal (request) vs public (allow) accounts
 | |
|         return form.ask({
 | |
|           label: "We don't recognize that address. Do you want to create a new account? [Y/n] "
 | |
|         , type: 'text' // TODO boolean with default Y or N
 | |
|         }).then(function (result) {
 | |
|           if (!result.input) {
 | |
|             result.input = 'Y';
 | |
|           }
 | |
| 
 | |
|           // TODO needs backup address if email is on same domain as login
 | |
|           result.input = result.input.toLowerCase();
 | |
| 
 | |
|           if ('y' !== result.input) {
 | |
|             return getCurrentUserEmail();
 | |
|           }
 | |
| 
 | |
|           if (!sameProvider) {
 | |
|             return { node: email, type: 'email' };
 | |
|           }
 | |
| 
 | |
|           return form.ask({
 | |
|             label: "What's your recovery email (or cloud mail) address? ", type: 'email'
 | |
|           }).then(function (recoveryResult) {
 | |
|             return {
 | |
|               node: email
 | |
|             , type: 'name'
 | |
|             , recovery: recoveryResult.result || recoveryResult.input
 | |
|             };
 | |
|           });
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       return confirmCreateAccount();
 | |
|     }).then(function (user) {
 | |
|       // TODO skip if token exists locally
 | |
|       var email = (user.recovery || user.node);
 | |
|       form.println("Sending login code to '" + email + "'...");
 | |
|       return OAUTH3.authn.otp(oauth3._providerDirectives, { email: email }).then(function (otpResult) {
 | |
|         return form.ask({
 | |
|           label: "What's your login code? "
 | |
|         , help: "(it was sent to '" + email + "' and looks like 1234-5678-9012)"
 | |
|           // onkeyup
 | |
|           // ondebounce
 | |
|           // onchange
 | |
|           // regexp // html5 name?
 | |
|         , onReturnAsync: function (rs, ws, input/*, ch*/) {
 | |
|             var formatted = input.toLowerCase().replace(/[^\d]+/g, '');
 | |
| 
 | |
|             if (12 !== formatted.length) {
 | |
|               return form.PromiseA.reject(new Error("invalid code please try again in the format xxxx-yyyy-zzzz"));
 | |
|             }
 | |
| 
 | |
|             formatted = formatted.match(/.{4,4}/g).join('-');
 | |
| 
 | |
|             if (14 !== formatted.split('').length) {
 | |
|               return form.PromiseA.reject(new Error("invalid code '" + formatted + "', please try again xxxx-yyyy-zzzz"));
 | |
|             }
 | |
| 
 | |
|             var data = {
 | |
|               username: email
 | |
|             , username_type: 'email'
 | |
|             , client_id: OAUTH3.uri.normalize(oauth3._providerDirectives.issuer)
 | |
|             , client_uri: OAUTH3.uri.normalize(oauth3._providerDirectives.issuer)
 | |
|             , otp_code: formatted
 | |
|             , otp_uuid: otpResult.data.uuid
 | |
|             };
 | |
| 
 | |
|             // returns session instead of input
 | |
|             var colors = require('colors');
 | |
|             form.setStatus(colors.dim("authenticating with server..."));
 | |
|             return OAUTH3.authn.resourceOwnerPassword(oauth3._providerDirectives, data).then(function (result) {
 | |
|               return result;
 | |
|             }, function (/*err*/) {
 | |
|               // TODO test error
 | |
|               return form.PromiseA.reject(new Error("The code '" + formatted + "' is mistyped or incorrect. Double check and try again."));
 | |
|             });
 | |
|           }
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   function getCurrentUserEmail() {
 | |
|     return form.ask({
 | |
|       label: "What's your email (or cloud mail) address? ", type: 'email', value: opts.email
 | |
|     }).then(function (emailResult) {
 | |
|       opts.email = undefined;
 | |
|       email = (emailResult.result || emailResult.input);
 | |
|       var emailParts = email.split('@');
 | |
|       var domain = emailParts[1];
 | |
|       username = emailParts[0];
 | |
|       providerUrl = 'https://' + domain;
 | |
|       providerUri = domain;
 | |
| 
 | |
|       var urlObj = url.parse(providerUrl);
 | |
|       // TODO get unique client id for bootstrapping app
 | |
|       oauth3 = OAUTH3.create(urlObj);
 | |
|       return oauth3.setProvider(domain).then(function () {
 | |
|         sameProvider = true;
 | |
|         // ignore
 | |
|       }, function () {
 | |
|         function askOauth3Url() {
 | |
|           return form.ask({
 | |
|             label: "What's your OAuth3 Provider URL? ", type: 'url', value: opts.providerUri
 | |
|           }).then(function (urlResult) {
 | |
|             opts.providerUri = undefined;
 | |
|             providerUrl = urlResult.result || urlResult.input;
 | |
|             providerUri = OAUTH3.uri.normalize(providerUrl);
 | |
| 
 | |
|             var urlObj = url.parse(providerUrl);
 | |
|             // TODO get unique client id for bootstrapping app
 | |
|             oauth3 = OAUTH3.create(urlObj);
 | |
|             return oauth3.setProvider(providerUri).then(function () {
 | |
|               // ignore
 | |
|             }, function (err) {
 | |
|               form.println(err.stack || err.message || err.toString());
 | |
|               return askOauth3Url();
 | |
|             });
 | |
|           });
 | |
|         }
 | |
| 
 | |
|         return askOauth3Url();
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   return getCurrentUserEmail().then(function () {
 | |
|     return OAUTH3._hooks.meta.get(email).then(function (id) {
 | |
|       if (!id) {
 | |
|         return null;
 | |
|       }
 | |
|       return OAUTH3._hooks.sessions.get(providerUri, id).then(function (session) {
 | |
|         if (session) {
 | |
|           return session;
 | |
|         }
 | |
|         return null;
 | |
|       });
 | |
|     });
 | |
|   }).then(function (session) {
 | |
|     if (session) {
 | |
|       return session;
 | |
|     }
 | |
| 
 | |
|     return getSession().then(function (sessionResult) {
 | |
|       var session = sessionResult.result;
 | |
|       var id = require('crypto').createHash('sha256').update(session.token.sub || '').digest('hex');
 | |
| 
 | |
|       return OAUTH3._hooks.sessions.set(providerUri, session, id).then(function (session) {
 | |
|         return OAUTH3._hooks.meta.set(email, id).then(function () {
 | |
|           return session;
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   }).then(function (session) {
 | |
|     oauth3.__session = session;
 | |
|     return oauth3;
 | |
|   });
 | |
| };
 |