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 @@
+
+
+
+
+
+
+
+
+
+ Use the following password to authenticate against the app:
+
+
{{ appPasswordAdd.password.password }}
+
+
+
+
Please copy the password now. It won't be shown again for security purposes.
+
+
+
+
+
+
+
+
+
+
+
App Passwords
+
+
+
+
+
+
+
These passwords can be used as a security measure in mobile apps.
+
+
+
+ | Name |
+ App |
+ Actions |
+
+
+
+
+ |
+ {{ 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();
});