diff --git a/src/js/client.js b/src/js/client.js index 9db32842b..352b11aeb 100644 --- a/src/js/client.js +++ b/src/js/client.js @@ -1301,6 +1301,38 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }); }; + Client.prototype.addAppPassword = function (identifier, name, callback) { + var data = { + identifier: identifier, + name: name + }; + + post('/api/v1/app_passwords', data, null, function (error, data, status) { + if (error) return callback(error); + if (status !== 201) return callback(new ClientError(status, data)); + + callback(null, data); + }); + }; + + Client.prototype.getAppPasswords = function (callback) { + get('/api/v1/app_passwords', null, function (error, data, status) { + if (error) return callback(error); + if (status !== 200) return callback(new ClientError(status, data)); + + callback(null, data); + }); + }; + + Client.prototype.delAppPassword = function (id, callback) { + del('/api/v1/app_passwords/' + id, null, function (error, data, status) { + if (error) return callback(error); + if (status !== 204) return callback(new ClientError(status, data)); + + callback(null); + }); + }; + Client.prototype.update = function (options, callback) { var data = { skipBackup: !!options.skipBackup diff --git a/src/views/profile.html b/src/views/profile.html index 8197d6f55..6b9dae827 100644 --- a/src/views/profile.html +++ b/src/views/profile.html @@ -228,6 +228,53 @@ + + +
@@ -281,6 +328,44 @@
+
+ +
+

App Passwords

+
+ +
+
+
+
+

These passwords can be used as a security measure in mobile apps.

+ + + + + + + + + + + + + + + +
NameAppActions
+ {{ password.name }} + + {{ password.prettyIdentifier }} + + +
+
+
+
+
+

Sessions and API Tokens

diff --git a/src/views/profile.js b/src/views/profile.js index 772571f83..695bb95a9 100644 --- a/src/views/profile.js +++ b/src/views/profile.js @@ -7,6 +7,7 @@ angular.module('Application').controller('ProfileController', ['$scope', '$location', 'Client', function ($scope, $location, Client) { $scope.user = Client.getUserInfo(); $scope.config = Client.getConfig(); + $scope.apps = Client.getInstalledApps(); $scope.activeClients = []; $scope.webadminClient = {}; @@ -342,6 +343,105 @@ angular.module('Application').controller('ProfileController', ['$scope', '$locat } }; + $scope.appPasswordAdd = { + password: null, + name: '', + identifier: '', + busy: false, + error: {}, + + reset: function () { + $scope.appPasswordAdd.busy = false; + $scope.appPasswordAdd.password = null; + $scope.appPasswordAdd.error.name = null; + $scope.appPasswordAdd.name = ''; + $scope.appPasswordAdd.identifier = ''; + + $scope.appPasswordAddForm.$setUntouched(); + $scope.appPasswordAddForm.$setPristine(); + }, + + show: function () { + $scope.appPasswordAdd.reset(); + $('#appPasswordAddModal').modal('show'); + }, + + submit: function () { + $scope.appPasswordAdd.busy = true; + $scope.appPasswordAdd.password = {}; + + Client.addAppPassword($scope.appPasswordAdd.identifier, $scope.appPasswordAdd.name, function (error, result) { + if (error) { + if (error.statusCode === 400) { + $scope.appPasswordAdd.error.name = error.message; + $scope.appPasswordAddForm.name.$setPristine(); + $('#inputAppPasswordName').focus(); + } else { + console.error('Unable to create password.', error); + } + return; + } + + $scope.appPasswordAdd.busy = false; + $scope.appPasswordAdd.password = result; + + console.log(result); + $scope.appPassword.refresh(); + }); + } + }; + + $scope.appPassword = { + busy: false, + error: {}, + passwords: [], + identifiers: [], + + refresh: function () { + Client.getAppPasswords(function (error, result) { + if (error) console.error(error); + + $scope.appPassword.passwords = result.appPasswords || []; + $scope.appPassword.identifiers = [ + { id: 'mail', label: 'Email' }, + // { id: 'webadmin', label: 'Web Admin'} + ]; + var appsById = {}; + $scope.apps.forEach(function (app) { + if (!app.manifest.addons || !app.manifest.addons.ldap) return; + + appsById[app.id] = app; + if (app.label) { + $scope.appPassword.identifiers.push({ id: app.id, label: app.label + ' (' + app.fqdn + ')' }); + } else { + $scope.appPassword.identifiers.push({ id: app.id, label: app.fqdn }); + } + }); + + // setup prettyIdentifier for the UI + $scope.appPassword.passwords.forEach(function (password) { + if (password.identifier === 'mail') return password.prettyIdentifier = password.identifier; + var app = appsById[password.identifier]; + if (!app) return password.prettyIdentifier = password.identifier + ' (App not found)'; + + if (app.label) { + password.prettyIdentifier = app.label + ' (' + app.fqdn + ')'; + } else { + password.prettyIdentifier = app.fqdn; + } + }); + }); + }, + + del: function (id) { + Client.delAppPassword(id, function (error) { + if (error) console.error(error); + + $scope.appPassword.refresh(); + }); + } + }; + $scope.displayNameChange = { busy: false, error: {}, @@ -427,6 +527,8 @@ angular.module('Application').controller('ProfileController', ['$scope', '$locat Client.getOAuthClients(function (error, activeClients) { if (error) return console.error(error); + $scope.appPassword.refresh(); + asyncForEach(activeClients, refreshClientTokens, function () { $scope.webadminClient = activeClients.filter(function (c) { return c.id === 'cid-webadmin'; })[0]; $scope.apiClient = activeClients.filter(function (c) { return c.id === 'cid-sdk'; })[0]; @@ -455,7 +557,7 @@ angular.module('Application').controller('ProfileController', ['$scope', '$locat }; // setup all the dialog focus handling - ['passwordChangeModal', 'emailChangeModal', 'fallbackEmailChangeModal', 'displayNameChangeModal', 'twoFactorAuthenticationEnableModal', 'twoFactorAuthenticationDisableModal'].forEach(function (id) { + ['passwordChangeModal', 'appPasswordAddModal', 'emailChangeModal', 'fallbackEmailChangeModal', 'displayNameChangeModal', 'twoFactorAuthenticationEnableModal', 'twoFactorAuthenticationDisableModal'].forEach(function (id) { $('#' + id).on('shown.bs.modal', function () { $(this).find("[autofocus]:first").focus(); });