diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js
index f888f16f9..2334c8fe0 100644
--- a/dashboard/src/js/client.js
+++ b/dashboard/src/js/client.js
@@ -1937,6 +1937,57 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
});
};
+ Client.prototype.getOidcClients = function (callback) {
+ get('/api/v1/oidc/clients', null, function (error, data, status) {
+ if (error) return callback(error);
+ if (status !== 200) return callback(new ClientError(status, data));
+
+ callback(null, data.clients);
+ });
+ };
+
+ Client.prototype.addOidcClient = function (id, secret, loginRedirectUri, logoutRedirectUri, callback) {
+ var data = {
+ id: id,
+ secret: secret,
+ loginRedirectUri: loginRedirectUri
+ };
+
+ if (logoutRedirectUri) data.logoutRedirectUri = logoutRedirectUri;
+
+ post('/api/v1/oidc/clients', data, null, function (error, data, status) {
+ if (error) return callback(error);
+ if (status !== 201) return callback(new ClientError(status, data));
+
+ callback(null);
+ });
+ };
+
+ Client.prototype.updateOidcClient = function (id, secret, loginRedirectUri, logoutRedirectUri, callback) {
+ var data = {
+ secret: secret,
+ loginRedirectUri: loginRedirectUri
+ };
+
+ if (logoutRedirectUri) data.logoutRedirectUri = logoutRedirectUri;
+
+ post('/api/v1/oidc/clients/' + id, data, null, function (error, data, status) {
+ if (error) return callback(error);
+ if (status !== 201) return callback(new ClientError(status, data));
+
+ callback(null);
+ });
+ };
+
+ Client.prototype.delOidcClient = function (id, callback) {
+ del('/api/v1/oidc/clients/' + id, null, function (error, data, status) {
+ if (error) return callback(error);
+ if (status !== 204) return callback(new ClientError(status, data));
+
+ callback(null);
+ });
+ };
+
Client.prototype.addAppPassword = function (identifier, name, callback) {
var data = {
identifier: identifier,
diff --git a/dashboard/src/js/index.js b/dashboard/src/js/index.js
index 66aef80f5..a7605c3f5 100644
--- a/dashboard/src/js/index.js
+++ b/dashboard/src/js/index.js
@@ -92,6 +92,9 @@ app.config(['$routeProvider', function ($routeProvider) {
}).when('/notifications', {
controller: 'NotificationsController',
templateUrl: 'views/notifications.html?<%= revision %>'
+ }).when('/oidc', {
+ controller: 'OidcController',
+ templateUrl: 'views/oidc.html?<%= revision %>'
}).when('/settings', {
controller: 'SettingsController',
templateUrl: 'views/settings.html?<%= revision %>'
diff --git a/dashboard/src/views/oidc.html b/dashboard/src/views/oidc.html
new file mode 100644
index 000000000..e5f8ae78b
--- /dev/null
+++ b/dashboard/src/views/oidc.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+ Add new OpenID connect client settings.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This will disconnect all external OpenID apps from this Cloudron using this client ID.
+
+
+
+
+
+
+
+
+
+
OpenID Provider
+
+
+
+
+
+
+
+
+ | Discovery URL |
+ https://{{ config.adminDomain }}/.well-known/openid-configuration |
+
+
+ | Auth Endpoint |
+ https://{{ config.adminFqdn }}/openid/auth |
+
+
+ | Token Endpoint |
+ https://{{ config.adminFqdn }}/openid/token |
+
+
+ | Keys Endpoint |
+ https://{{ config.adminFqdn }}/openid/jwks |
+
+
+ | Profile Endpoint |
+ https://{{ config.adminFqdn }}/openid/me |
+
+
+
+
+
+
+
+
+
+
+
Clients
+
+
+
+
+
+
+
+
+
+ | Client ID |
+ Client Secret |
+ {{ 'main.actions' | tr }} |
+
+
+
+
+ | No clients yet |
+
+
+ |
+ {{ client.id }}
+ |
+
+ {{ client.secret }}
+ |
+
+
+ |
+
+
+
+
+
+
+
+
diff --git a/dashboard/src/views/oidc.js b/dashboard/src/views/oidc.js
new file mode 100644
index 000000000..cd03e8694
--- /dev/null
+++ b/dashboard/src/views/oidc.js
@@ -0,0 +1,104 @@
+'use strict';
+
+/* global angular */
+/* global $ */
+
+angular.module('Application').controller('OidcController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
+ Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
+
+ $scope.user = Client.getUserInfo();
+ $scope.config = Client.getConfig();
+ $scope.clients = [];
+
+ $scope.refreshClients = function () {
+ Client.getOidcClients(function (error, result) {
+ if (error) return console.error('Failed to load oidc clients', error);
+
+ $scope.clients = result;
+ });
+ };
+
+ $scope.clientAdd = {
+ busy: false,
+ error: {},
+ id: '',
+ secret: '',
+ loginRedirectUri: '',
+ logoutRedirectUri: '',
+
+ show: function () {
+ $scope.clientAdd.id = '';
+ $scope.clientAdd.secret = '';
+ $scope.clientAdd.loginRedirectUri = '';
+ $scope.clientAdd.logoutRedirectUri = '';
+ $scope.clientAdd.busy = false;
+ $scope.clientAdd.error = null;
+ $scope.clientAddForm.$setPristine();
+
+ $('#clientAddModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.clientAdd.busy = true;
+ $scope.clientAdd.error = {};
+
+ Client.addOidcClient($scope.clientAdd.id, $scope.clientAdd.secret, $scope.clientAdd.loginRedirectUri, $scope.clientAdd.logoutRedirectUri, function (error) {
+ if (error) {
+ if (error.statusCode === 409) {
+ $scope.clientAdd.error.id = 'Client ID already exists';
+ $('#clientId').focus();
+ } else {
+ console.error('Unable to add openid client.', error);
+ }
+
+ $scope.clientAdd.busy = false;
+
+ return;
+ }
+
+ $scope.refreshClients();
+ $scope.clientAdd.busy = false;
+
+ $('#clientAddModal').modal('hide');
+ });
+ }
+ };
+
+ $scope.deleteClient = {
+ busy: false,
+ error: {},
+ id: '',
+
+ show: function (clientId) {
+ $scope.deleteClient.busy = false;
+ $scope.deleteClient.id = clientId;
+
+ $('#clientDeleteModal').modal('show');
+ },
+
+ submit: function () {
+ Client.delOidcClient($scope.deleteClient.id, function (error) {
+ $scope.deleteClient.busy = false;
+
+ if (error) return console.error('Failed to delete openid client', error);
+
+ $scope.refreshClients();
+
+ $('#clientDeleteModal').modal('hide');
+ });
+ }
+ };
+
+ Client.onReady(function () {
+ $scope.refreshClients();
+ });
+
+ // setup all the dialog focus handling
+ ['clientAddModal', 'clientEditModal'].forEach(function (id) {
+ $('#' + id).on('shown.bs.modal', function () {
+ $(this).find('[autofocus]:first').focus();
+ });
+ });
+
+ $('.modal-backdrop').remove();
+}]);
diff --git a/src/oidc.js b/src/oidc.js
index edbec1012..8f1fa77c5 100644
--- a/src/oidc.js
+++ b/src/oidc.js
@@ -43,7 +43,7 @@ async function clientsAdd(id, secret, loginRedirectUri, logoutRedirectUri) {
assert.strictEqual(typeof loginRedirectUri, 'string');
assert.strictEqual(typeof logoutRedirectUri, 'string');
- const query = 'INSERT INTO oidcClients (id, secret, loginRedirectUri, logoutRedirectUri) VALUES (?, ?, ?)';
+ const query = 'INSERT INTO oidcClients (id, secret, loginRedirectUri, logoutRedirectUri) VALUES (?, ?, ?, ?)';
const args = [ id, secret, loginRedirectUri, logoutRedirectUri ];
const [error] = await safe(database.query(query, args));