mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-06-14 00:14:27 +00:00
Compare commits
21 Commits
8d7ea556eb
...
9d68c6353c
Author | SHA1 | Date | |
---|---|---|---|
|
9d68c6353c | ||
|
da22e0777e | ||
|
0bfd2f901d | ||
|
c4df89df1f | ||
|
6228a54ecf | ||
|
d20e15125e | ||
|
34194e65d2 | ||
|
5d2a76adfe | ||
|
33906bfcdc | ||
|
6e51d819fa | ||
|
8aa173a732 | ||
|
35938db24b | ||
|
69f7920675 | ||
|
45fc63875c | ||
|
f3fee7d886 | ||
|
50f7bfc726 | ||
|
8fb9d9fec6 | ||
|
cea9a17218 | ||
|
6bbe7d4cc4 | ||
|
c6ab315165 | ||
|
2c1595756d |
97
backend/internal/mfa.js
Normal file
97
backend/internal/mfa.js
Normal file
@ -0,0 +1,97 @@
|
||||
const authModel = require('../models/auth');
|
||||
const error = require('../lib/error');
|
||||
const speakeasy = require('speakeasy');
|
||||
|
||||
module.exports = {
|
||||
validateMfaTokenForUser: (userId, token) => {
|
||||
return authModel
|
||||
.query()
|
||||
.where('user_id', userId)
|
||||
.first()
|
||||
.then((auth) => {
|
||||
if (!auth || !auth.mfa_enabled) {
|
||||
throw new error.AuthError('MFA is not enabled for this user.');
|
||||
}
|
||||
const verified = speakeasy.totp.verify({
|
||||
secret: auth.mfa_secret,
|
||||
encoding: 'base32',
|
||||
token: token,
|
||||
window: 2
|
||||
});
|
||||
if (!verified) {
|
||||
throw new error.AuthError('Invalid MFA token.');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
isMfaEnabledForUser: (userId) => {
|
||||
return authModel
|
||||
.query()
|
||||
.where('user_id', userId)
|
||||
.first()
|
||||
.then((auth) => {
|
||||
console.log(auth);
|
||||
if (!auth) {
|
||||
throw new error.AuthError('User not found.');
|
||||
}
|
||||
return auth.mfa_enabled === true;
|
||||
});
|
||||
},
|
||||
createMfaSecretForUser: (userId) => {
|
||||
const secret = speakeasy.generateSecret({ length: 20 });
|
||||
console.log(secret);
|
||||
return authModel
|
||||
.query()
|
||||
.where('user_id', userId)
|
||||
.update({
|
||||
mfa_secret: secret.base32
|
||||
})
|
||||
.then(() => secret);
|
||||
},
|
||||
enableMfaForUser: (userId, token) => {
|
||||
return authModel
|
||||
.query()
|
||||
.where('user_id', userId)
|
||||
.first()
|
||||
.then((auth) => {
|
||||
if (!auth || !auth.mfa_secret) {
|
||||
throw new error.AuthError('MFA is not set up for this user.');
|
||||
}
|
||||
const verified = speakeasy.totp.verify({
|
||||
secret: auth.mfa_secret,
|
||||
encoding: 'base32',
|
||||
token: token,
|
||||
window: 2
|
||||
});
|
||||
if (!verified) {
|
||||
throw new error.AuthError('Invalid MFA token.');
|
||||
}
|
||||
return authModel
|
||||
.query()
|
||||
.where('user_id', userId)
|
||||
.update({ mfa_enabled: true })
|
||||
.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 });
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
@ -4,6 +4,7 @@ const userModel = require('../models/user');
|
||||
const authModel = require('../models/auth');
|
||||
const helpers = require('../lib/helpers');
|
||||
const TokenModel = require('../models/token');
|
||||
const mfa = require('../internal/mfa'); // <-- added MFA import
|
||||
|
||||
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
|
||||
|
||||
@ -21,6 +22,8 @@ module.exports = {
|
||||
getTokenFromEmail: (data, issuer) => {
|
||||
let Token = new TokenModel();
|
||||
|
||||
console.log(data);
|
||||
|
||||
data.scope = data.scope || 'user';
|
||||
data.expiry = data.expiry || '1d';
|
||||
|
||||
@ -41,34 +44,66 @@ module.exports = {
|
||||
.then((auth) => {
|
||||
if (auth) {
|
||||
return auth.verifyPassword(data.secret)
|
||||
.then((valid) => {
|
||||
.then(async (valid) => {
|
||||
if (valid) {
|
||||
|
||||
if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
|
||||
// The scope requested doesn't exist as a role against the user,
|
||||
// you shall not pass.
|
||||
throw new error.AuthError('Invalid scope: ' + data.scope);
|
||||
}
|
||||
return await mfa.isMfaEnabledForUser(user.id)
|
||||
.then((mfaEnabled) => {
|
||||
if (mfaEnabled) {
|
||||
if (!data.mfa_token) {
|
||||
throw new error.AuthError('MFA token required');
|
||||
}
|
||||
console.log(data.mfa_token);
|
||||
return mfa.validateMfaTokenForUser(user.id, data.mfa_token)
|
||||
.then((mfaValid) => {
|
||||
if (!mfaValid) {
|
||||
throw new error.AuthError('Invalid MFA token');
|
||||
}
|
||||
// Create a moment of the expiry expression
|
||||
let expiry = helpers.parseDatePeriod(data.expiry);
|
||||
if (expiry === null) {
|
||||
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
|
||||
}
|
||||
|
||||
// Create a moment of the expiry expression
|
||||
let expiry = helpers.parseDatePeriod(data.expiry);
|
||||
if (expiry === null) {
|
||||
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
|
||||
}
|
||||
return Token.create({
|
||||
iss: issuer || 'api',
|
||||
attrs: {
|
||||
id: user.id
|
||||
},
|
||||
scope: [data.scope],
|
||||
expiresIn: data.expiry
|
||||
})
|
||||
.then((signed) => {
|
||||
return {
|
||||
token: signed.token,
|
||||
expires: expiry.toISOString()
|
||||
};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Create a moment of the expiry expression
|
||||
let expiry = helpers.parseDatePeriod(data.expiry);
|
||||
if (expiry === null) {
|
||||
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
|
||||
}
|
||||
|
||||
return Token.create({
|
||||
iss: issuer || 'api',
|
||||
attrs: {
|
||||
id: user.id
|
||||
},
|
||||
scope: [data.scope],
|
||||
expiresIn: data.expiry
|
||||
})
|
||||
.then((signed) => {
|
||||
return {
|
||||
token: signed.token,
|
||||
expires: expiry.toISOString()
|
||||
};
|
||||
return Token.create({
|
||||
iss: issuer || 'api',
|
||||
attrs: {
|
||||
id: user.id
|
||||
},
|
||||
scope: [data.scope],
|
||||
expiresIn: data.expiry
|
||||
})
|
||||
.then((signed) => {
|
||||
return {
|
||||
token: signed.token,
|
||||
expires: expiry.toISOString()
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
||||
|
@ -507,7 +507,8 @@ const internalUser = {
|
||||
.then((user) => {
|
||||
return internalToken.getTokenFromUser(user);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = internalUser;
|
||||
|
45
backend/migrations/20250115041439_mfa_integeration.js
Normal file
45
backend/migrations/20250115041439_mfa_integeration.js
Normal file
@ -0,0 +1,45 @@
|
||||
const migrate_name = 'identifier_for_migrate';
|
||||
const logger = require('../logger').migrate;
|
||||
|
||||
/**
|
||||
* Migrate
|
||||
*
|
||||
* @see http://knexjs.org/#Schema
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.up = function (knex/*, Promise*/) {
|
||||
|
||||
logger.info('[' + migrate_name + '] Migrating Up...');
|
||||
|
||||
return knex.schema.alterTable('auth', (table) => {
|
||||
table.string('mfa_secret');
|
||||
table.boolean('mfa_enabled').defaultTo(false);
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] User Table altered');
|
||||
logger.info('[' + migrate_name + '] Migrating Up Complete');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.down = function (knex/*, Promise*/) {
|
||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
||||
|
||||
return knex.schema.alterTable('auth', (table) => {
|
||||
table.dropColumn('mfa_key');
|
||||
table.dropColumn('mfa_enabled');
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] User Table altered');
|
||||
logger.info('[' + migrate_name + '] Migrating Down Complete');
|
||||
});
|
||||
};
|
@ -23,8 +23,10 @@
|
||||
"node-rsa": "^1.0.8",
|
||||
"objection": "3.0.1",
|
||||
"path": "^0.12.7",
|
||||
"qrcode": "^1.5.4",
|
||||
"pg": "^8.13.1",
|
||||
"signale": "1.4.0",
|
||||
"speakeasy": "^2.0.0",
|
||||
"sqlite3": "5.1.6",
|
||||
"temp-write": "^4.0.0"
|
||||
},
|
||||
|
@ -27,6 +27,7 @@ router.get('/', (req, res/*, next*/) => {
|
||||
|
||||
router.use('/schema', require('./schema'));
|
||||
router.use('/tokens', require('./tokens'));
|
||||
router.use('/mfa', require('./mfa'));
|
||||
router.use('/users', require('./users'));
|
||||
router.use('/audit-log', require('./audit-log'));
|
||||
router.use('/reports', require('./reports'));
|
||||
|
81
backend/routes/mfa.js
Normal file
81
backend/routes/mfa.js
Normal file
@ -0,0 +1,81 @@
|
||||
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,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
router
|
||||
.route('/create')
|
||||
.post(jwtdecode(), (req, res, next) => {
|
||||
if (!res.locals.access) {
|
||||
return next(new Error('Invalid token'));
|
||||
}
|
||||
const userId = res.locals.access.token.getUserId();
|
||||
internalMfa.createMfaSecretForUser(userId)
|
||||
.then((secret) => {
|
||||
return userModel.query()
|
||||
.where('id', '=', userId)
|
||||
.first()
|
||||
.then((user) => {
|
||||
if (!user) {
|
||||
return next(new Error('User not found'));
|
||||
}
|
||||
return { secret, user };
|
||||
});
|
||||
})
|
||||
.then(({ secret, user }) => {
|
||||
const otpAuthUrl = speakeasy.otpauthURL({
|
||||
secret: secret.ascii,
|
||||
label: user.email,
|
||||
issuer: 'Nginx Proxy Manager'
|
||||
});
|
||||
qrcode.toDataURL(otpAuthUrl, (err, dataUrl) => {
|
||||
if (err) {
|
||||
console.error('Error generating QR code:', err);
|
||||
return next(err);
|
||||
}
|
||||
res.status(200).send({ qrCode: dataUrl });
|
||||
});
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
router
|
||||
.route('/enable')
|
||||
.post(jwtdecode(), (req, res, next) => {
|
||||
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')
|
||||
.get(jwtdecode(), (req, res, next) => {
|
||||
internalMfa.isMfaEnabledForUser(res.locals.access.token.getUserId())
|
||||
.then((active) => res.status(200).send({ active }))
|
||||
.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;
|
44
backend/schema/paths/mfa/delete/delete.json
Normal file
44
backend/schema/paths/mfa/delete/delete.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
backend/schema/paths/mfa/enable/post.json
Normal file
44
backend/schema/paths/mfa/enable/post.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"operationId": "enableMfa",
|
||||
"summary": "Enable multi-factor authentication for a user",
|
||||
"tags": [
|
||||
"MFA"
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "MFA Token Payload",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"token"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "MFA enabled successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,10 @@
|
||||
"secret": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"mfa_token": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["identity", "secret"],
|
||||
|
@ -14,7 +14,7 @@
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"additionalProperties": true,
|
||||
"required": ["name", "nickname", "email"],
|
||||
"properties": {
|
||||
"name": {
|
||||
|
@ -15,6 +15,16 @@
|
||||
"$ref": "./paths/get.json"
|
||||
}
|
||||
},
|
||||
"/mfa/enable": {
|
||||
"post": {
|
||||
"$ref": "./paths/mfa/enable/post.json"
|
||||
}
|
||||
},
|
||||
"/mfa/delete": {
|
||||
"delete": {
|
||||
"$ref": "./paths/mfa/delete/delete.json"
|
||||
}
|
||||
},
|
||||
"/audit-log": {
|
||||
"get": {
|
||||
"$ref": "./paths/audit-log/get.json"
|
||||
|
@ -2735,67 +2735,11 @@ path@^0.12.7:
|
||||
process "^0.11.1"
|
||||
util "^0.10.3"
|
||||
|
||||
pg-cloudflare@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
|
||||
integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
|
||||
|
||||
pg-connection-string@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
||||
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
||||
|
||||
pg-connection-string@^2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37"
|
||||
integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==
|
||||
|
||||
pg-int8@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
||||
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
|
||||
|
||||
pg-pool@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec"
|
||||
integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==
|
||||
|
||||
pg-protocol@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93"
|
||||
integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==
|
||||
|
||||
pg-types@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
|
||||
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
|
||||
dependencies:
|
||||
pg-int8 "1.0.1"
|
||||
postgres-array "~2.0.0"
|
||||
postgres-bytea "~1.0.0"
|
||||
postgres-date "~1.0.4"
|
||||
postgres-interval "^1.1.0"
|
||||
|
||||
pg@^8.13.1:
|
||||
version "8.13.1"
|
||||
resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080"
|
||||
integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==
|
||||
dependencies:
|
||||
pg-connection-string "^2.7.0"
|
||||
pg-pool "^3.7.0"
|
||||
pg-protocol "^1.7.0"
|
||||
pg-types "^2.1.0"
|
||||
pgpass "1.x"
|
||||
optionalDependencies:
|
||||
pg-cloudflare "^1.1.1"
|
||||
|
||||
pgpass@1.x:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
|
||||
integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
|
||||
dependencies:
|
||||
split2 "^4.1.0"
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||
@ -2814,28 +2758,6 @@ pkg-conf@^2.1.0:
|
||||
find-up "^2.0.0"
|
||||
load-json-file "^4.0.0"
|
||||
|
||||
postgres-array@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
|
||||
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
|
||||
|
||||
postgres-bytea@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
|
||||
integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==
|
||||
|
||||
postgres-date@~1.0.4:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
|
||||
integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
|
||||
|
||||
postgres-interval@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
|
||||
integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
|
||||
dependencies:
|
||||
xtend "^4.0.0"
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
@ -3272,11 +3194,6 @@ socks@^2.6.2:
|
||||
ip "^2.0.0"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
split2@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
|
||||
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
@ -3748,11 +3665,6 @@ xdg-basedir@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -37,8 +37,19 @@
|
||||
"all": "All",
|
||||
"any": "Any"
|
||||
},
|
||||
"mfa": {
|
||||
"mfa": "Multi Factor Authentication",
|
||||
"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",
|
||||
"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"
|
||||
"title": "Login to your account",
|
||||
"mfa-required-text": "Please enter your MFA token to continue"
|
||||
},
|
||||
"main": {
|
||||
"app": "Nginx Proxy Manager",
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col col-login mx-auto">
|
||||
@ -24,6 +25,12 @@
|
||||
<input name="secret" type="password" class="form-control" placeholder="<%- i18n('str', 'password') %>" required>
|
||||
<div class="invalid-feedback secret-error"></div>
|
||||
</div>
|
||||
<div class="form-group mfa-group" style="display: none;">
|
||||
<p class="mfa-info"><%- i18n('login', 'mfa-required-text') %>:</p>
|
||||
<label class="form-label"><%- i18n('mfa', 'mfa-token') %></label>
|
||||
<input name="mfa_token" type="text" class="form-control" placeholder="<%- i18n('mfa', 'mfa-token') %>">
|
||||
<div class="invalid-feedback mfa-error"></div>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-teal btn-block"><%- i18n('str', 'sign-in') %></button>
|
||||
</div>
|
||||
@ -34,4 +41,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -13,7 +13,11 @@ module.exports = Mn.View.extend({
|
||||
identity: 'input[name="identity"]',
|
||||
secret: 'input[name="secret"]',
|
||||
error: '.secret-error',
|
||||
button: 'button'
|
||||
error_mfa:'.mfa-error',
|
||||
button: 'button',
|
||||
mfaGroup: '.mfa-group', // added MFA group selector
|
||||
mfaToken: 'input[name="mfa_token"]', // added MFA token input
|
||||
mfaInfo: '.mfa-info' // added MFA info element
|
||||
},
|
||||
|
||||
events: {
|
||||
@ -22,14 +26,36 @@ module.exports = Mn.View.extend({
|
||||
this.ui.button.addClass('btn-loading').prop('disabled', true);
|
||||
this.ui.error.hide();
|
||||
|
||||
Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true)
|
||||
if(this.ui.mfaToken.val()) {
|
||||
Api.Tokens.loginWithMFA(this.ui.identity.val(), this.ui.secret.val(), this.ui.mfaToken.val(), true)
|
||||
.then(() => {
|
||||
window.location = '/';
|
||||
})
|
||||
.catch(err => {
|
||||
this.ui.error.text(err.message).show();
|
||||
if (err.message === 'Invalid MFA token.') {
|
||||
this.ui.error_mfa.text(err.message).show();
|
||||
} else {
|
||||
this.ui.error.text(err.message).show();
|
||||
}
|
||||
this.ui.button.removeClass('btn-loading').prop('disabled', false);
|
||||
});
|
||||
} else {
|
||||
Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true)
|
||||
.then(() => {
|
||||
window.location = '/';
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.message === 'MFA token required') {
|
||||
this.ui.mfaGroup.show();
|
||||
this.ui.mfaInfo.show();
|
||||
} else {
|
||||
this.ui.error.text(err.message).show();
|
||||
}
|
||||
this.ui.button.removeClass('btn-loading').prop('disabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@ -40,3 +66,5 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user