FEAT: Add Open ID Connect authentication method

* add `oidc-config` setting allowing an admin user to configure parameters
* modify login page to show another button when oidc is configured
* add dependency `openid-client` `v5.4.0`
* add backend route to process "OAuth2 Authorization Code" flow
  initialisation
* add backend route to process callback of above flow
* sign in the authenticated user with internal jwt token if internal
  user with email matching the one retrieved from oauth claims exists

Note: Only Open ID Connect Discovery is supported which most modern
Identity Providers offer.

Tested with Authentik 2023.2.2 and Keycloak 18.0.2
This commit is contained in:
Marcell FÜLÖP
2023-02-24 08:39:21 +00:00
committed by Marcell Fülöp
parent 5920b0cf5e
commit caeb2934f0
17 changed files with 441 additions and 7 deletions

View File

@ -9,6 +9,14 @@
<% if (id === 'default-site') { %>
<%- i18n('settings', 'default-site-' + value) %>
<% } %>
<% if (id === 'oidc-config' && meta && meta.name && meta.clientID && meta.clientSecret && meta.issuerURL && meta.redirectURL) { %>
<%- meta.name %>
<% if (!meta.enabled) { %>
(Disabled)
<% } %>
<% } else if (id === 'oidc-config') { %>
Not configured
<% } %>
</div>
</td>
<td class="text-right">

View File

@ -0,0 +1,47 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('settings', id) %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<form>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<div class="form-label"><%- description %></div>
<div class="custom-controls-stacked">
<div class="form-group">
<div class="form-label">Name</div>
<input class="form-control name-input" name="meta[name]" placeholder="" type="text" value="<%- meta && typeof meta.name !== 'undefined' ? meta.name : '' %>">
</div>
<div class="form-group">
<div class="form-label">Client ID</div>
<input class="form-control id-input" name="meta[clientID]" placeholder="" type="text" value="<%- meta && typeof meta.clientID !== 'undefined' ? meta.clientID : '' %>">
</div>
<div class="form-group">
<div class="form-label">Client Secret</div>
<input class="form-control secret-input" name="meta[clientSecret]" placeholder="" type="text" value="<%- meta && typeof meta.clientSecret !== 'undefined' ? meta.clientSecret : '' %>">
</div>
<div class="form-group">
<div class="form-label">Issuer URL</div>
<input class="form-control issuer-input" name="meta[issuerURL]" placeholder="https://" type="url" value="<%- meta && typeof meta.issuerURL !== 'undefined' ? meta.issuerURL : '' %>">
</div>
<div class="form-group">
<div class="form-label">Redirect URL</div>
<input class="form-control redirect-url-input" name="meta[redirectURL]" placeholder="https://" type="url" value="<%- meta && typeof meta.redirectURL !== 'undefined' ? meta.redirectURL : '' %>">
</div>
<div class="form-group">
<div class="form-label">Enabled</div>
<input class="form-check enabled-input" name="meta[enabled]" placeholder="" type="checkbox" <%- meta && typeof meta.enabled !== 'undefined' && meta.enabled === true ? 'checked="checked"' : '' %> >
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
</div>
</div>

View File

@ -0,0 +1,47 @@
const Mn = require('backbone.marionette');
const App = require('../../main');
const template = require('./main.ejs');
require('jquery-serializejson');
require('selectize');
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog',
ui: {
form: 'form',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
},
events: {
'click @ui.save': function (e) {
e.preventDefault();
if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
return;
}
let view = this;
let data = this.ui.form.serializeJSON();
data.id = this.model.get('id');
if (data.meta.enabled) {
data.meta.enabled = data.meta.enabled === "on" || data.meta.enabled === "true";
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
App.Api.Settings.update(data)
.then(result => {
view.model.set(result);
App.UI.closeModal();
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});