Compare commits

..

4 Commits

Author SHA1 Message Date
Julian Gassner
9d68c6353c
Merge da22e0777eebd779c2b7c87e3876aad32d5d3252 into c4df89df1f11ef8cb26ab375d161f6c82e72d401 2025-02-06 18:01:51 +01:00
Julian Gassner
da22e0777e Fixed a bug that prevented the mfa to be enabled 2025-02-06 17:01:46 +00:00
Julian Gassner
0bfd2f901d Add possibility to remove mfa 2025-02-06 16:47:56 +00:00
Jamie Curnow
c4df89df1f
Fix dashboard loading loop and freezing the page 2025-02-06 13:38:47 +10:00
11 changed files with 634 additions and 522 deletions

View File

@ -73,4 +73,25 @@ module.exports = {
.then(() => true);
});
},
disableMfaForUser: (data, userId) => {
return authModel
.query()
.where('user_id', userId)
.first()
.then((auth) => {
if (!auth) {
throw new error.AuthError('User not found.');
}
return auth.verifyPassword(data.secret)
.then((valid) => {
if (!valid) {
throw new error.AuthError('Invalid password.');
}
return authModel
.query()
.where('user_id', userId)
.update({ mfa_enabled: false, mfa_secret: null });
});
});
},
};

View File

@ -1,12 +1,11 @@
const express = require('express');
const jwtdecode = require('../lib/express/jwt-decode');
const apiValidator = require('../lib/validator/api');
const internalToken = require('../internal/token');
const schema = require('../schema');
const internalMfa = require('../internal/mfa');
const qrcode = require('qrcode');
const speakeasy = require('speakeasy');
const userModel = require('../models/user');
const express = require('express');
const jwtdecode = require('../lib/express/jwt-decode');
const apiValidator = require('../lib/validator/api');
const schema = require('../schema');
const internalMfa = require('../internal/mfa');
const qrcode = require('qrcode');
const speakeasy = require('speakeasy');
const userModel = require('../models/user');
let router = express.Router({
caseSensitive: true,
@ -14,24 +13,6 @@ let router = express.Router({
mergeParams: true
});
router
.route('/')
.options((_, res) => {
res.sendStatus(204);
})
.get(async (req, res, next) => {
internalToken.getFreshToken(res.locals.access, {
expiry: (typeof req.query.expiry !== 'undefined' ? req.query.expiry : null),
scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null)
})
.then((data) => {
res.status(200)
.send(data);
})
.catch(next);
});
router
.route('/create')
.post(jwtdecode(), (req, res, next) => {
@ -71,12 +52,13 @@ router
router
.route('/enable')
.post(jwtdecode(), (req, res, next) => {
apiValidator(schema.getValidationSchema('/mfa', 'post'), req.body).then((params) => {
apiValidator(schema.getValidationSchema('/mfa/enable', 'post'), req.body).then((params) => {
internalMfa.enableMfaForUser(res.locals.access.token.getUserId(), params.token)
.then(() => res.status(200).send({ success: true }))
.catch(next);
}
);});
).catch(next);
});
router
.route('/check')
@ -86,4 +68,14 @@ router
.catch(next);
});
router
.route('/delete')
.delete(jwtdecode(), (req, res, next) => {
apiValidator(schema.getValidationSchema('/mfa/delete', 'delete'), req.body).then((params) => {
internalMfa.disableMfaForUser(params, res.locals.access.token.getUserId())
.then(() => res.status(200).send({ success: true }))
.catch(next);
}).catch(next);
});
module.exports = router;

View File

@ -0,0 +1,44 @@
{
"operationId": "disableMfa",
"summary": "Disable multi-factor authentication for a user",
"tags": [
"MFA"
],
"requestBody": {
"description": "Payload to disable MFA",
"required": true,
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"secret": {
"type": "string",
"minLength": 1
}
},
"required": [
"secret"
]
}
}
}
},
"responses": {
"200": {
"description": "MFA disabled successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
}
}
}
}
}
}
}

View File

@ -1,14 +1,15 @@
{
"operationId": "enableMfa",
"summary": "Enable multi-factor authentication for a user",
"tags": ["MFA"],
"tags": [
"MFA"
],
"requestBody": {
"description": "MFA Token Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"token": {
@ -16,7 +17,9 @@
"minLength": 1
}
},
"required": ["token"]
"required": [
"token"
]
}
}
}
@ -38,4 +41,4 @@
}
}
}
}
}

View File

@ -15,9 +15,14 @@
"$ref": "./paths/get.json"
}
},
"/mfa": {
"/mfa/enable": {
"post": {
"$ref": "./paths/mfa/post.json"
"$ref": "./paths/mfa/enable/post.json"
}
},
"/mfa/delete": {
"delete": {
"$ref": "./paths/mfa/delete/delete.json"
}
},
"/audit-log": {

View File

@ -211,6 +211,9 @@ module.exports = {
},
check: function () {
return fetch('get', 'mfa/check');
},
delete: function (secret) {
return fetch('delete', 'mfa/delete', {secret: secret});
}
},

View File

@ -4,444 +4,438 @@ const Tokens = require('./tokens');
module.exports = {
/**
* @param {String} route
* @param {Object} [options]
* @returns {Boolean}
*/
navigate: function (route, options) {
options = options || {};
Backbone.history.navigate(route.toString(), options);
return true;
},
/**
* @param {String} route
* @param {Object} [options]
* @returns {Boolean}
*/
navigate: function (route, options) {
options = options || {};
Backbone.history.navigate(route.toString(), options);
return true;
},
/**
* Login
*/
showLogin: function () {
window.location = '/login';
},
/**
* Login
*/
showLogin: function () {
window.location = '/login';
},
/**
* Users
*/
showUsers: function () {
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './users/main'], (App, View) => {
controller.navigate('/users');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Users
*/
showUsers: function () {
const controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './users/main'], (App, View) => {
controller.navigate('/users');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* User Form
*
* @param [model]
*/
showUserForm: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Form
*
* @param [model]
*/
showUserForm: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Permissions Form
*
* @param model
*/
showUserPermissions: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/permissions'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Permissions Form
*
* @param model
*/
showUserPermissions: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/permissions'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Password Form
*
* @param model
*/
showUserPasswordForm: function (model) {
if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
require(['./main', './user/password'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Password Form
*
* @param model
*/
showUserPasswordForm: function (model) {
if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
require(['./main', './user/password'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Delete Confirm
*
* @param model
*/
showUserDeleteConfirm: function (model) {
if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
require(['./main', './user/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Delete Confirm
*
* @param model
*/
showUserDeleteConfirm: function (model) {
if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
require(['./main', './user/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dashboard
*/
showDashboard: function () {
let controller = this;
/**
* Dashboard
*/
showDashboard: function () {
const controller = this;
require(['./main', './dashboard/main'], (App, View) => {
controller.navigate('/');
App.UI.showAppContent(new View());
});
},
require(['./main', './dashboard/main'], (App, View) => {
controller.navigate('/');
App.UI.showAppContent(new View());
});
},
/**
* Nginx Proxy Hosts
*/
showNginxProxy: function () {
if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
const controller = this;
/**
* Nginx Proxy Hosts
*/
showNginxProxy: function () {
if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
let controller = this;
require(['./main', './nginx/proxy/main'], (App, View) => {
controller.navigate('/nginx/proxy');
App.UI.showAppContent(new View());
});
}
},
require(['./main', './nginx/proxy/main'], (App, View) => {
controller.navigate('/nginx/proxy');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Proxy Host Form
*
* @param [model]
*/
showNginxProxyForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Proxy Host Form
*
* @param [model]
*/
showNginxProxyForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Proxy Host Delete Confirm
*
* @param model
*/
showNginxProxyDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Proxy Host Delete Confirm
*
* @param model
*/
showNginxProxyDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Redirection Hosts
*/
showNginxRedirection: function () {
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
const controller = this;
require(['./main', './nginx/redirection/main'], (App, View) => {
controller.navigate('/nginx/redirection');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Redirection Hosts
*/
showNginxRedirection: function () {
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
let controller = this;
/**
* Nginx Redirection Host Form
*
* @param [model]
*/
showNginxRedirectionForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
require(['./main', './nginx/redirection/main'], (App, View) => {
controller.navigate('/nginx/redirection');
App.UI.showAppContent(new View());
});
}
},
/**
* Proxy Redirection Delete Confirm
*
* @param model
*/
showNginxRedirectionDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Redirection Host Form
*
* @param [model]
*/
showNginxRedirectionForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Stream Hosts
*/
showNginxStream: function () {
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
const controller = this;
require(['./main', './nginx/stream/main'], (App, View) => {
controller.navigate('/nginx/stream');
App.UI.showAppContent(new View());
});
}
},
/**
* Proxy Redirection Delete Confirm
*
* @param model
*/
showNginxRedirectionDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Stream Form
*
* @param [model]
*/
showNginxStreamForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Stream Hosts
*/
showNginxStream: function () {
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
let controller = this;
/**
* Stream Delete Confirm
*
* @param model
*/
showNginxStreamDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
require(['./main', './nginx/stream/main'], (App, View) => {
controller.navigate('/nginx/stream');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Dead Hosts
*/
showNginxDead: function () {
if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
const controller = this;
require(['./main', './nginx/dead/main'], (App, View) => {
controller.navigate('/nginx/404');
App.UI.showAppContent(new View());
});
}
},
/**
* Stream Form
*
* @param [model]
*/
showNginxStreamForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dead Host Form
*
* @param [model]
*/
showNginxDeadForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Stream Delete Confirm
*
* @param model
*/
showNginxStreamDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dead Host Delete Confirm
*
* @param model
*/
showNginxDeadDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Dead Hosts
*/
showNginxDead: function () {
if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
let controller = this;
/**
* Help Dialog
*
* @param {String} title
* @param {String} content
*/
showHelp: function (title, content) {
require(['./main', './help/main'], function (App, View) {
App.UI.showModalDialog(new View({title: title, content: content}));
});
},
require(['./main', './nginx/dead/main'], (App, View) => {
controller.navigate('/nginx/404');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Access
*/
showNginxAccess: function () {
if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
const controller = this;
require(['./main', './nginx/access/main'], (App, View) => {
controller.navigate('/nginx/access');
App.UI.showAppContent(new View());
});
}
},
/**
* Dead Host Form
*
* @param [model]
*/
showNginxDeadForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Access List Form
*
* @param [model]
*/
showNginxAccessListForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dead Host Delete Confirm
*
* @param model
*/
showNginxDeadDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Access List Delete Confirm
*
* @param model
*/
showNginxAccessListDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Help Dialog
*
* @param {String} title
* @param {String} content
*/
showHelp: function (title, content) {
require(['./main', './help/main'], function (App, View) {
App.UI.showModalDialog(new View({title: title, content: content}));
});
},
/**
* Nginx Certificates
*/
showNginxCertificates: function () {
if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
const controller = this;
require(['./main', './nginx/certificates/main'], (App, View) => {
controller.navigate('/nginx/certificates');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Access
*/
showNginxAccess: function () {
if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
let controller = this;
/**
* Nginx Certificate Form
*
* @param [model]
*/
showNginxCertificateForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
require(['./main', './nginx/access/main'], (App, View) => {
controller.navigate('/nginx/access');
App.UI.showAppContent(new View());
});
}
},
/**
* Certificate Renew
*
* @param model
*/
showNginxCertificateRenew: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/renew'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Access List Form
*
* @param [model]
*/
showNginxAccessListForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Delete Confirm
*
* @param model
*/
showNginxCertificateDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Access List Delete Confirm
*
* @param model
*/
showNginxAccessListDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Test Reachability
*
* @param model
*/
showNginxCertificateTestReachability: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/test'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Certificates
*/
showNginxCertificates: function () {
if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
let controller = this;
/**
* Audit Log
*/
showAuditLog: function () {
const controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/main'], (App, View) => {
controller.navigate('/audit-log');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
require(['./main', './nginx/certificates/main'], (App, View) => {
controller.navigate('/nginx/certificates');
App.UI.showAppContent(new View());
});
}
},
/**
* Audit Log Metadata
*
* @param model
*/
showAuditMeta: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/meta'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Certificate Form
*
* @param [model]
*/
showNginxCertificateForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Settings
*/
showSettings: function () {
const controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './settings/main'], (App, View) => {
controller.navigate('/settings');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Certificate Renew
*
* @param model
*/
showNginxCertificateRenew: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/renew'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Settings Item Form
*
* @param model
*/
showSettingForm: function (model) {
if (Cache.User.isAdmin()) {
if (model.get('id') === 'default-site') {
require(['./main', './settings/default-site/main'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
}
},
/**
* Certificate Delete Confirm
*
* @param model
*/
showNginxCertificateDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Test Reachability
*
* @param model
*/
showNginxCertificateTestReachability: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/test'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Audit Log
*/
showAuditLog: function () {
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/main'], (App, View) => {
controller.navigate('/audit-log');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Audit Log Metadata
*
* @param model
*/
showAuditMeta: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/meta'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Settings
*/
showSettings: function () {
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './settings/main'], (App, View) => {
controller.navigate('/settings');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Settings Item Form
*
* @param model
*/
showSettingForm: function (model) {
if (Cache.User.isAdmin()) {
if (model.get('id') === 'default-site') {
require(['./main', './settings/default-site/main'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
}
},
/**
* Logout
*/
logout: function () {
Tokens.dropTopToken();
this.showLogin();
}
/**
* Logout
*/
logout: function () {
Tokens.dropTopToken();
this.showLogin();
}
};

View File

@ -6,85 +6,85 @@ const Helpers = require('../../lib/helpers');
const template = require('./main.ejs');
module.exports = Mn.View.extend({
template: template,
id: 'dashboard',
columns: 0,
template: template,
id: 'dashboard',
columns: 0,
stats: {},
stats: {},
ui: {
links: 'a'
},
ui: {
links: 'a'
},
events: {
'click @ui.links': function (e) {
e.preventDefault();
Controller.navigate($(e.currentTarget).attr('href'), true);
}
},
events: {
'click @ui.links': function (e) {
e.preventDefault();
Controller.navigate($(e.currentTarget).attr('href'), true);
}
},
templateContext: function () {
let view = this;
templateContext: function () {
const view = this;
return {
getUserName: function () {
return Cache.User.get('nickname') || Cache.User.get('name');
},
return {
getUserName: function () {
return Cache.User.get('nickname') || Cache.User.get('name');
},
getHostStat: function (type) {
if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
return Helpers.niceNumber(view.stats.hosts[type]);
}
getHostStat: function (type) {
if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
return Helpers.niceNumber(view.stats.hosts[type]);
}
return '-';
},
return '-';
},
canShow: function (perm) {
return Cache.User.isAdmin() || Cache.User.canView(perm);
},
canShow: function (perm) {
return Cache.User.isAdmin() || Cache.User.canView(perm);
},
columns: view.columns
};
},
columns: view.columns
};
},
onRender: function () {
let view = this;
onRender: function () {
const view = this;
if (typeof view.stats.hosts === 'undefined') {
Api.Reports.getHostStats()
.then(response => {
if (!view.isDestroyed()) {
view.stats.hosts = response;
view.render();
}
})
.catch(err => {
console.log(err);
});
}
},
Api.Reports.getHostStats()
.then(response => {
if (!view.isDestroyed()) {
view.stats.hosts = response;
view.render();
}
})
.catch(err => {
console.log(err);
});
},
/**
* @param {Object} [model]
*/
preRender: function (model) {
this.columns = 0;
/**
* @param {Object} [model]
*/
preRender: function (model) {
this.columns = 0;
// calculate the available columns based on permissions for the objects
// and store as a variable
const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
// calculate the available columns based on permissions for the objects
// and store as a variable
//let view = this;
let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
perms.map(perm => {
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
});
perms.map(perm => {
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
});
// Prevent double rendering on initial calls
if (typeof model !== 'undefined') {
this.render();
}
},
// Prevent double rendering on initial calls
if (typeof model !== 'undefined') {
this.render();
}
},
initialize: function () {
this.preRender();
this.listenTo(Cache.User, 'change', this.preRender);
}
initialize: function () {
this.preRender();
this.listenTo(Cache.User, 'change', this.preRender);
}
});

View File

@ -27,12 +27,22 @@
</div>
<div class="col-sm-12 col-md-12">
<label class="form-label mfa-label" style="display: none;"><%- i18n('mfa', 'mfa') %></label>
<button type="button" class="btn btn-info add-mfa"><%- i18n('mfa', 'add-mfa') %></button>
<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>

View File

@ -15,10 +15,14 @@ module.exports = Mn.View.extend({
cancel: 'button.cancel',
save: 'button.save',
error: '.secret-error',
addMfa: '.add-mfa',
mfaLabel: '.mfa-label', // added binding
mfaValidation: '.mfa-validation-container', // added binding
qrInstructions: '.qr-instructions' // added binding for instructions
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: {
@ -29,9 +33,9 @@ module.exports = Mn.View.extend({
let view = this;
let data = this.ui.form.serializeJSON();
// Save "mfa_validation" value and remove it from data
let mfaToken = data.mfa_validation;
delete data.mfa_validation;
delete data.mfa_password;
let show_password = this.model.get('email') === 'admin@example.com';
@ -73,9 +77,13 @@ module.exports = Mn.View.extend({
if (mfaToken) {
return App.Api.Mfa.enable(mfaToken)
.then(() => result);
.then(() => result)
.catch(err => {
view.ui.mfaError.text(err.message).show();
err.mfaHandled = true;
return Promise.reject(err);
});
}
console.log(result);
return result;
})
.then(result => {
@ -89,7 +97,9 @@ 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');
});
},
@ -106,6 +116,31 @@ module.exports = Mn.View.extend({
.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();
});
}
},
@ -143,16 +178,17 @@ module.exports = Mn.View.extend({
.then(response => {
if (response.active) {
view.ui.addMfa.hide();
view.ui.mfaLabel.hide();
view.ui.qrInstructions.hide();
view.ui.mfaValidation.hide();
// Remove required attribute if MFA is active & field is hidden
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.mfaLabel.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');
}
})

View File

@ -39,9 +39,13 @@
},
"mfa": {
"mfa": "Multi Factor Authentication",
"add-mfa": "Generate secret",
"mfa-add": "Add Multi Factor Authentication",
"mfa-remove": "Remove Multi Factor Authentication",
"mfa-setup-instruction": "Scan this QR code in your authenticator app to set up MFA and then enter the current MFA code in the input field.",
"mfa-token": "Multi factor authentication token"
"mfa-token": "Multi factor authentication token",
"confirm-password": "Please enter your password to confirm",
"enter-password": "Enter Password",
"confirm-remove-mfa": "Confirm Multi Factor Authentication removal"
},
"login": {
"title": "Login to your account",