mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-10-05 20:30:10 +00:00
Merge da22e0777e
into 79d28f03d0
This commit is contained in:
@@ -202,7 +202,49 @@ module.exports = {
|
||||
return fetch('get', '');
|
||||
},
|
||||
|
||||
Mfa: {
|
||||
create: function () {
|
||||
return fetch('post', 'mfa/create');
|
||||
},
|
||||
enable: function (token) {
|
||||
return fetch('post', 'mfa/enable', {token: token});
|
||||
},
|
||||
check: function () {
|
||||
return fetch('get', 'mfa/check');
|
||||
},
|
||||
delete: function (secret) {
|
||||
return fetch('delete', 'mfa/delete', {secret: secret});
|
||||
}
|
||||
},
|
||||
|
||||
Tokens: {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} identity
|
||||
* @param {String} secret
|
||||
* @param {String} token
|
||||
* @param {Boolean} wipe
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
loginWithMFA: function (identity, secret, mfaToken, wipe) {
|
||||
return fetch('post', 'tokens', {identity: identity, secret: secret, mfa_token: mfaToken})
|
||||
.then(response => {
|
||||
if (response.token) {
|
||||
if (wipe) {
|
||||
Tokens.clearTokens();
|
||||
}
|
||||
|
||||
// Set storage token
|
||||
Tokens.addToken(response.token);
|
||||
return response.token;
|
||||
} else {
|
||||
Tokens.clearTokens();
|
||||
throw(new Error('No token returned'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} identity
|
||||
|
@@ -25,6 +25,27 @@
|
||||
<div class="invalid-feedback secret-error"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<label class="form-label mfa-label"><%- i18n('mfa', 'mfa') %></label>
|
||||
<button type="button" class="btn btn-info mfa-add"><%- i18n('mfa', 'mfa-add') %></button>
|
||||
<button type="button" class="btn btn-danger mfa-remove" style="display: none;"><%- i18n('mfa', 'mfa-remove') %></button>
|
||||
<div class="mfa-remove-confirm-container" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('mfa', 'confirm-password') %></label>
|
||||
<input name="mfa_password" type="password" class="form-control mfa-remove-password-field" placeholder="<%- i18n('mfa', 'enter-password') %>">
|
||||
<div class="invalid-feedback mfa-error"></div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger mfa-remove-confirm"><%- i18n('mfa', 'confirm-remove-mfa') %></button>
|
||||
</div>
|
||||
<p class="qr-instructions" style="display: none;"><%- i18n('mfa', 'mfa-setup-instruction') %></p>
|
||||
<div class="mfa-validation-container" style="display: none;">
|
||||
<label class="form-label"><%- i18n('mfa', 'mfa-token') %> <span class="form-required">*</span></label>
|
||||
<input name="mfa_validation" type="text" class="form-control" placeholder="000000" value="">
|
||||
<div class="invalid-feedback mfa-error"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (isAdmin() && !isSelf()) { %>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-label"><%- i18n('roles', 'title') %></div>
|
||||
|
@@ -14,7 +14,15 @@ module.exports = Mn.View.extend({
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
error: '.secret-error'
|
||||
error: '.secret-error',
|
||||
mfaError: '.mfa-error',
|
||||
addMfa: '.mfa-add',
|
||||
mfaValidation: '.mfa-validation-container',
|
||||
qrInstructions: '.qr-instructions',
|
||||
removeMfa: '.mfa-remove',
|
||||
removeMfaConfirmContainer: '.mfa-remove-confirm-container',
|
||||
removeMfaConfirm: '.mfa-remove-confirm',
|
||||
removeMfaPassword: '.mfa-remove-password-field'
|
||||
},
|
||||
|
||||
events: {
|
||||
@@ -25,6 +33,10 @@ module.exports = Mn.View.extend({
|
||||
let view = this;
|
||||
let data = this.ui.form.serializeJSON();
|
||||
|
||||
let mfaToken = data.mfa_validation;
|
||||
delete data.mfa_validation;
|
||||
delete data.mfa_password;
|
||||
|
||||
let show_password = this.model.get('email') === 'admin@example.com';
|
||||
|
||||
// admin@example.com is not allowed
|
||||
@@ -62,6 +74,19 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
|
||||
view.model.set(result);
|
||||
|
||||
if (mfaToken) {
|
||||
return App.Api.Mfa.enable(mfaToken)
|
||||
.then(() => result)
|
||||
.catch(err => {
|
||||
view.ui.mfaError.text(err.message).show();
|
||||
err.mfaHandled = true;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.then(result => {
|
||||
App.UI.closeModal(function () {
|
||||
if (method === App.Api.Users.create) {
|
||||
// Show permissions dialog immediately
|
||||
@@ -72,9 +97,50 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.ui.error.text(err.message).show();
|
||||
if (!err.mfaHandled) {
|
||||
this.ui.error.text(err.message).show();
|
||||
}
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
},
|
||||
'click @ui.addMfa': function (e) {
|
||||
let view = this;
|
||||
App.Api.Mfa.create()
|
||||
.then(response => {
|
||||
view.ui.addMfa.replaceWith(`<img class="qr-code" src="${response.qrCode}" alt="QR Code">`);
|
||||
view.ui.qrInstructions.show();
|
||||
view.ui.mfaValidation.show();
|
||||
// Add required attribute once MFA is activated
|
||||
view.ui.mfaValidation.find('input[name="mfa_validation"]').attr('required', true);
|
||||
})
|
||||
.catch(err => {
|
||||
view.ui.error.text(err.message).show();
|
||||
});
|
||||
},
|
||||
'click @ui.removeMfa': function (e) {
|
||||
// Show confirmation section with a password field and confirm button
|
||||
this.ui.removeMfa.hide();
|
||||
this.ui.removeMfaConfirmContainer.show();
|
||||
},
|
||||
'click @ui.removeMfaConfirm': function (e) {
|
||||
let view = this;
|
||||
let password = view.ui.removeMfaPassword.val();
|
||||
if (!password) {
|
||||
view.ui.error.text('Password required to remove MFA').show();
|
||||
return;
|
||||
}
|
||||
App.Api.Mfa.delete(password)
|
||||
.then(() => {
|
||||
view.ui.addMfa.show();
|
||||
view.ui.qrInstructions.hide();
|
||||
view.ui.mfaValidation.hide();
|
||||
view.ui.removeMfaConfirmContainer.hide();
|
||||
view.ui.removeMfa.hide();
|
||||
view.ui.mfaValidation.find('input[name="mfa_validation"]').removeAttr('required');
|
||||
})
|
||||
.catch(err => {
|
||||
view.ui.mfaError.text(err.message).show();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -104,5 +170,30 @@ module.exports = Mn.View.extend({
|
||||
if (typeof options.model === 'undefined' || !options.model) {
|
||||
this.model = new UserModel.Model();
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
App.Api.Mfa.check()
|
||||
.then(response => {
|
||||
if (response.active) {
|
||||
view.ui.addMfa.hide();
|
||||
view.ui.qrInstructions.hide();
|
||||
view.ui.mfaValidation.hide();
|
||||
view.ui.removeMfa.show();
|
||||
view.ui.removeMfaConfirmContainer.hide();
|
||||
view.ui.mfaValidation.find('input[name="mfa_validation"]').removeAttr('required');
|
||||
} else {
|
||||
view.ui.addMfa.show();
|
||||
view.ui.qrInstructions.hide();
|
||||
view.ui.mfaValidation.hide();
|
||||
view.ui.removeMfa.hide();
|
||||
view.ui.removeMfaConfirmContainer.hide();
|
||||
view.ui.mfaValidation.find('input[name="mfa_validation"]').removeAttr('required');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.ui.error.text(err.message).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user