mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-10-05 04:10:10 +00:00
Compare commits
3 Commits
da22e0777e
...
v2.12.2
Author | SHA1 | Date | |
---|---|---|---|
|
b4f49969d6 | ||
|
5084cb7296 | ||
|
e677bfa2e8 |
6
Jenkinsfile
vendored
6
Jenkinsfile
vendored
@@ -128,7 +128,7 @@ pipeline {
|
|||||||
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
unstable {
|
unstable {
|
||||||
dir(path: 'test/results') {
|
dir(path: 'testing/results') {
|
||||||
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ pipeline {
|
|||||||
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
unstable {
|
unstable {
|
||||||
dir(path: 'test/results') {
|
dir(path: 'testing/results') {
|
||||||
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ pipeline {
|
|||||||
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
unstable {
|
unstable {
|
||||||
dir(path: 'test/results') {
|
dir(path: 'testing/results') {
|
||||||
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -258,7 +258,6 @@ const internalAccessList = {
|
|||||||
})
|
})
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.andWhere('access_list.id', data.id)
|
.andWhere('access_list.id', data.id)
|
||||||
.groupBy('access_list.id')
|
|
||||||
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
|
@@ -313,9 +313,6 @@ const internalCertificate = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner]')
|
||||||
.allowGraph('[proxy_hosts]')
|
|
||||||
.allowGraph('[redirection_hosts]')
|
|
||||||
.allowGraph('[dead_hosts]')
|
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@@ -467,9 +464,6 @@ const internalCertificate = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner]')
|
||||||
.allowGraph('[proxy_hosts]')
|
|
||||||
.allowGraph('[redirection_hosts]')
|
|
||||||
.allowGraph('[dead_hosts]')
|
|
||||||
.orderBy('nice_name', 'ASC');
|
.orderBy('nice_name', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
|
@@ -1,97 +0,0 @@
|
|||||||
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 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,15 +1,13 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const streamModel = require('../models/stream');
|
const streamModel = require('../models/stream');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
const internalHost = require('./host');
|
|
||||||
const {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted'];
|
return ['is_deleted'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalStream = {
|
const internalStream = {
|
||||||
@@ -20,12 +18,6 @@ const internalStream = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
create: (access, data) => {
|
create: (access, data) => {
|
||||||
const create_certificate = data.certificate_id === 'new';
|
|
||||||
|
|
||||||
if (create_certificate) {
|
|
||||||
delete data.certificate_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return access.can('streams:create', data)
|
return access.can('streams:create', data)
|
||||||
.then((/*access_data*/) => {
|
.then((/*access_data*/) => {
|
||||||
// TODO: At this point the existing ports should have been checked
|
// TODO: At this point the existing ports should have been checked
|
||||||
@@ -35,44 +27,16 @@ const internalStream = {
|
|||||||
data.meta = {};
|
data.meta = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// streams aren't routed by domain name so don't store domain names in the DB
|
|
||||||
let data_no_domains = structuredClone(data);
|
|
||||||
delete data_no_domains.domain_names;
|
|
||||||
|
|
||||||
return streamModel
|
return streamModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data_no_domains)
|
.insertAndFetch(data)
|
||||||
.then(utils.omitRow(omissions()));
|
.then(utils.omitRow(omissions()));
|
||||||
})
|
})
|
||||||
.then((row) => {
|
|
||||||
if (create_certificate) {
|
|
||||||
return internalCertificate.createQuickCertificate(access, data)
|
|
||||||
.then((cert) => {
|
|
||||||
// update host with cert id
|
|
||||||
return internalStream.update(access, {
|
|
||||||
id: row.id,
|
|
||||||
certificate_id: cert.id
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
// re-fetch with cert
|
|
||||||
return internalStream.get(access, {
|
|
||||||
id: row.id,
|
|
||||||
expand: ['certificate', 'owner']
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
// Configure nginx
|
// Configure nginx
|
||||||
return internalNginx.configure(streamModel, 'stream', row)
|
return internalNginx.configure(streamModel, 'stream', row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return row;
|
return internalStream.get(access, {id: row.id, expand: ['owner']});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
@@ -96,12 +60,6 @@ const internalStream = {
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
update: (access, data) => {
|
update: (access, data) => {
|
||||||
const create_certificate = data.certificate_id === 'new';
|
|
||||||
|
|
||||||
if (create_certificate) {
|
|
||||||
delete data.certificate_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return access.can('streams:update', data.id)
|
return access.can('streams:update', data.id)
|
||||||
.then((/*access_data*/) => {
|
.then((/*access_data*/) => {
|
||||||
// TODO: at this point the existing streams should have been checked
|
// TODO: at this point the existing streams should have been checked
|
||||||
@@ -113,32 +71,16 @@ const internalStream = {
|
|||||||
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (create_certificate) {
|
|
||||||
return internalCertificate.createQuickCertificate(access, {
|
|
||||||
domain_names: data.domain_names || row.domain_names,
|
|
||||||
meta: _.assign({}, row.meta, data.meta)
|
|
||||||
})
|
|
||||||
.then((cert) => {
|
|
||||||
// update host with cert id
|
|
||||||
data.certificate_id = cert.id;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
|
|
||||||
data = _.assign({}, {
|
|
||||||
domain_names: row.domain_names
|
|
||||||
}, data);
|
|
||||||
|
|
||||||
return streamModel
|
return streamModel
|
||||||
.query()
|
.query()
|
||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(utils.omitRow(omissions()))
|
.then(utils.omitRow(omissions()))
|
||||||
|
.then((saved_row) => {
|
||||||
|
return internalNginx.configure(streamModel, 'stream', saved_row)
|
||||||
|
.then(() => {
|
||||||
|
return internalStream.get(access, {id: row.id, expand: ['owner']});
|
||||||
|
});
|
||||||
|
})
|
||||||
.then((saved_row) => {
|
.then((saved_row) => {
|
||||||
// Add to audit log
|
// Add to audit log
|
||||||
return internalAuditLog.add(access, {
|
return internalAuditLog.add(access, {
|
||||||
@@ -151,17 +93,6 @@ const internalStream = {
|
|||||||
return saved_row;
|
return saved_row;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']})
|
|
||||||
.then((row) => {
|
|
||||||
return internalNginx.configure(streamModel, 'stream', row)
|
|
||||||
.then((new_meta) => {
|
|
||||||
row.meta = new_meta;
|
|
||||||
row = internalHost.cleanRowCertificateMeta(row);
|
|
||||||
return _.omit(row, omissions());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -184,7 +115,7 @@ const internalStream = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner,certificate]')
|
.allowGraph('[owner]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@@ -201,7 +132,6 @@ const internalStream = {
|
|||||||
if (!row || !row.id) {
|
if (!row || !row.id) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
row = internalHost.cleanRowCertificateMeta(row);
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
row = _.omit(row, data.omit);
|
||||||
@@ -267,14 +197,14 @@ const internalStream = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return internalStream.get(access, {
|
return internalStream.get(access, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
expand: ['certificate', 'owner']
|
expand: ['owner']
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row || !row.id) {
|
if (!row || !row.id) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
} else if (row.enabled) {
|
} else if (row.enabled) {
|
||||||
throw new error.ValidationError('Stream is already enabled');
|
throw new error.ValidationError('Host is already enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
row.enabled = 1;
|
row.enabled = 1;
|
||||||
@@ -320,7 +250,7 @@ const internalStream = {
|
|||||||
if (!row || !row.id) {
|
if (!row || !row.id) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
} else if (!row.enabled) {
|
} else if (!row.enabled) {
|
||||||
throw new error.ValidationError('Stream is already disabled');
|
throw new error.ValidationError('Host is already disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
row.enabled = 0;
|
row.enabled = 0;
|
||||||
@@ -368,7 +298,7 @@ const internalStream = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.allowGraph('[owner]')
|
||||||
.orderByRaw('CAST(incoming_port AS INTEGER) ASC');
|
.orderByRaw('CAST(incoming_port AS INTEGER) ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@@ -387,13 +317,6 @@ const internalStream = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query.then(utils.omitRows(omissions()));
|
||||||
})
|
|
||||||
.then((rows) => {
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
|
||||||
return internalHost.cleanAllRowsCertificateMeta(rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ const userModel = require('../models/user');
|
|||||||
const authModel = require('../models/auth');
|
const authModel = require('../models/auth');
|
||||||
const helpers = require('../lib/helpers');
|
const helpers = require('../lib/helpers');
|
||||||
const TokenModel = require('../models/token');
|
const TokenModel = require('../models/token');
|
||||||
const mfa = require('../internal/mfa'); // <-- added MFA import
|
|
||||||
|
|
||||||
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
|
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
|
||||||
|
|
||||||
@@ -22,8 +21,6 @@ module.exports = {
|
|||||||
getTokenFromEmail: (data, issuer) => {
|
getTokenFromEmail: (data, issuer) => {
|
||||||
let Token = new TokenModel();
|
let Token = new TokenModel();
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
data.scope = data.scope || 'user';
|
data.scope = data.scope || 'user';
|
||||||
data.expiry = data.expiry || '1d';
|
data.expiry = data.expiry || '1d';
|
||||||
|
|
||||||
@@ -44,66 +41,34 @@ module.exports = {
|
|||||||
.then((auth) => {
|
.then((auth) => {
|
||||||
if (auth) {
|
if (auth) {
|
||||||
return auth.verifyPassword(data.secret)
|
return auth.verifyPassword(data.secret)
|
||||||
.then(async (valid) => {
|
.then((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
|
||||||
if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
|
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);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Token.create({
|
// Create a moment of the expiry expression
|
||||||
iss: issuer || 'api',
|
let expiry = helpers.parseDatePeriod(data.expiry);
|
||||||
attrs: {
|
if (expiry === null) {
|
||||||
id: user.id
|
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
|
||||||
},
|
}
|
||||||
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({
|
return Token.create({
|
||||||
iss: issuer || 'api',
|
iss: issuer || 'api',
|
||||||
attrs: {
|
attrs: {
|
||||||
id: user.id
|
id: user.id
|
||||||
},
|
},
|
||||||
scope: [data.scope],
|
scope: [data.scope],
|
||||||
expiresIn: data.expiry
|
expiresIn: data.expiry
|
||||||
})
|
})
|
||||||
.then((signed) => {
|
.then((signed) => {
|
||||||
return {
|
return {
|
||||||
token: signed.token,
|
token: signed.token,
|
||||||
expires: expiry.toISOString()
|
expires: expiry.toISOString()
|
||||||
};
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
||||||
|
@@ -507,8 +507,7 @@ const internalUser = {
|
|||||||
.then((user) => {
|
.then((user) => {
|
||||||
return internalToken.getTokenFromUser(user);
|
return internalToken.getTokenFromUser(user);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = internalUser;
|
module.exports = internalUser;
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
const migrate_name = 'stream_ssl';
|
|
||||||
const logger = require('../logger').migrate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate
|
|
||||||
*
|
|
||||||
* @see http://knexjs.org/#Schema
|
|
||||||
*
|
|
||||||
* @param {Object} knex
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
exports.up = function (knex) {
|
|
||||||
logger.info('[' + migrate_name + '] Migrating Up...');
|
|
||||||
|
|
||||||
return knex.schema.table('stream', (table) => {
|
|
||||||
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
logger.info('[' + migrate_name + '] stream Table altered');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undo Migrate
|
|
||||||
*
|
|
||||||
* @param {Object} knex
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
exports.down = function (knex) {
|
|
||||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
|
||||||
|
|
||||||
return knex.schema.table('stream', (table) => {
|
|
||||||
table.dropColumn('certificate_id');
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
logger.info('[' + migrate_name + '] stream Table altered');
|
|
||||||
});
|
|
||||||
};
|
|
@@ -1,45 +0,0 @@
|
|||||||
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');
|
|
||||||
});
|
|
||||||
};
|
|
@@ -4,6 +4,7 @@
|
|||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const helpers = require('../lib/helpers');
|
const helpers = require('../lib/helpers');
|
||||||
const Model = require('objection').Model;
|
const Model = require('objection').Model;
|
||||||
|
const User = require('./user');
|
||||||
const now = require('./now_helper');
|
const now = require('./now_helper');
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
@@ -67,11 +68,6 @@ class Certificate extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get relationMappings () {
|
static get relationMappings () {
|
||||||
const ProxyHost = require('./proxy_host');
|
|
||||||
const DeadHost = require('./dead_host');
|
|
||||||
const User = require('./user');
|
|
||||||
const RedirectionHost = require('./redirection_host');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
owner: {
|
owner: {
|
||||||
relation: Model.HasOneRelation,
|
relation: Model.HasOneRelation,
|
||||||
@@ -83,39 +79,6 @@ class Certificate extends Model {
|
|||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
proxy_hosts: {
|
|
||||||
relation: Model.HasManyRelation,
|
|
||||||
modelClass: ProxyHost,
|
|
||||||
join: {
|
|
||||||
from: 'certificate.id',
|
|
||||||
to: 'proxy_host.certificate_id'
|
|
||||||
},
|
|
||||||
modify: function (qb) {
|
|
||||||
qb.where('proxy_host.is_deleted', 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dead_hosts: {
|
|
||||||
relation: Model.HasManyRelation,
|
|
||||||
modelClass: DeadHost,
|
|
||||||
join: {
|
|
||||||
from: 'certificate.id',
|
|
||||||
to: 'dead_host.certificate_id'
|
|
||||||
},
|
|
||||||
modify: function (qb) {
|
|
||||||
qb.where('dead_host.is_deleted', 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
redirection_hosts: {
|
|
||||||
relation: Model.HasManyRelation,
|
|
||||||
modelClass: RedirectionHost,
|
|
||||||
join: {
|
|
||||||
from: 'certificate.id',
|
|
||||||
to: 'redirection_host.certificate_id'
|
|
||||||
},
|
|
||||||
modify: function (qb) {
|
|
||||||
qb.where('redirection_host.is_deleted', 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
const Model = require('objection').Model;
|
// Objection Docs:
|
||||||
const db = require('../db');
|
// http://vincit.github.io/objection.js/
|
||||||
const helpers = require('../lib/helpers');
|
|
||||||
const User = require('./user');
|
const db = require('../db');
|
||||||
const Certificate = require('./certificate');
|
const helpers = require('../lib/helpers');
|
||||||
const now = require('./now_helper');
|
const Model = require('objection').Model;
|
||||||
|
const User = require('./user');
|
||||||
|
const now = require('./now_helper');
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
|
|
||||||
const boolFields = [
|
const boolFields = [
|
||||||
'enabled',
|
|
||||||
'is_deleted',
|
'is_deleted',
|
||||||
'tcp_forwarding',
|
'tcp_forwarding',
|
||||||
'udp_forwarding',
|
'udp_forwarding',
|
||||||
@@ -63,17 +64,6 @@ class Stream extends Model {
|
|||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
certificate: {
|
|
||||||
relation: Model.HasOneRelation,
|
|
||||||
modelClass: Certificate,
|
|
||||||
join: {
|
|
||||||
from: 'stream.certificate_id',
|
|
||||||
to: 'certificate.id'
|
|
||||||
},
|
|
||||||
modify: function (qb) {
|
|
||||||
qb.where('certificate.is_deleted', 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -23,10 +23,8 @@
|
|||||||
"node-rsa": "^1.0.8",
|
"node-rsa": "^1.0.8",
|
||||||
"objection": "3.0.1",
|
"objection": "3.0.1",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"qrcode": "^1.5.4",
|
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"signale": "1.4.0",
|
"signale": "1.4.0",
|
||||||
"speakeasy": "^2.0.0",
|
|
||||||
"sqlite3": "5.1.6",
|
"sqlite3": "5.1.6",
|
||||||
"temp-write": "^4.0.0"
|
"temp-write": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
@@ -27,7 +27,6 @@ router.get('/', (req, res/*, next*/) => {
|
|||||||
|
|
||||||
router.use('/schema', require('./schema'));
|
router.use('/schema', require('./schema'));
|
||||||
router.use('/tokens', require('./tokens'));
|
router.use('/tokens', require('./tokens'));
|
||||||
router.use('/mfa', require('./mfa'));
|
|
||||||
router.use('/users', require('./users'));
|
router.use('/users', require('./users'));
|
||||||
router.use('/audit-log', require('./audit-log'));
|
router.use('/audit-log', require('./audit-log'));
|
||||||
router.use('/reports', require('./reports'));
|
router.use('/reports', require('./reports'));
|
||||||
|
@@ -1,81 +0,0 @@
|
|||||||
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;
|
|
@@ -19,7 +19,9 @@
|
|||||||
"incoming_port": {
|
"incoming_port": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 65535
|
"maximum": 65535,
|
||||||
|
"if": {"properties": {"tcp_forwarding": {"const": true}}},
|
||||||
|
"then": {"not": {"oneOf": [{"const": 80}, {"const": 443}]}}
|
||||||
},
|
},
|
||||||
"forwarding_host": {
|
"forwarding_host": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
@@ -53,24 +55,8 @@
|
|||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "../common.json#/properties/enabled"
|
"$ref": "../common.json#/properties/enabled"
|
||||||
},
|
},
|
||||||
"certificate_id": {
|
|
||||||
"$ref": "../common.json#/properties/certificate_id"
|
|
||||||
},
|
|
||||||
"meta": {
|
"meta": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
|
||||||
"owner": {
|
|
||||||
"$ref": "./user-object.json"
|
|
||||||
},
|
|
||||||
"certificate": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "./certificate-object.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,10 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
"description": "Token Expiry ISO Time String",
|
"description": "Token Expiry Unix Time",
|
||||||
"example": "2025-02-04T20:40:46.340Z",
|
"example": 1566540249,
|
||||||
"type": "string"
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"description": "JWT Token",
|
"description": "JWT Token",
|
||||||
|
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,7 +14,7 @@
|
|||||||
"description": "Expansions",
|
"description": "Expansions",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["owner", "certificate"]
|
"enum": ["access_list", "owner", "certificate"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -40,8 +40,7 @@
|
|||||||
"nginx_online": true,
|
"nginx_online": true,
|
||||||
"nginx_err": null
|
"nginx_err": null
|
||||||
},
|
},
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"certificate_id": 0
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -32,9 +32,6 @@
|
|||||||
"udp_forwarding": {
|
"udp_forwarding": {
|
||||||
"$ref": "../../../components/stream-object.json#/properties/udp_forwarding"
|
"$ref": "../../../components/stream-object.json#/properties/udp_forwarding"
|
||||||
},
|
},
|
||||||
"certificate_id": {
|
|
||||||
"$ref": "../../../components/stream-object.json#/properties/certificate_id"
|
|
||||||
},
|
|
||||||
"meta": {
|
"meta": {
|
||||||
"$ref": "../../../components/stream-object.json#/properties/meta"
|
"$ref": "../../../components/stream-object.json#/properties/meta"
|
||||||
}
|
}
|
||||||
@@ -76,8 +73,7 @@
|
|||||||
"nickname": "Admin",
|
"nickname": "Admin",
|
||||||
"avatar": "",
|
"avatar": "",
|
||||||
"roles": ["admin"]
|
"roles": ["admin"]
|
||||||
},
|
}
|
||||||
"certificate_id": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -40,8 +40,7 @@
|
|||||||
"nginx_online": true,
|
"nginx_online": true,
|
||||||
"nginx_err": null
|
"nginx_err": null
|
||||||
},
|
},
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"certificate_id": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -29,26 +29,56 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"minProperties": 1,
|
"minProperties": 1,
|
||||||
"properties": {
|
"properties": {
|
||||||
"incoming_port": {
|
"domain_names": {
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/incoming_port"
|
"$ref": "../../../../components/proxy-host-object.json#/properties/domain_names"
|
||||||
},
|
},
|
||||||
"forwarding_host": {
|
"forward_scheme": {
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/forwarding_host"
|
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme"
|
||||||
},
|
},
|
||||||
"forwarding_port": {
|
"forward_host": {
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/forwarding_port"
|
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_host"
|
||||||
},
|
},
|
||||||
"tcp_forwarding": {
|
"forward_port": {
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding"
|
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_port"
|
||||||
},
|
|
||||||
"udp_forwarding": {
|
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/udp_forwarding"
|
|
||||||
},
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/certificate_id"
|
"$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id"
|
||||||
|
},
|
||||||
|
"ssl_forced": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/ssl_forced"
|
||||||
|
},
|
||||||
|
"hsts_enabled": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_enabled"
|
||||||
|
},
|
||||||
|
"hsts_subdomains": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains"
|
||||||
|
},
|
||||||
|
"http2_support": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support"
|
||||||
|
},
|
||||||
|
"block_exploits": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/block_exploits"
|
||||||
|
},
|
||||||
|
"caching_enabled": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/caching_enabled"
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/allow_websocket_upgrade"
|
||||||
|
},
|
||||||
|
"access_list_id": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/access_list_id"
|
||||||
|
},
|
||||||
|
"advanced_config": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/advanced_config"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/enabled"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"$ref": "../../../../components/stream-object.json#/properties/meta"
|
"$ref": "../../../../components/proxy-host-object.json#/properties/meta"
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"$ref": "../../../../components/proxy-host-object.json#/properties/locations"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,32 +94,42 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"value": {
|
"value": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"created_on": "2024-10-09T02:33:45.000Z",
|
"created_on": "2024-10-08T23:23:03.000Z",
|
||||||
"modified_on": "2024-10-09T02:33:45.000Z",
|
"modified_on": "2024-10-08T23:26:37.000Z",
|
||||||
"owner_user_id": 1,
|
"owner_user_id": 1,
|
||||||
"incoming_port": 9090,
|
"domain_names": ["test.example.com"],
|
||||||
"forwarding_host": "router.internal",
|
"forward_host": "192.168.0.10",
|
||||||
"forwarding_port": 80,
|
"forward_port": 8989,
|
||||||
"tcp_forwarding": true,
|
"access_list_id": 0,
|
||||||
"udp_forwarding": false,
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": false,
|
||||||
|
"caching_enabled": false,
|
||||||
|
"block_exploits": false,
|
||||||
|
"advanced_config": "",
|
||||||
"meta": {
|
"meta": {
|
||||||
"nginx_online": true,
|
"nginx_online": true,
|
||||||
"nginx_err": null
|
"nginx_err": null
|
||||||
},
|
},
|
||||||
|
"allow_websocket_upgrade": false,
|
||||||
|
"http2_support": false,
|
||||||
|
"forward_scheme": "http",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"hsts_enabled": false,
|
||||||
|
"hsts_subdomains": false,
|
||||||
"owner": {
|
"owner": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"created_on": "2024-10-09T02:33:16.000Z",
|
"created_on": "2024-10-07T22:43:55.000Z",
|
||||||
"modified_on": "2024-10-09T02:33:16.000Z",
|
"modified_on": "2024-10-08T12:52:54.000Z",
|
||||||
"is_deleted": false,
|
"is_deleted": false,
|
||||||
"is_disabled": false,
|
"is_disabled": false,
|
||||||
"email": "admin@example.com",
|
"email": "admin@example.com",
|
||||||
"name": "Administrator",
|
"name": "Administrator",
|
||||||
"nickname": "Admin",
|
"nickname": "some guy",
|
||||||
"avatar": "",
|
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
|
||||||
"roles": ["admin"]
|
"roles": ["admin"]
|
||||||
},
|
},
|
||||||
"certificate_id": 0
|
"certificate": null,
|
||||||
|
"access_list": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
"examples": {
|
"examples": {
|
||||||
"default": {
|
"default": {
|
||||||
"value": {
|
"value": {
|
||||||
"expires": "2025-02-04T20:40:46.340Z",
|
"expires": 1566540510,
|
||||||
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,10 +22,6 @@
|
|||||||
"secret": {
|
"secret": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
|
||||||
"mfa_token": {
|
|
||||||
"minLength": 1,
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["identity", "secret"],
|
"required": ["identity", "secret"],
|
||||||
@@ -42,7 +38,7 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"value": {
|
"value": {
|
||||||
"result": {
|
"result": {
|
||||||
"expires": "2025-02-04T20:40:46.340Z",
|
"expires": 1566540510,
|
||||||
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": true,
|
"additionalProperties": false,
|
||||||
"required": ["name", "nickname", "email"],
|
"required": ["name", "nickname", "email"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
|
@@ -15,16 +15,6 @@
|
|||||||
"$ref": "./paths/get.json"
|
"$ref": "./paths/get.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/mfa/enable": {
|
|
||||||
"post": {
|
|
||||||
"$ref": "./paths/mfa/enable/post.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/mfa/delete": {
|
|
||||||
"delete": {
|
|
||||||
"$ref": "./paths/mfa/delete/delete.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/audit-log": {
|
"/audit-log": {
|
||||||
"get": {
|
"get": {
|
||||||
"$ref": "./paths/audit-log/get.json"
|
"$ref": "./paths/audit-log/get.json"
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
{% if certificate.provider == "letsencrypt" %}
|
{% if certificate.provider == "letsencrypt" %}
|
||||||
# Let's Encrypt SSL
|
# Let's Encrypt SSL
|
||||||
include conf.d/include/letsencrypt-acme-challenge.conf;
|
include conf.d/include/letsencrypt-acme-challenge.conf;
|
||||||
include conf.d/include/ssl-cache.conf;
|
|
||||||
include conf.d/include/ssl-ciphers.conf;
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
{% if certificate and certificate_id > 0 %}
|
|
||||||
{% if certificate.provider == "letsencrypt" %}
|
|
||||||
# Let's Encrypt SSL
|
|
||||||
include conf.d/include/ssl-cache-stream.conf;
|
|
||||||
include conf.d/include/ssl-ciphers.conf;
|
|
||||||
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
|
|
||||||
{%- else %}
|
|
||||||
# Custom SSL
|
|
||||||
ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem;
|
|
||||||
ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem;
|
|
||||||
{%- endif -%}
|
|
||||||
{%- endif -%}
|
|
@@ -5,10 +5,12 @@
|
|||||||
{% if enabled %}
|
{% if enabled %}
|
||||||
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
|
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
|
||||||
server {
|
server {
|
||||||
listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %};
|
listen {{ incoming_port }};
|
||||||
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %};
|
{% if ipv6 -%}
|
||||||
|
listen [::]:{{ incoming_port }};
|
||||||
{%- include "_certificates_stream.conf" %}
|
{% else -%}
|
||||||
|
#listen [::]:{{ incoming_port }};
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
||||||
|
|
||||||
@@ -17,12 +19,14 @@ server {
|
|||||||
include /data/nginx/custom/server_stream_tcp[.]conf;
|
include /data/nginx/custom/server_stream_tcp[.]conf;
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if udp_forwarding == 1 or udp_forwarding == true %}
|
||||||
{% if udp_forwarding == 1 or udp_forwarding == true -%}
|
|
||||||
server {
|
server {
|
||||||
listen {{ incoming_port }} udp;
|
listen {{ incoming_port }} udp;
|
||||||
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp;
|
{% if ipv6 -%}
|
||||||
|
listen [::]:{{ incoming_port }} udp;
|
||||||
|
{% else -%}
|
||||||
|
#listen [::]:{{ incoming_port }} udp;
|
||||||
|
{% endif %}
|
||||||
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
|
@@ -2735,11 +2735,67 @@ path@^0.12.7:
|
|||||||
process "^0.11.1"
|
process "^0.11.1"
|
||||||
util "^0.10.3"
|
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:
|
pg-connection-string@2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
||||||
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
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:
|
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||||
@@ -2758,6 +2814,28 @@ pkg-conf@^2.1.0:
|
|||||||
find-up "^2.0.0"
|
find-up "^2.0.0"
|
||||||
load-json-file "^4.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:
|
prelude-ls@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
@@ -3194,6 +3272,11 @@ socks@^2.6.2:
|
|||||||
ip "^2.0.0"
|
ip "^2.0.0"
|
||||||
smart-buffer "^4.2.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:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
@@ -3665,6 +3748,11 @@ xdg-basedir@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||||
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
|
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:
|
y18n@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||||
|
@@ -22,10 +22,6 @@ services:
|
|||||||
test: ["CMD", "/usr/bin/check-health"]
|
test: ["CMD", "/usr/bin/check-health"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
expose:
|
|
||||||
- '80-81/tcp'
|
|
||||||
- '443/tcp'
|
|
||||||
- '1500-1503/tcp'
|
|
||||||
networks:
|
networks:
|
||||||
fulltest:
|
fulltest:
|
||||||
aliases:
|
aliases:
|
||||||
@@ -44,7 +40,7 @@ services:
|
|||||||
- ca.internal
|
- ca.internal
|
||||||
|
|
||||||
pdns:
|
pdns:
|
||||||
image: pschiffe/pdns-mysql:4.8
|
image: pschiffe/pdns-mysql
|
||||||
volumes:
|
volumes:
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
environment:
|
environment:
|
||||||
@@ -101,7 +97,7 @@ services:
|
|||||||
HTTP_PROXY: 'squid:3128'
|
HTTP_PROXY: 'squid:3128'
|
||||||
HTTPS_PROXY: 'squid:3128'
|
HTTPS_PROXY: 'squid:3128'
|
||||||
volumes:
|
volumes:
|
||||||
- 'cypress_logs:/test/results'
|
- 'cypress_logs:/results'
|
||||||
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
command: cypress run --browser chrome --config-file=cypress/config/ci.js
|
command: cypress run --browser chrome --config-file=cypress/config/ci.js
|
||||||
|
@@ -132,7 +132,7 @@ services:
|
|||||||
- 8128:3128
|
- 8128:3128
|
||||||
|
|
||||||
pdns:
|
pdns:
|
||||||
image: pschiffe/pdns-mysql:4.8
|
image: pschiffe/pdns-mysql
|
||||||
container_name: npm2dev.pdns
|
container_name: npm2dev.pdns
|
||||||
volumes:
|
volumes:
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
@@ -218,7 +218,7 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- ci.env
|
- ci.env
|
||||||
ports:
|
ports:
|
||||||
- 9000:9000
|
- 9000:9000
|
||||||
depends_on:
|
depends_on:
|
||||||
- authentik-redis
|
- authentik-redis
|
||||||
- db-postgres
|
- db-postgres
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
ssl_session_timeout 5m;
|
|
||||||
ssl_session_cache shared:SSL_stream:50m;
|
|
@@ -1,2 +0,0 @@
|
|||||||
ssl_session_timeout 5m;
|
|
||||||
ssl_session_cache shared:SSL:50m;
|
|
@@ -1,3 +1,6 @@
|
|||||||
|
ssl_session_timeout 5m;
|
||||||
|
ssl_session_cache shared:SSL:50m;
|
||||||
|
|
||||||
# intermediate configuration. tweak to your needs.
|
# intermediate configuration. tweak to your needs.
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
||||||
|
@@ -8,7 +8,7 @@ BLUE='\E[1;34m'
|
|||||||
GREEN='\E[1;32m'
|
GREEN='\E[1;32m'
|
||||||
RESET='\E[0m'
|
RESET='\E[0m'
|
||||||
|
|
||||||
S6_OVERLAY_VERSION=3.2.0.2
|
S6_OVERLAY_VERSION=3.1.5.0
|
||||||
TARGETPLATFORM=${1:-linux/amd64}
|
TARGETPLATFORM=${1:-linux/amd64}
|
||||||
|
|
||||||
# Determine the correct binary file for the architecture given
|
# Determine the correct binary file for the architecture given
|
||||||
|
@@ -1065,9 +1065,9 @@ vfile@^6.0.0:
|
|||||||
vfile-message "^4.0.0"
|
vfile-message "^4.0.0"
|
||||||
|
|
||||||
vite@^5.4.8:
|
vite@^5.4.8:
|
||||||
version "5.4.14"
|
version "5.4.8"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8"
|
||||||
integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
|
integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.21.3"
|
esbuild "^0.21.3"
|
||||||
postcss "^8.4.43"
|
postcss "^8.4.43"
|
||||||
|
@@ -202,49 +202,7 @@ module.exports = {
|
|||||||
return fetch('get', '');
|
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: {
|
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
|
* @param {String} identity
|
||||||
|
@@ -50,7 +50,8 @@ module.exports = Mn.View.extend({
|
|||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
let view = this;
|
||||||
|
|
||||||
Api.Reports.getHostStats()
|
if (typeof view.stats.hosts === 'undefined') {
|
||||||
|
Api.Reports.getHostStats()
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!view.isDestroyed()) {
|
if (!view.isDestroyed()) {
|
||||||
view.stats.hosts = response;
|
view.stats.hosts = response;
|
||||||
@@ -60,6 +61,7 @@ module.exports = Mn.View.extend({
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -33,13 +33,6 @@
|
|||||||
<td class="<%- isExpired() ? 'text-danger' : '' %>">
|
<td class="<%- isExpired() ? 'text-danger' : '' %>">
|
||||||
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
|
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<% if (active_domain_names().length > 0) { %>
|
|
||||||
<span class="status-icon bg-success"></span> <%- i18n('certificates', 'in-use') %>
|
|
||||||
<% } else { %>
|
|
||||||
<span class="status-icon bg-danger"></span> <%- i18n('certificates', 'inactive') %>
|
|
||||||
<% } %>
|
|
||||||
</td>
|
|
||||||
<% if (canManage) { %>
|
<% if (canManage) { %>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="item-action dropdown">
|
<div class="item-action dropdown">
|
||||||
@@ -55,14 +48,7 @@
|
|||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
||||||
<% if (active_domain_names().length > 0) { %>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<span class="dropdown-header"><%- i18n('certificates', 'active-domain_names') %></span>
|
|
||||||
<% active_domain_names().forEach(function(host) { %>
|
|
||||||
<a href="https://<%- host %>" class="dropdown-item" target="_blank"><%- host %></a>
|
|
||||||
<% }); %>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@@ -44,24 +44,14 @@ module.exports = Mn.View.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
templateContext: function () {
|
templateContext: {
|
||||||
return {
|
canManage: App.Cache.User.canManage('certificates'),
|
||||||
canManage: App.Cache.User.canManage('certificates'),
|
isExpired: function () {
|
||||||
isExpired: function () {
|
return moment(this.expires_on).isBefore(moment());
|
||||||
return moment(this.expires_on).isBefore(moment());
|
},
|
||||||
},
|
dns_providers: dns_providers
|
||||||
dns_providers: dns_providers,
|
|
||||||
active_domain_names: function () {
|
|
||||||
const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this;
|
|
||||||
return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => {
|
|
||||||
acc.push(...(host.domain_names || []));
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
this.listenTo(this.model, 'change', this.render);
|
this.listenTo(this.model, 'change', this.render);
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
<th><%- i18n('str', 'name') %></th>
|
<th><%- i18n('str', 'name') %></th>
|
||||||
<th><%- i18n('all-hosts', 'cert-provider') %></th>
|
<th><%- i18n('all-hosts', 'cert-provider') %></th>
|
||||||
<th><%- i18n('str', 'expires') %></th>
|
<th><%- i18n('str', 'expires') %></th>
|
||||||
<th><%- i18n('str', 'status') %></th>
|
|
||||||
<% if (canManage) { %>
|
<% if (canManage) { %>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@@ -74,7 +74,7 @@ module.exports = Mn.View.extend({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let query = this.ui.query.val();
|
let query = this.ui.query.val();
|
||||||
|
|
||||||
this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query)
|
this.fetch(['owner'], query)
|
||||||
.then(response => this.showData(response))
|
.then(response => this.showData(response))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.showError(err);
|
this.showError(err);
|
||||||
@@ -89,7 +89,7 @@ module.exports = Mn.View.extend({
|
|||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
let view = this;
|
||||||
|
|
||||||
view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'])
|
view.fetch(['owner'])
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!view.isDestroyed()) {
|
if (!view.isDestroyed()) {
|
||||||
if (response && response.length) {
|
if (response && response.length) {
|
||||||
|
@@ -3,187 +3,48 @@
|
|||||||
<h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5>
|
<h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5>
|
||||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body has-tabs">
|
<div class="modal-body">
|
||||||
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
|
|
||||||
<form>
|
<form>
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<div class="row">
|
||||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
|
<div class="col-sm-12 col-md-12">
|
||||||
<li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li>
|
<div class="form-group">
|
||||||
</ul>
|
<label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label>
|
||||||
<div class="tab-content">
|
<input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required>
|
||||||
<!-- Details -->
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="details">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label>
|
|
||||||
<input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-8 col-md-8">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label>
|
|
||||||
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label>
|
|
||||||
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="custom-switch">
|
|
||||||
<input type="checkbox" class="custom-switch-input" name="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
<span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="custom-switch">
|
|
||||||
<input type="checkbox" class="custom-switch-input" name="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
<span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-8 col-md-8">
|
||||||
<!-- SSL -->
|
<div class="form-group">
|
||||||
<div role="tabpanel" class="tab-pane" id="ssl-options">
|
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label>
|
||||||
<div class="row">
|
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('streams', 'ssl-certificate') %></label>
|
|
||||||
<select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>">
|
|
||||||
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option>
|
|
||||||
<option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- DNS challenge -->
|
|
||||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label>
|
|
||||||
<input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="custom-switch">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="custom-switch-input"
|
|
||||||
name="meta[dns_challenge]"
|
|
||||||
value="1"
|
|
||||||
checked
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
|
||||||
<fieldset class="form-fieldset dns-challenge">
|
|
||||||
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
|
|
||||||
|
|
||||||
<!-- Certbot DNS plugin selection -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
|
|
||||||
<select
|
|
||||||
name="meta[dns_provider]"
|
|
||||||
id="dns_provider"
|
|
||||||
class="form-control custom-select"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value=""
|
|
||||||
disabled
|
|
||||||
hidden
|
|
||||||
<%- getDnsProvider() === null ? 'selected' : '' %>
|
|
||||||
>Please Choose...</option>
|
|
||||||
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
|
|
||||||
<option
|
|
||||||
value="<%- plugin_name %>"
|
|
||||||
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
|
|
||||||
><%- plugin_info.name %></option>
|
|
||||||
<% }); %>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Certbot credentials file content -->
|
|
||||||
<div class="row credentials-file-content">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
|
|
||||||
<textarea
|
|
||||||
name="meta[dns_provider_credentials]"
|
|
||||||
class="form-control text-monospace"
|
|
||||||
id="dns_provider_credentials"
|
|
||||||
><%- getDnsProviderCredentials() %></textarea>
|
|
||||||
<div class="text-secondary small">
|
|
||||||
<i class="fe fe-info"></i>
|
|
||||||
<%= i18n('ssl', 'credentials-file-content-info') %>
|
|
||||||
</div>
|
|
||||||
<div class="text-red small">
|
|
||||||
<i class="fe fe-alert-triangle"></i>
|
|
||||||
<%= i18n('ssl', 'stored-as-plaintext-info') %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- DNS propagation delay -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group mb-0">
|
|
||||||
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
name="meta[propagation_seconds]"
|
|
||||||
class="form-control"
|
|
||||||
id="propagation_seconds"
|
|
||||||
value="<%- getPropagationSeconds() %>"
|
|
||||||
>
|
|
||||||
<div class="text-secondary small">
|
|
||||||
<i class="fe fe-info"></i>
|
|
||||||
<%= i18n('ssl', 'propagation-seconds-info') %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Lets encrypt -->
|
|
||||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label>
|
|
||||||
<input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="custom-switch">
|
|
||||||
<input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
<span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-4 col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label>
|
||||||
|
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="custom-switch">
|
||||||
|
<input type="checkbox" class="custom-switch-input" name="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
<span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="custom-switch">
|
||||||
|
<input type="checkbox" class="custom-switch-input" name="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
<span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,38 +1,24 @@
|
|||||||
const Mn = require('backbone.marionette');
|
const Mn = require('backbone.marionette');
|
||||||
const App = require('../../main');
|
const App = require('../../main');
|
||||||
const StreamModel = require('../../../models/stream');
|
const StreamModel = require('../../../models/stream');
|
||||||
const template = require('./form.ejs');
|
const template = require('./form.ejs');
|
||||||
const dns_providers = require('../../../../../global/certbot-dns-plugins');
|
|
||||||
|
|
||||||
require('jquery-serializejson');
|
require('jquery-serializejson');
|
||||||
require('jquery-mask-plugin');
|
require('jquery-mask-plugin');
|
||||||
require('selectize');
|
require('selectize');
|
||||||
const Helpers = require("../../../lib/helpers");
|
|
||||||
const certListItemTemplate = require("../certificates-list-item.ejs");
|
|
||||||
const i18n = require("../../i18n");
|
|
||||||
|
|
||||||
module.exports = Mn.View.extend({
|
module.exports = Mn.View.extend({
|
||||||
template: template,
|
template: template,
|
||||||
className: 'modal-dialog',
|
className: 'modal-dialog',
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
form: 'form',
|
form: 'form',
|
||||||
forwarding_host: 'input[name="forwarding_host"]',
|
forwarding_host: 'input[name="forwarding_host"]',
|
||||||
type_error: '.forward-type-error',
|
type_error: '.forward-type-error',
|
||||||
buttons: '.modal-footer button',
|
buttons: '.modal-footer button',
|
||||||
switches: '.custom-switch-input',
|
switches: '.custom-switch-input',
|
||||||
cancel: 'button.cancel',
|
cancel: 'button.cancel',
|
||||||
save: 'button.save',
|
save: 'button.save'
|
||||||
le_error_info: '#le-error-info',
|
|
||||||
certificate_select: 'select[name="certificate_id"]',
|
|
||||||
domain_names: 'input[name="domain_names"]',
|
|
||||||
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
|
|
||||||
dns_challenge_content: '.dns-challenge',
|
|
||||||
dns_provider: 'select[name="meta[dns_provider]"]',
|
|
||||||
credentials_file_content: '.credentials-file-content',
|
|
||||||
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
|
|
||||||
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
|
|
||||||
letsencrypt: '.letsencrypt'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@@ -62,35 +48,6 @@ module.exports = Mn.View.extend({
|
|||||||
data.tcp_forwarding = !!data.tcp_forwarding;
|
data.tcp_forwarding = !!data.tcp_forwarding;
|
||||||
data.udp_forwarding = !!data.udp_forwarding;
|
data.udp_forwarding = !!data.udp_forwarding;
|
||||||
|
|
||||||
if (typeof data.meta === 'undefined') data.meta = {};
|
|
||||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
|
|
||||||
data.meta.dns_challenge = true;
|
|
||||||
|
|
||||||
if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
|
|
||||||
|
|
||||||
if (typeof data.domain_names === 'string' && data.domain_names) {
|
|
||||||
data.domain_names = data.domain_names.split(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
|
|
||||||
if (data.certificate_id === 'new') {
|
|
||||||
let domain_err = false;
|
|
||||||
if (!data.meta.dns_challenge) {
|
|
||||||
data.domain_names.map(function (name) {
|
|
||||||
if (name.match(/\*/im)) {
|
|
||||||
domain_err = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domain_err) {
|
|
||||||
alert(i18n('ssl', 'no-wildcard-without-dns'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data.certificate_id = parseInt(data.certificate_id, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
let method = App.Api.Nginx.Streams.create;
|
let method = App.Api.Nginx.Streams.create;
|
||||||
let is_new = true;
|
let is_new = true;
|
||||||
|
|
||||||
@@ -113,108 +70,10 @@ module.exports = Mn.View.extend({
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
let more_info = '';
|
alert(err.message);
|
||||||
if (err.code === 500 && err.debug) {
|
|
||||||
try {
|
|
||||||
more_info = JSON.parse(err.debug).debug.stack.join("\n");
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>` : ''}`;
|
|
||||||
this.ui.le_error_info.show();
|
|
||||||
this.ui.le_error_info[0].scrollIntoView();
|
|
||||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||||
this.ui.save.removeClass('btn-loading');
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
'change @ui.certificate_select': function () {
|
|
||||||
let id = this.ui.certificate_select.val();
|
|
||||||
if (id === 'new') {
|
|
||||||
this.ui.letsencrypt.show().find('input').prop('disabled', false);
|
|
||||||
this.ui.domain_names.prop('required', 'required');
|
|
||||||
|
|
||||||
this.ui.dns_challenge_switch
|
|
||||||
.prop('disabled', true)
|
|
||||||
.parents('.form-group')
|
|
||||||
.css('opacity', 0.5);
|
|
||||||
|
|
||||||
this.ui.dns_provider.prop('required', 'required');
|
|
||||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
|
||||||
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
|
||||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
|
||||||
}
|
|
||||||
this.ui.dns_challenge_content.show();
|
|
||||||
} else {
|
|
||||||
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'change @ui.dns_provider': function () {
|
|
||||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
|
||||||
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
|
||||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
|
||||||
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
|
|
||||||
this.ui.credentials_file_content.show();
|
|
||||||
} else {
|
|
||||||
this.ui.dns_provider_credentials.prop('required', false);
|
|
||||||
this.ui.credentials_file_content.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
templateContext: {
|
|
||||||
getLetsencryptEmail: function () {
|
|
||||||
return App.Cache.User.get('email');
|
|
||||||
},
|
|
||||||
getDnsProvider: function () {
|
|
||||||
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
|
|
||||||
},
|
|
||||||
getDnsProviderCredentials: function () {
|
|
||||||
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
|
|
||||||
},
|
|
||||||
getPropagationSeconds: function () {
|
|
||||||
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
|
|
||||||
},
|
|
||||||
dns_plugins: dns_providers,
|
|
||||||
},
|
|
||||||
|
|
||||||
onRender: function () {
|
|
||||||
let view = this;
|
|
||||||
|
|
||||||
// Certificates
|
|
||||||
this.ui.le_error_info.hide();
|
|
||||||
this.ui.dns_challenge_content.hide();
|
|
||||||
this.ui.credentials_file_content.hide();
|
|
||||||
this.ui.letsencrypt.hide();
|
|
||||||
this.ui.certificate_select.selectize({
|
|
||||||
valueField: 'id',
|
|
||||||
labelField: 'nice_name',
|
|
||||||
searchField: ['nice_name', 'domain_names'],
|
|
||||||
create: false,
|
|
||||||
preload: true,
|
|
||||||
allowEmptyOption: true,
|
|
||||||
render: {
|
|
||||||
option: function (item) {
|
|
||||||
item.i18n = App.i18n;
|
|
||||||
item.formatDbDate = Helpers.formatDbDate;
|
|
||||||
return certListItemTemplate(item);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
load: function (query, callback) {
|
|
||||||
App.Api.Nginx.Certificates.getAll()
|
|
||||||
.then(rows => {
|
|
||||||
callback(rows);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onLoad: function () {
|
|
||||||
view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
|
@@ -16,10 +16,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>
|
<div>
|
||||||
<% if (certificate) { %>
|
<% if (tcp_forwarding) { %>
|
||||||
<span class="tag"><%- i18n('streams', 'tcp+ssl') %></span>
|
|
||||||
<% }
|
|
||||||
else if (tcp_forwarding) { %>
|
|
||||||
<span class="tag"><%- i18n('streams', 'tcp') %></span>
|
<span class="tag"><%- i18n('streams', 'tcp') %></span>
|
||||||
<% }
|
<% }
|
||||||
if (udp_forwarding) { %>
|
if (udp_forwarding) { %>
|
||||||
@@ -27,9 +24,6 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<%
|
<%
|
||||||
var o = isOnline();
|
var o = isOnline();
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
<th><%- i18n('streams', 'incoming-port') %></th>
|
<th><%- i18n('streams', 'incoming-port') %></th>
|
||||||
<th><%- i18n('str', 'destination') %></th>
|
<th><%- i18n('str', 'destination') %></th>
|
||||||
<th><%- i18n('streams', 'protocol') %></th>
|
<th><%- i18n('streams', 'protocol') %></th>
|
||||||
<th><%- i18n('str', 'ssl') %></th>
|
|
||||||
<th><%- i18n('str', 'status') %></th>
|
<th><%- i18n('str', 'status') %></th>
|
||||||
<% if (canManage) { %>
|
<% if (canManage) { %>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
|
@@ -88,7 +88,7 @@ module.exports = Mn.View.extend({
|
|||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
let view = this;
|
||||||
|
|
||||||
view.fetch(['owner', 'certificate'])
|
view.fetch(['owner'])
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!view.isDestroyed()) {
|
if (!view.isDestroyed()) {
|
||||||
if (response && response.length) {
|
if (response && response.length) {
|
||||||
|
@@ -25,27 +25,6 @@
|
|||||||
<div class="invalid-feedback secret-error"></div>
|
<div class="invalid-feedback secret-error"></div>
|
||||||
</div>
|
</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()) { %>
|
<% if (isAdmin() && !isSelf()) { %>
|
||||||
<div class="col-sm-12 col-md-12">
|
<div class="col-sm-12 col-md-12">
|
||||||
<div class="form-label"><%- i18n('roles', 'title') %></div>
|
<div class="form-label"><%- i18n('roles', 'title') %></div>
|
||||||
|
@@ -14,15 +14,7 @@ module.exports = Mn.View.extend({
|
|||||||
buttons: '.modal-footer button',
|
buttons: '.modal-footer button',
|
||||||
cancel: 'button.cancel',
|
cancel: 'button.cancel',
|
||||||
save: 'button.save',
|
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: {
|
events: {
|
||||||
@@ -33,10 +25,6 @@ module.exports = Mn.View.extend({
|
|||||||
let view = this;
|
let view = this;
|
||||||
let data = this.ui.form.serializeJSON();
|
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';
|
let show_password = this.model.get('email') === 'admin@example.com';
|
||||||
|
|
||||||
// admin@example.com is not allowed
|
// admin@example.com is not allowed
|
||||||
@@ -74,19 +62,6 @@ module.exports = Mn.View.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
view.model.set(result);
|
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 () {
|
App.UI.closeModal(function () {
|
||||||
if (method === App.Api.Users.create) {
|
if (method === App.Api.Users.create) {
|
||||||
// Show permissions dialog immediately
|
// Show permissions dialog immediately
|
||||||
@@ -97,50 +72,9 @@ module.exports = Mn.View.extend({
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (!err.mfaHandled) {
|
this.ui.error.text(err.message).show();
|
||||||
this.ui.error.text(err.message).show();
|
|
||||||
}
|
|
||||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
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();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -170,30 +104,5 @@ module.exports = Mn.View.extend({
|
|||||||
if (typeof options.model === 'undefined' || !options.model) {
|
if (typeof options.model === 'undefined' || !options.model) {
|
||||||
this.model = new UserModel.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,19 +37,8 @@
|
|||||||
"all": "All",
|
"all": "All",
|
||||||
"any": "Any"
|
"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": {
|
"login": {
|
||||||
"title": "Login to your account",
|
"title": "Login to your account"
|
||||||
"mfa-required-text": "Please enter your MFA token to continue"
|
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
"app": "Nginx Proxy Manager",
|
"app": "Nginx Proxy Manager",
|
||||||
@@ -190,9 +179,7 @@
|
|||||||
"delete-confirm": "Are you sure you want to delete this Stream?",
|
"delete-confirm": "Are you sure you want to delete this Stream?",
|
||||||
"help-title": "What is a Stream?",
|
"help-title": "What is a Stream?",
|
||||||
"help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.",
|
"help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.",
|
||||||
"search": "Search Incoming Port…",
|
"search": "Search Incoming Port…"
|
||||||
"ssl-certificate": "SSL Certificate for TCP Forwarding",
|
|
||||||
"tcp+ssl": "TCP+SSL"
|
|
||||||
},
|
},
|
||||||
"certificates": {
|
"certificates": {
|
||||||
"title": "SSL Certificates",
|
"title": "SSL Certificates",
|
||||||
@@ -219,10 +206,7 @@
|
|||||||
"reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
|
"reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"renew-title": "Renew Let's Encrypt Certificate",
|
"renew-title": "Renew Let's Encrypt Certificate",
|
||||||
"search": "Search Certificate…",
|
"search": "Search Certificate…"
|
||||||
"in-use" : "In use",
|
|
||||||
"inactive": "Inactive",
|
|
||||||
"active-domain_names": "Active domain names"
|
|
||||||
},
|
},
|
||||||
"access-lists": {
|
"access-lists": {
|
||||||
"title": "Access Lists",
|
"title": "Access Lists",
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-login mx-auto">
|
<div class="col col-login mx-auto">
|
||||||
@@ -25,12 +24,6 @@
|
|||||||
<input name="secret" type="password" class="form-control" placeholder="<%- i18n('str', 'password') %>" required>
|
<input name="secret" type="password" class="form-control" placeholder="<%- i18n('str', 'password') %>" required>
|
||||||
<div class="invalid-feedback secret-error"></div>
|
<div class="invalid-feedback secret-error"></div>
|
||||||
</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">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn btn-teal btn-block"><%- i18n('str', 'sign-in') %></button>
|
<button type="submit" class="btn btn-teal btn-block"><%- i18n('str', 'sign-in') %></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,4 +34,4 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -13,11 +13,7 @@ module.exports = Mn.View.extend({
|
|||||||
identity: 'input[name="identity"]',
|
identity: 'input[name="identity"]',
|
||||||
secret: 'input[name="secret"]',
|
secret: 'input[name="secret"]',
|
||||||
error: '.secret-error',
|
error: '.secret-error',
|
||||||
error_mfa:'.mfa-error',
|
button: 'button'
|
||||||
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: {
|
events: {
|
||||||
@@ -26,36 +22,14 @@ module.exports = Mn.View.extend({
|
|||||||
this.ui.button.addClass('btn-loading').prop('disabled', true);
|
this.ui.button.addClass('btn-loading').prop('disabled', true);
|
||||||
this.ui.error.hide();
|
this.ui.error.hide();
|
||||||
|
|
||||||
if(this.ui.mfaToken.val()) {
|
Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true)
|
||||||
Api.Tokens.loginWithMFA(this.ui.identity.val(), this.ui.secret.val(), this.ui.mfaToken.val(), true)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location = '/';
|
window.location = '/';
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'Invalid MFA token.') {
|
this.ui.error.text(err.message).show();
|
||||||
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);
|
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -66,5 +40,3 @@ module.exports = Mn.View.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@@ -15,11 +15,8 @@ const model = Backbone.Model.extend({
|
|||||||
udp_forwarding: false,
|
udp_forwarding: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
meta: {},
|
meta: {},
|
||||||
certificate_id: 0,
|
|
||||||
domain_names: [],
|
|
||||||
// The following are expansions:
|
// The following are expansions:
|
||||||
owner: null,
|
owner: null
|
||||||
certificate: null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -215,14 +215,6 @@
|
|||||||
"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN",
|
"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN",
|
||||||
"full_plugin_name": "dns-gandi"
|
"full_plugin_name": "dns-gandi"
|
||||||
},
|
},
|
||||||
"gcore": {
|
|
||||||
"name": "Gcore DNS",
|
|
||||||
"package_name": "certbot-dns-gcore",
|
|
||||||
"version": "~=0.1.8",
|
|
||||||
"dependencies": "",
|
|
||||||
"credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567",
|
|
||||||
"full_plugin_name": "dns-gcore"
|
|
||||||
},
|
|
||||||
"godaddy": {
|
"godaddy": {
|
||||||
"name": "GoDaddy",
|
"name": "GoDaddy",
|
||||||
"package_name": "certbot-dns-godaddy",
|
"package_name": "certbot-dns-godaddy",
|
||||||
@@ -364,7 +356,7 @@
|
|||||||
"package_name": "certbot-dns-mijn-host",
|
"package_name": "certbot-dns-mijn-host",
|
||||||
"version": "~=0.0.4",
|
"version": "~=0.0.4",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_mijn_host_api_key=0123456789abcdef0123456789abcdef",
|
"credentials": "dns-mijn-host-credentials = /etc/letsencrypt/mijnhost-credentials.ini",
|
||||||
"full_plugin_name": "dns-mijn-host"
|
"full_plugin_name": "dns-mijn-host"
|
||||||
},
|
},
|
||||||
"namecheap": {
|
"namecheap": {
|
||||||
@@ -535,4 +527,4 @@
|
|||||||
"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf",
|
"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf",
|
||||||
"full_plugin_name": "edgedns"
|
"full_plugin_name": "edgedns"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,22 +1,11 @@
|
|||||||
FROM cypress/included:14.0.1
|
FROM cypress/included:13.9.0
|
||||||
|
|
||||||
|
COPY --chown=1000 ./test /test
|
||||||
|
|
||||||
# Disable Cypress CLI colors
|
# Disable Cypress CLI colors
|
||||||
ENV FORCE_COLOR=0
|
ENV FORCE_COLOR=0
|
||||||
ENV NO_COLOR=1
|
ENV NO_COLOR=1
|
||||||
|
|
||||||
# testssl.sh and mkcert
|
|
||||||
RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \
|
|
||||||
&& tar -xzf /tmp/testssl.tgz -C /tmp \
|
|
||||||
&& mv /tmp/testssl.sh-3.2rc4 /testssl \
|
|
||||||
&& rm /tmp/testssl.tgz \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get install -y bsdmainutils curl dnsutils \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
|
||||||
&& wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \
|
|
||||||
&& chmod +x /bin/mkcert
|
|
||||||
|
|
||||||
COPY --chown=1000 ./test /test
|
|
||||||
WORKDIR /test
|
WORKDIR /test
|
||||||
RUN yarn install && yarn cache clean
|
RUN yarn install && yarn cache clean
|
||||||
ENTRYPOINT []
|
ENTRYPOINT []
|
||||||
|
@@ -1,213 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
describe('Streams', () => {
|
|
||||||
let token;
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.getToken().then((tok) => {
|
|
||||||
token = tok;
|
|
||||||
// Set default site content
|
|
||||||
cy.task('backendApiPut', {
|
|
||||||
token: token,
|
|
||||||
path: '/api/settings/default-site',
|
|
||||||
data: {
|
|
||||||
value: 'html',
|
|
||||||
meta: {
|
|
||||||
html: '<p>yay it works</p>'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a custom cert pair
|
|
||||||
cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => {
|
|
||||||
expect(result.code).to.eq(0);
|
|
||||||
// Install CA
|
|
||||||
cy.exec('mkcert -install').then((result) => {
|
|
||||||
expect(result.code).to.eq(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.exec('rm -f /test/results/testssl.json');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to create TCP Stream', function() {
|
|
||||||
cy.task('backendApiPost', {
|
|
||||||
token: token,
|
|
||||||
path: '/api/nginx/streams',
|
|
||||||
data: {
|
|
||||||
incoming_port: 1500,
|
|
||||||
forwarding_host: '127.0.0.1',
|
|
||||||
forwarding_port: 80,
|
|
||||||
certificate_id: 0,
|
|
||||||
meta: {
|
|
||||||
dns_provider_credentials: "",
|
|
||||||
letsencrypt_agree: false,
|
|
||||||
dns_challenge: true
|
|
||||||
},
|
|
||||||
tcp_forwarding: true,
|
|
||||||
udp_forwarding: false
|
|
||||||
}
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
|
||||||
expect(data).to.have.property('id');
|
|
||||||
expect(data.id).to.be.greaterThan(0);
|
|
||||||
expect(data).to.have.property('enabled', true);
|
|
||||||
expect(data).to.have.property('tcp_forwarding', true);
|
|
||||||
expect(data).to.have.property('udp_forwarding', false);
|
|
||||||
|
|
||||||
cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => {
|
|
||||||
expect(result.code).to.eq(0);
|
|
||||||
expect(result.stdout).to.contain('yay it works');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to create UDP Stream', function() {
|
|
||||||
cy.task('backendApiPost', {
|
|
||||||
token: token,
|
|
||||||
path: '/api/nginx/streams',
|
|
||||||
data: {
|
|
||||||
incoming_port: 1501,
|
|
||||||
forwarding_host: '127.0.0.1',
|
|
||||||
forwarding_port: 80,
|
|
||||||
certificate_id: 0,
|
|
||||||
meta: {
|
|
||||||
dns_provider_credentials: "",
|
|
||||||
letsencrypt_agree: false,
|
|
||||||
dns_challenge: true
|
|
||||||
},
|
|
||||||
tcp_forwarding: false,
|
|
||||||
udp_forwarding: true
|
|
||||||
}
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
|
||||||
expect(data).to.have.property('id');
|
|
||||||
expect(data.id).to.be.greaterThan(0);
|
|
||||||
expect(data).to.have.property('enabled', true);
|
|
||||||
expect(data).to.have.property('tcp_forwarding', false);
|
|
||||||
expect(data).to.have.property('udp_forwarding', true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to create TCP/UDP Stream', function() {
|
|
||||||
cy.task('backendApiPost', {
|
|
||||||
token: token,
|
|
||||||
path: '/api/nginx/streams',
|
|
||||||
data: {
|
|
||||||
incoming_port: 1502,
|
|
||||||
forwarding_host: '127.0.0.1',
|
|
||||||
forwarding_port: 80,
|
|
||||||
certificate_id: 0,
|
|
||||||
meta: {
|
|
||||||
dns_provider_credentials: "",
|
|
||||||
letsencrypt_agree: false,
|
|
||||||
dns_challenge: true
|
|
||||||
},
|
|
||||||
tcp_forwarding: true,
|
|
||||||
udp_forwarding: true
|
|
||||||
}
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
|
||||||
expect(data).to.have.property('id');
|
|
||||||
expect(data.id).to.be.greaterThan(0);
|
|
||||||
expect(data).to.have.property('enabled', true);
|
|
||||||
expect(data).to.have.property('tcp_forwarding', true);
|
|
||||||
expect(data).to.have.property('udp_forwarding', true);
|
|
||||||
|
|
||||||
cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => {
|
|
||||||
expect(result.code).to.eq(0);
|
|
||||||
expect(result.stdout).to.contain('yay it works');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to create SSL TCP Stream', function() {
|
|
||||||
let certID = 0;
|
|
||||||
|
|
||||||
// Create custom cert
|
|
||||||
cy.task('backendApiPost', {
|
|
||||||
token: token,
|
|
||||||
path: '/api/nginx/certificates',
|
|
||||||
data: {
|
|
||||||
provider: "other",
|
|
||||||
nice_name: "Custom Certificate for SSL Stream",
|
|
||||||
},
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data);
|
|
||||||
expect(data).to.have.property('id');
|
|
||||||
certID = data.id;
|
|
||||||
|
|
||||||
// Upload files
|
|
||||||
cy.task('backendApiPostFiles', {
|
|
||||||
token: token,
|
|
||||||
path: `/api/nginx/certificates/${certID}/upload`,
|
|
||||||
files: {
|
|
||||||
certificate: 'website1.pem',
|
|
||||||
certificate_key: 'website1.key.pem',
|
|
||||||
},
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data);
|
|
||||||
expect(data).to.have.property('certificate');
|
|
||||||
expect(data).to.have.property('certificate_key');
|
|
||||||
|
|
||||||
// Create the stream
|
|
||||||
cy.task('backendApiPost', {
|
|
||||||
token: token,
|
|
||||||
path: '/api/nginx/streams',
|
|
||||||
data: {
|
|
||||||
incoming_port: 1503,
|
|
||||||
forwarding_host: '127.0.0.1',
|
|
||||||
forwarding_port: 80,
|
|
||||||
certificate_id: certID,
|
|
||||||
meta: {
|
|
||||||
dns_provider_credentials: "",
|
|
||||||
letsencrypt_agree: false,
|
|
||||||
dns_challenge: true
|
|
||||||
},
|
|
||||||
tcp_forwarding: true,
|
|
||||||
udp_forwarding: false
|
|
||||||
}
|
|
||||||
}).then((data) => {
|
|
||||||
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
|
||||||
expect(data).to.have.property('id');
|
|
||||||
expect(data.id).to.be.greaterThan(0);
|
|
||||||
expect(data).to.have.property("enabled", true);
|
|
||||||
expect(data).to.have.property('tcp_forwarding', true);
|
|
||||||
expect(data).to.have.property('udp_forwarding', false);
|
|
||||||
expect(data).to.have.property('certificate_id', certID);
|
|
||||||
|
|
||||||
// Check the ssl termination
|
|
||||||
cy.task('log', '[testssl.sh] Running ...');
|
|
||||||
cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', {
|
|
||||||
timeout: 120000, // 2 minutes
|
|
||||||
}).then((result) => {
|
|
||||||
cy.task('log', '[testssl.sh] ' + result.stdout);
|
|
||||||
|
|
||||||
const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"];
|
|
||||||
const ignoredIDs = [
|
|
||||||
'cert_chain_of_trust',
|
|
||||||
'cert_extlifeSpan',
|
|
||||||
'cert_revocation',
|
|
||||||
'overall_grade',
|
|
||||||
];
|
|
||||||
|
|
||||||
cy.readFile('/test/results/testssl.json').then((data) => {
|
|
||||||
// Parse each array item
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
const item = data[i];
|
|
||||||
if (ignoredIDs.includes(item.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
expect(item.severity).to.be.oneOf(allowedSeverities);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@@ -4,18 +4,18 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jc21/cypress-swagger-validation": "^0.3.2",
|
"@jc21/cypress-swagger-validation": "^0.3.1",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.7",
|
||||||
"cypress": "^14.0.1",
|
"cypress": "^13.15.0",
|
||||||
"cypress-multi-reporters": "^2.0.5",
|
"cypress-multi-reporters": "^1.6.4",
|
||||||
"cypress-wait-until": "^3.0.2",
|
"cypress-wait-until": "^3.0.2",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-align-assignments": "^1.1.2",
|
"eslint-plugin-align-assignments": "^1.1.2",
|
||||||
"eslint-plugin-chai-friendly": "^1.0.1",
|
"eslint-plugin-chai-friendly": "^1.0.1",
|
||||||
"eslint-plugin-cypress": "^4.1.0",
|
"eslint-plugin-cypress": "^3.5.0",
|
||||||
"form-data": "^4.0.1",
|
"form-data": "^4.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mocha": "^11.1.0",
|
"mocha": "^10.7.3",
|
||||||
"mocha-junit-reporter": "^2.2.1"
|
"mocha-junit-reporter": "^2.2.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
Reference in New Issue
Block a user