diff --git a/src/js/client.js b/src/js/client.js index be4bf24d7..2fa99606e 100644 --- a/src/js/client.js +++ b/src/js/client.js @@ -111,7 +111,8 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N id: null, username: null, email: null, - admin: false + admin: false, + twoFactorAuthenticationEnabled: false }; this._config = { apiServerOrigin: null, @@ -220,6 +221,7 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N this._userInfo.fallbackEmail = userInfo.fallbackEmail; this._userInfo.displayName = userInfo.displayName; this._userInfo.admin = !!userInfo.admin; + this._userInfo.twoFactorAuthenticationEnabled = userInfo.twoFactorAuthenticationEnabled; this._userInfo.gravatar = 'https://www.gravatar.com/avatar/' + md5.createHash(userInfo.email) + '.jpg?s=24&d=mm'; this._userInfo.gravatarHuge = 'https://www.gravatar.com/avatar/' + md5.createHash(userInfo.email) + '.jpg?s=128&d=mm'; }; @@ -725,7 +727,7 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N if (status !== 201 || typeof data !== 'object') return callback(new ClientError(status, data)); that.setToken(data.token); - that.setUserInfo({ username: username, email: email, admin: true }); + that.setUserInfo({ username: username, email: email, admin: true, twoFactorAuthenticationEnabled: false }); callback(null, data.activated); }).error(defaultErrorHandler(callback)); @@ -930,6 +932,37 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N }).error(defaultErrorHandler(callback)); }; + Client.prototype.setTwoFactorAuthenticationSecret = function (callback) { + var data = {}; + + post('/api/v1/profile/twofactorauthentication', data).success(function(data, status) { + if (status !== 201) return callback(new ClientError(status, data)); + callback(null, data); + }).error(defaultErrorHandler(callback)); + }; + + Client.prototype.enableTwoFactorAuthentication = function (totpToken, callback) { + var data = { + totpToken: totpToken + }; + + post('/api/v1/profile/twofactorauthentication/enable', data).success(function(data, status) { + if (status !== 202) return callback(new ClientError(status, data)); + callback(null); + }).error(defaultErrorHandler(callback)); + }; + + Client.prototype.disableTwoFactorAuthentication = function (password, callback) { + var data = { + password: password + }; + + post('/api/v1/profile/twofactorauthentication/disable', data).success(function(data, status) { + if (status !== 202) return callback(new ClientError(status, data)); + callback(null); + }).error(defaultErrorHandler(callback)); + }; + Client.prototype.refreshUserInfo = function (callback) { var that = this; diff --git a/src/views/account.html b/src/views/account.html index 25ebf6863..593c11636 100644 --- a/src/views/account.html +++ b/src/views/account.html @@ -128,6 +128,41 @@ + + +
@@ -158,6 +193,10 @@ Password recovery email {{ user.fallbackEmail }} + + Two-Factor Authentication + not enabled +
@@ -192,4 +231,4 @@
- + \ No newline at end of file diff --git a/src/views/account.js b/src/views/account.js index 8843b27df..e283eb7ee 100644 --- a/src/views/account.js +++ b/src/views/account.js @@ -10,6 +10,80 @@ angular.module('Application').controller('AccountController', ['$scope', 'Client $scope.activeClients = []; $scope.webadminClient = {}; + $scope.twoFactorAuthentication = { + busy: false, + error: null, + password: '', + totpToken: '', + secret: '', + qrcode: '', + + reset: function () { + $scope.twoFactorAuthentication.busy = false; + $scope.twoFactorAuthentication.error = null; + $scope.twoFactorAuthentication.password = ''; + $scope.twoFactorAuthentication.totpToken = ''; + $scope.twoFactorAuthentication.secret = ''; + $scope.twoFactorAuthentication.qrcode = ''; + + $scope.twoFactorAuthenticationEnableForm.$setUntouched(); + $scope.twoFactorAuthenticationEnableForm.$setPristine(); + }, + + show: function () { + $scope.twoFactorAuthentication.reset(); + + if ($scope.user.twoFactorAuthenticationEnabled) { + $('#twoFactorAuthenticationDisableModal').modal('show'); + } else { + $('#twoFactorAuthenticationEnableModal').modal('show'); + + Client.setTwoFactorAuthenticationSecret(function (error, result) { + if (error) return console.error(error); + + $scope.twoFactorAuthentication.secret = result.secret; + $scope.twoFactorAuthentication.qrcode = result.qrcode; + }); + } + }, + + enable: function() { + $scope.twoFactorAuthentication.busy = true; + + Client.enableTwoFactorAuthentication($scope.twoFactorAuthentication.totpToken, function (error) { + $scope.twoFactorAuthentication.busy = false; + + if (error) { + $scope.twoFactorAuthentication.error = error.message; + + $scope.twoFactorAuthentication.totpToken = ''; + $scope.twoFactorAuthenticationEnableForm.totpToken.$setPristine(); + $('#twoFactorAuthenticationTotpTokenInput').focus(); + + return; + } + + $('#twoFactorAuthenticationEnableModal').modal('hide'); + }); + }, + + disable: function () { + $scope.twoFactorAuthentication.busy = true; + + Client.disableTwoFactorAuthentication($scope.twoFactorAuthentication.totpToken, function (error) { + $scope.twoFactorAuthentication.busy = false; + + if (error) { + $scope.twoFactorAuthentication.error = error.message; + console.error(error); + return; + } + + $('#twoFactorAuthenticationDisableModal').modal('hide'); + }); + } + }; + $scope.passwordchange = { busy: false, error: {}, @@ -256,7 +330,7 @@ angular.module('Application').controller('AccountController', ['$scope', 'Client }); // setup all the dialog focus handling - ['passwordChangeModal', 'emailChangeModal', 'fallbackEmailChangeModal', 'displayNameChangeModal'].forEach(function (id) { + ['passwordChangeModal', 'emailChangeModal', 'fallbackEmailChangeModal', 'displayNameChangeModal', 'twoFactorAuthenticationEnableModal'].forEach(function (id) { $('#' + id).on('shown.bs.modal', function () { $(this).find("[autofocus]:first").focus(); });