403 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| angular.module('yololiumApp')
 | |
|   .controller('LoginController3', [
 | |
|     '$scope'
 | |
|   , '$q'
 | |
|   , '$timeout'
 | |
|   , '$modalInstance'
 | |
|   , 'Oauth3'
 | |
|   , 'DaplieApiSession'
 | |
|   , 'DaplieApiRequest'
 | |
|   , 'LdsAccount'
 | |
|   , 'myLoginOptions'
 | |
|   , function (
 | |
|       $scope
 | |
|     , $q
 | |
|     , $timeout
 | |
|     , $modalInstance
 | |
|     , Oauth3
 | |
|     , Oauth3ApiSession
 | |
|     , Oauth3ApiRequest
 | |
|     , LdsAccount
 | |
|     , opts
 | |
|     ) {
 | |
|     opts = opts || {};
 | |
|     var scope = this;
 | |
|     var secretMinLen = scope.secretMinLength = Oauth3ApiSession.secretMinLength;
 | |
|     //var usernameMinLen = Oauth3ApiSession.usernameMinLength;
 | |
|     var mySession;
 | |
| 
 | |
|     scope.hideSocial = opts.hideSocial;
 | |
|     scope.flashMessage = opts.flashMessage;
 | |
|     scope.flashMessageClass = opts.flashMessageClass;
 | |
| 
 | |
|     scope.delta = { localLogin: {} };
 | |
| 
 | |
|     function onDaplieLogin(oauth3Session) {
 | |
|       console.log('DEBUG onLogin');
 | |
|       console.log(oauth3Session);
 | |
|       // TODO if there is not a default account, show user-switching screen
 | |
|       // this will close both on user/pass and social login
 | |
|       scope.flashMessage = "You've logged in. Please wait while we download some ward and stake data...";
 | |
|       scope.flashMessageClass = 'alert-info';
 | |
|       return Oauth3ApiRequest.profile(oauth3Session).then(function (profile) {
 | |
|         console.log('DEBUG profile');
 | |
|         console.log(profile);
 | |
| 
 | |
|         $modalInstance.close(oauth3Session);
 | |
|         /*
 | |
|         return LdsAccount.verifyAccount(ldsSession, profile).then(function () {
 | |
|           scope.flashMessage = "Done!";
 | |
|           scope.flashMessageClass = 'alert-success';
 | |
|           console.log('DEBUG verifiedAccount');
 | |
|           $modalInstance.close(ldsSession);
 | |
|         });
 | |
|         */
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     function handleLoginException(err) {
 | |
|       scope.formState = 'login';
 | |
| 
 | |
|       console.error("[Uknown Error] Either 'resource owner password' or 'delegated' login");
 | |
|       console.warn(err.stack);
 | |
|       scope.flashMessage = err.message || err.code;
 | |
|       scope.flashMessageClass = "alert-danger";
 | |
|     }
 | |
| 
 | |
|     function handleSuccess(session) {
 | |
|       return Oauth3ApiSession.requireAccount(session).then(onDaplieLogin, function (err) {
 | |
|         if ('E_NO_LDSACCOUNT' !== err.code) {
 | |
|           throw err;
 | |
|         }
 | |
| 
 | |
|         scope.hideSocial = true;
 | |
|         scope.flashMessage = err.message;
 | |
|         scope.flashMessageClass = "alert-warning";
 | |
|       }).catch(handleLoginException);
 | |
|     }
 | |
| 
 | |
|     function handleLoginError(err) {
 | |
|       console.error('handleLoginError');
 | |
|       console.error(err);
 | |
|       console.warn(err.stack);
 | |
|       if (!err.message) {
 | |
|         throw err;
 | |
|       }
 | |
| 
 | |
|       scope.formState = 'login';
 | |
| 
 | |
|       scope.flashMessage = err.message || err.code;
 | |
|       scope.flashMessageClass = "alert-warning";
 | |
| 
 | |
|       throw err;
 | |
|     }
 | |
| 
 | |
|     scope.loginStrategies = [
 | |
|       { label: 'Facebook'
 | |
|       , name: 'facebook'
 | |
|       , faImage: ""
 | |
|       , faClass: "fa-facebook"
 | |
|       , btnClass: "btn-facebook"
 | |
|       , login: function () {
 | |
|           var providerUri = (window.location.host +  window.location.pathname).replace(/\/$/g, '');
 | |
|           var providerApiUri = 'https://oauth3.org';
 | |
| 
 | |
|           return Oauth3ApiSession.login({
 | |
|             providerUri: providerUri // 'daplie.com/connect'
 | |
|           , scope: [ 'email' ]
 | |
|           , redirectUri: 'https://' + providerUri + '/oauth3.html'
 | |
|           , popup: true
 | |
|             // TODO XXX use whatever variable it is for apiHost
 | |
|           , authorizationRedirect: providerApiUri + '/api/org.oauth3.consumer/authorization_redirect/facebook.com'
 | |
|           }).then(handleSuccess, handleLoginError).catch(handleLoginException);
 | |
|         }
 | |
|       }
 | |
|     , { label: 'Google+'
 | |
|       , name: 'google'
 | |
|       , faImage: ""
 | |
|       , faClass: "fa-google-plus"
 | |
|       , btnClass: "btn-google-plus"
 | |
|       , login: function () {
 | |
|           return Oauth3ApiSession.logins.authorizationRedirect({
 | |
|             providerUri: 'google.com'
 | |
|           , scope: [ 'https://www.googleapis.com/auth/plus.login' ]
 | |
|           , redirectUri: 'https://beta.ldsconnect.org/oauth3.html'
 | |
|           , popup: true
 | |
|           }).then(handleSuccess, handleLoginError).catch(handleLoginException);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     /*
 | |
|     , { label: 'LDS.org Account'
 | |
|       , faImage: "images/moroni-128px.png"
 | |
|       , faClass: ""
 | |
|       , btnClass: "openid"
 | |
|       , login: scope.loginWithLdsconnect
 | |
|       }
 | |
|     */
 | |
|     ];
 | |
| 
 | |
|     scope.hideSocial = opts.hideSocial;
 | |
|     scope.flashMessage = opts.flashMessage;
 | |
|     scope.flashMessageClass = opts.flashMessageClass;
 | |
| 
 | |
|     // This dialog is opened to update necessary account details
 | |
|     // It should be passed options to inform the dialog which
 | |
|     // missing fields are necessary to show at this time
 | |
|     //
 | |
|     // Examples:
 | |
|     // we want to get facebook but haven't connected yet, so we should show the connection dialog
 | |
|     // we just logged in for the first time and don't have an account or a local login
 | |
|     function onLogout() {
 | |
|       scope.session = null;
 | |
|       scope.account = null;
 | |
|       scope.accounts = null;
 | |
|     }
 | |
| 
 | |
|     function onLogin(session) {
 | |
|       // session is always ensured as part of login
 | |
|       mySession = session;
 | |
| 
 | |
|       var defaultAction = '';
 | |
|       var emails = [];
 | |
| 
 | |
|       scope.account = scope.account || mySession.account || {};
 | |
|       if (scope.account.id) {
 | |
|         defaultAction = 'update';
 | |
|       }
 | |
| 
 | |
|       scope.formAction = scope.formAction || defaultAction;
 | |
| 
 | |
|       scope.account = scope.account || {};
 | |
|       scope.delta = scope.delta || {};
 | |
|       scope.deltaEmail = { type: 'email' };
 | |
|       scope.deltaPhone = { type: 'phone' };
 | |
|       scope.delta.localLogin = scope.delta.localLogin || {};
 | |
|       scope.logins = mySession.logins.map(function (login) {
 | |
|         return {
 | |
|           comment: ('local' === (login.provider || login.type)) ? (login.uid || 'username') : login.provider
 | |
|         , id: login.id
 | |
|         , uid: login.uid
 | |
|         , provider: login.provider
 | |
|         , type: login.type
 | |
|         , link: true
 | |
|         };
 | |
|       });
 | |
| 
 | |
|       mySession.logins.some(function (login) {
 | |
|         if ('local' === (login.type || login.provider)) {
 | |
|           scope.account.localLoginId = login.id;
 | |
|           scope.delta.localLogin.id = login.id;
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       // login is always ensured prior to account
 | |
|       mySession.logins.forEach(function (l) {
 | |
|         (l.emails||[]).forEach(function (email) {
 | |
|           if (email && email.value) {
 | |
|             emails.push(email);
 | |
|           }
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       // TODO combo box for creating new logins
 | |
|       scope.emails = emails;
 | |
|       scope.deltaEmail.node = (emails[0] || {}).value;
 | |
| 
 | |
|       if (!scope.deltaEmail.node) {
 | |
|         mySession.logins.some(function (login) {
 | |
|           scope.deltaEmail.node = (login.emails && login.emails[0] || {}).value;
 | |
|           return scope.deltaEmail.node;
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|       if (mySession.logins.length) {
 | |
|         scope.formAction = 'create';
 | |
|       }
 | |
|       */
 | |
| 
 | |
|       // TODO check mySession
 | |
|       if (mySession.logins.length >= 2) {
 | |
|         scope.recoverable = true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     scope.checkTotp = function (nodeObj) {
 | |
|       var tokenLen = 6;
 | |
|       var len = (nodeObj.totp||'').replace(/\D/g, '').length;
 | |
| 
 | |
|       if (len === tokenLen) {
 | |
|         nodeObj.totpMessage = 'So far, so good.';
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       nodeObj.totpMessage = 'Token is too short '
 | |
|         + len + '/' + tokenLen
 | |
|         + ' (needs to be ' + tokenLen + '+ digits)'
 | |
|         ;
 | |
|     };
 | |
|     scope.checkSecret = function (nodeObj) {
 | |
|       var len = (nodeObj.secret||'').length;
 | |
|       var meetsLen = (len >= secretMinLen);
 | |
| 
 | |
|       if (meetsLen) {
 | |
|         nodeObj.secretMessage = 'Login when ready, captain!';
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       nodeObj.secretMessage = 'Passphrase is too short '
 | |
|         + len + '/' + secretMinLen
 | |
|         + ' (needs to be ' + secretMinLen + '+ characters)'
 | |
|         ;
 | |
|     };
 | |
| 
 | |
|     scope.submitLogin = function (nodeObj) {
 | |
|       var promise;
 | |
|       scope.flashMessage = "";
 | |
|       scope.flashMessageClass = "alert-danger";
 | |
| 
 | |
|       if (scope._loginPromise) {
 | |
|         promise = scope._loginPromise;
 | |
|       } else {
 | |
|         promise = $q.when();
 | |
|       }
 | |
| 
 | |
|       // TODO this must be implemented in all platforms
 | |
|       nodeObj.secret = nodeObj.secret.trim();
 | |
| 
 | |
|       console.log('submitLogin nodeObj', nodeObj);
 | |
|       if ('create' === scope.formState) {
 | |
|         nodeObj.kdf = 'PBKDF2';
 | |
|         nodeObj.bits = 128;
 | |
|         nodeObj.algo = 'SHA-256';
 | |
|         nodeObj.iter = Math.floor(Math.random() * 100) + 1001;
 | |
|       }
 | |
| 
 | |
|       promise = window.getProofOfSecret(nodeObj);
 | |
| 
 | |
|       if ('create' === scope.formState) {
 | |
|         // TODO redo soft check
 | |
|         // TODO move to OAuth3
 | |
|         return promise.then(function (kdf) {
 | |
|           kdf.secret = null;
 | |
|           //console.log('Oauth3.logins', Object.keys(Oauth3.logins));
 | |
|           return Oauth3ApiSession.createLogin(kdf.node, kdf.type || 'email', null, {
 | |
|             kdf: kdf.kdf
 | |
|           , algo: kdf.algo
 | |
|           //, hash: kdf.algo
 | |
|           , iter: kdf.iter
 | |
|           , bits: kdf.bits
 | |
|           , salt: kdf.salt
 | |
|           , proof: kdf.proof
 | |
|           //, shadow: kdf.proof
 | |
|           }, null);
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       return promise.then(function (kdf) {
 | |
|         // TODO change the state to authenticating for social logins as well
 | |
|         scope.formState = 'authenticating';
 | |
|         // ALL THE SCOPES!!!
 | |
|         // TODO
 | |
|         //return Oauth3.requests.resourceOwnerPassword('https://daplie.com/connect', nodeObj.node, kdf.proof, { scope: '*', appId: 'ID__b5db805e27cc27a0ee8eddf42f46' }).then(function (session) {
 | |
|         return Oauth3ApiSession.login({
 | |
|           username: nodeObj.node
 | |
|         //, usernameType: nodeObj.nodeType
 | |
|         , password: kdf.proof
 | |
|         , totp: nodeObj.totp
 | |
|         //, mfa: kdf.mfa
 | |
|         , scope: '*'
 | |
|         }).then(function (session) {
 | |
|           console.log('[session]', session);
 | |
|           if (session.error) {
 | |
|             throw new Error("no oauth3 session");
 | |
|           }
 | |
|           return Oauth3ApiSession.requireAccount(session).then(onDaplieLogin);
 | |
|         }, handleLoginError).catch(handleLoginException);
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     scope.checkLdsLogin = function (nodeObj) {
 | |
|       var username;
 | |
|       var myPromise;
 | |
|       var nameError;
 | |
| 
 | |
|       scope.formState = 'login';
 | |
|       nodeObj.claimable = false;
 | |
|       nodeObj.message = '';
 | |
|       scope.flashMessage = '';
 | |
|       scope.flashMessageClass = "alert-warning";
 | |
|       username = nodeObj.node;
 | |
| 
 | |
|       $timeout.cancel(scope._loginTimeout);
 | |
| 
 | |
|       if (!username || 'null' === username || 'undefined' === username) {
 | |
|         myPromise = false;
 | |
|         scope.formState = 'invalid';
 | |
|         nodeObj.message = '';
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // returns true or error object
 | |
|       nameError = Oauth3ApiSession.validateUsername(nodeObj.node);
 | |
|       if (nameError.message) {
 | |
|         myPromise = false;
 | |
|         scope.formState = 'invalid';
 | |
|         nodeObj.message = nameError.message;
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       nodeObj.message = 'Checking username...';
 | |
|       myPromise = scope._loginTimeout = $timeout(function () {
 | |
|         scope._loginPromise = Oauth3ApiSession.getMeta(nodeObj.node, nodeObj.type || 'email').then(function (kdf) {
 | |
|           // kdf
 | |
|           nodeObj.kdf = kdf.kdf;
 | |
|           nodeObj.algo = kdf.algo;
 | |
|           nodeObj.bits = kdf.bits;
 | |
|           nodeObj.iter = kdf.iter;
 | |
|           nodeObj.salt = kdf.salt;
 | |
|           scope.showTotp = kdf.totpEnabledAt || kdf.totpEnabled || kdf.totp;
 | |
| 
 | |
|           myPromise = false;
 | |
|           nodeObj.message = "'" + username + "' is already registered."
 | |
|             + ' Welcome back!'
 | |
|             ;
 | |
|           scope.formState = 'login';
 | |
|         }, function () {
 | |
|           // TODO test that error is simply not exists
 | |
|           // and not a server error
 | |
|           if (myPromise !== scope._loginTimeout) {
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           scope.formState = 'create';
 | |
|           nodeObj.message = "'" + username + "' is available!";
 | |
|         }).catch(function (err) {
 | |
|           nodeObj.message = '';
 | |
| 
 | |
|           scope.formState = 'login';
 | |
|           scope.flashMessage = "[Uknown Error] " + err.message
 | |
|             + " (might need to wait a minute and try again)";
 | |
|           scope.flashMessageClass = "alert-danger";
 | |
|           throw err;
 | |
|         });
 | |
|       }, 250);
 | |
| 
 | |
|       return scope._loginTimeout;
 | |
|     };
 | |
| 
 | |
|     //
 | |
|     // Begin
 | |
|     //
 | |
|     //Oauth3ApiSession.onLogin($scope, onDaplieLogin);
 | |
|     Oauth3ApiSession.onLogout($scope, onLogout);
 | |
|     //Oauth3ApiSession.checkSession().then(onDaplieLogin, onLogout);
 | |
|     if (false) {
 | |
|       // prevent lint warnings until I figure out how I'll use this
 | |
|       onLogin();
 | |
|     }
 | |
|   }]);
 |