mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-06-18 18:16:26 +00:00
Compare commits
19 Commits
v2.12.2
...
1a030a6ddd
Author | SHA1 | Date | |
---|---|---|---|
1a030a6ddd | |||
7ef52d8ed4 | |||
0b126ca546 | |||
7196dfa1ad | |||
8b841176fa | |||
0b09f03f49 | |||
0f588baa3e | |||
6ed64153e7 | |||
d0d36a95ec | |||
fd49644f21 | |||
ef64edd943 | |||
df5ab361e3 | |||
6f98fa61e4 | |||
baee4641db | |||
bc0b466a8e | |||
8350271e6f | |||
457d1a75ba | |||
3e2a411dfb | |||
caeb2934f0 |
38
Jenkinsfile
vendored
38
Jenkinsfile
vendored
@ -167,44 +167,6 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Test Postgres') {
|
|
||||||
environment {
|
|
||||||
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres"
|
|
||||||
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml'
|
|
||||||
}
|
|
||||||
when {
|
|
||||||
not {
|
|
||||||
equals expected: 'UNSTABLE', actual: currentBuild.result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'rm -rf ./test/results/junit/*'
|
|
||||||
sh './scripts/ci/fulltest-cypress'
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
// Dumps to analyze later
|
|
||||||
sh 'mkdir -p debug/postgres'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1'
|
|
||||||
sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1'
|
|
||||||
|
|
||||||
junit 'test/results/junit/*'
|
|
||||||
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
|
||||||
}
|
|
||||||
unstable {
|
|
||||||
dir(path: 'testing/results') {
|
|
||||||
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('MultiArch Build') {
|
stage('MultiArch Build') {
|
||||||
when {
|
when {
|
||||||
not {
|
not {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginxproxymanager.com/github.png">
|
<img src="https://nginxproxymanager.com/github.png">
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/badge/version-2.12.2-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.12.1-green.svg?style=for-the-badge">
|
||||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||||
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
@ -81,7 +81,7 @@ const internalAccessList = {
|
|||||||
|
|
||||||
return internalAccessList.build(row)
|
return internalAccessList.build(row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (parseInt(row.proxy_host_count, 10)) {
|
if (row.proxy_host_count) {
|
||||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -223,7 +223,7 @@ const internalAccessList = {
|
|||||||
.then((row) => {
|
.then((row) => {
|
||||||
return internalAccessList.build(row)
|
return internalAccessList.build(row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (parseInt(row.proxy_host_count, 10)) {
|
if (row.proxy_host_count) {
|
||||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||||
}
|
}
|
||||||
}).then(internalNginx.reload)
|
}).then(internalNginx.reload)
|
||||||
@ -252,10 +252,7 @@ const internalAccessList = {
|
|||||||
let query = accessListModel
|
let query = accessListModel
|
||||||
.query()
|
.query()
|
||||||
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
||||||
.leftJoin('proxy_host', function() {
|
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||||
this.on('proxy_host.access_list_id', '=', 'access_list.id')
|
|
||||||
.andOn('proxy_host.is_deleted', '=', 0);
|
|
||||||
})
|
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.andWhere('access_list.id', data.id)
|
.andWhere('access_list.id', data.id)
|
||||||
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
||||||
@ -376,10 +373,7 @@ const internalAccessList = {
|
|||||||
let query = accessListModel
|
let query = accessListModel
|
||||||
.query()
|
.query()
|
||||||
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
||||||
.leftJoin('proxy_host', function() {
|
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||||
this.on('proxy_host.access_list_id', '=', 'access_list.id')
|
|
||||||
.andOn('proxy_host.is_deleted', '=', 0);
|
|
||||||
})
|
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.groupBy('access_list.id')
|
.groupBy('access_list.id')
|
||||||
.allowGraph('[owner,items,clients]')
|
.allowGraph('[owner,items,clients]')
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const auditLogModel = require('../models/audit-log');
|
const auditLogModel = require('../models/audit-log');
|
||||||
const {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
const internalAuditLog = {
|
const internalAuditLog = {
|
||||||
|
|
||||||
@ -23,9 +22,9 @@ const internalAuditLog = {
|
|||||||
.allowGraph('[user]');
|
.allowGraph('[user]');
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string' && search_query.length > 0) {
|
if (typeof search_query === 'string') {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%');
|
this.where('meta', 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ const internalHost = require('./host');
|
|||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const internalCertificate = require('./certificate');
|
||||||
const {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -410,16 +409,16 @@ const internalDeadHost = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.allowGraph('[owner,certificate]')
|
||||||
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
|
.orderBy('domain_names', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string' && search_query.length > 0) {
|
if (typeof search_query === 'string') {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%');
|
this.where('domain_names', 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ const _ = require('lodash');
|
|||||||
const proxyHostModel = require('../models/proxy_host');
|
const proxyHostModel = require('../models/proxy_host');
|
||||||
const redirectionHostModel = require('../models/redirection_host');
|
const redirectionHostModel = require('../models/redirection_host');
|
||||||
const deadHostModel = require('../models/dead_host');
|
const deadHostModel = require('../models/dead_host');
|
||||||
const {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
const internalHost = {
|
const internalHost = {
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ const internalHost = {
|
|||||||
cleanSslHstsData: function (data, existing_data) {
|
cleanSslHstsData: function (data, existing_data) {
|
||||||
existing_data = existing_data === undefined ? {} : existing_data;
|
existing_data = existing_data === undefined ? {} : existing_data;
|
||||||
|
|
||||||
const combined_data = _.assign({}, existing_data, data);
|
let combined_data = _.assign({}, existing_data, data);
|
||||||
|
|
||||||
if (!combined_data.certificate_id) {
|
if (!combined_data.certificate_id) {
|
||||||
combined_data.ssl_forced = false;
|
combined_data.ssl_forced = false;
|
||||||
@ -74,7 +73,7 @@ const internalHost = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
getHostsWithDomains: function (domain_names) {
|
getHostsWithDomains: function (domain_names) {
|
||||||
const promises = [
|
let promises = [
|
||||||
proxyHostModel
|
proxyHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0),
|
.where('is_deleted', 0),
|
||||||
@ -126,19 +125,19 @@ const internalHost = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
|
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
|
||||||
const promises = [
|
let promises = [
|
||||||
proxyHostModel
|
proxyHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
|
.andWhere('domain_names', 'like', '%' + hostname + '%'),
|
||||||
redirectionHostModel
|
redirectionHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
|
.andWhere('domain_names', 'like', '%' + hostname + '%'),
|
||||||
deadHostModel
|
deadHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%')
|
.andWhere('domain_names', 'like', '%' + hostname + '%')
|
||||||
];
|
];
|
||||||
|
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
|
@ -6,7 +6,6 @@ const internalHost = require('./host');
|
|||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const internalCertificate = require('./certificate');
|
||||||
const {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted', 'owner.is_deleted'];
|
return ['is_deleted', 'owner.is_deleted'];
|
||||||
@ -417,16 +416,16 @@ const internalProxyHost = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,access_list,certificate]')
|
.allowGraph('[owner,access_list,certificate]')
|
||||||
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
|
.orderBy('domain_names', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string' && search_query.length > 0) {
|
if (typeof search_query === 'string') {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
|
this.where('domain_names', 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ const internalHost = require('./host');
|
|||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const internalCertificate = require('./certificate');
|
||||||
const {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -410,16 +409,16 @@ const internalRedirectionHost = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.allowGraph('[owner,certificate]')
|
||||||
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
|
.orderBy('domain_names', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string' && search_query.length > 0) {
|
if (typeof search_query === 'string') {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
|
this.where('domain_names', 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ 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 {castJsonIfNeed} = require('../lib/helpers');
|
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -294,21 +293,21 @@ const internalStream = {
|
|||||||
getAll: (access, expand, search_query) => {
|
getAll: (access, expand, search_query) => {
|
||||||
return access.can('streams:list')
|
return access.can('streams:list')
|
||||||
.then((access_data) => {
|
.then((access_data) => {
|
||||||
const query = streamModel
|
let query = streamModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner]')
|
||||||
.orderByRaw('CAST(incoming_port AS INTEGER) ASC');
|
.orderBy('incoming_port', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string' && search_query.length > 0) {
|
if (typeof search_query === 'string') {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`);
|
this.where('incoming_port', 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,9 +327,9 @@ const internalStream = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
getCount: (user_id, visibility) => {
|
getCount: (user_id, visibility) => {
|
||||||
const query = streamModel
|
let query = streamModel
|
||||||
.query()
|
.query()
|
||||||
.count('id AS count')
|
.count('id as count')
|
||||||
.where('is_deleted', 0);
|
.where('is_deleted', 0);
|
||||||
|
|
||||||
if (visibility !== 'all') {
|
if (visibility !== 'all') {
|
||||||
|
@ -5,8 +5,6 @@ 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 ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,19 +69,60 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
throw new error.AuthError('Invalid password');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
throw new error.AuthError('No password auth for user');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
throw new error.AuthError('No relevant user found');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {String} data.identity
|
||||||
|
* @param {String} [issuer]
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
getTokenFromOAuthClaim: (data) => {
|
||||||
|
let Token = new TokenModel();
|
||||||
|
|
||||||
|
data.scope = 'user';
|
||||||
|
data.expiry = '1d';
|
||||||
|
|
||||||
|
return userModel
|
||||||
|
.query()
|
||||||
|
.where('email', data.identity)
|
||||||
|
.andWhere('is_deleted', 0)
|
||||||
|
.andWhere('is_disabled', 0)
|
||||||
|
.first()
|
||||||
|
.then((user) => {
|
||||||
|
if (!user) {
|
||||||
|
throw new error.AuthError('No relevant user found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
let iss = 'api',
|
||||||
|
attrs = { id: user.id },
|
||||||
|
scope = [ data.scope ],
|
||||||
|
expiresIn = data.expiry;
|
||||||
|
|
||||||
|
return Token.create({ iss, attrs, scope, expiresIn })
|
||||||
|
.then((signed) => {
|
||||||
|
return { token: signed.token, expires: expiry.toISOString() };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Access} access
|
* @param {Access} access
|
||||||
* @param {Object} [data]
|
* @param {Object} [data]
|
||||||
|
@ -3,9 +3,6 @@ const NodeRSA = require('node-rsa');
|
|||||||
const logger = require('../logger').global;
|
const logger = require('../logger').global;
|
||||||
|
|
||||||
const keysFile = '/data/keys.json';
|
const keysFile = '/data/keys.json';
|
||||||
const mysqlEngine = 'mysql2';
|
|
||||||
const postgresEngine = 'pg';
|
|
||||||
const sqliteClientName = 'sqlite3';
|
|
||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
|
|
||||||
@ -17,7 +14,7 @@ const configure = () => {
|
|||||||
let configData;
|
let configData;
|
||||||
try {
|
try {
|
||||||
configData = require(filename);
|
configData = require(filename);
|
||||||
} catch (_) {
|
} catch (err) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +34,7 @@ const configure = () => {
|
|||||||
logger.info('Using MySQL configuration');
|
logger.info('Using MySQL configuration');
|
||||||
instance = {
|
instance = {
|
||||||
database: {
|
database: {
|
||||||
engine: mysqlEngine,
|
engine: 'mysql2',
|
||||||
host: envMysqlHost,
|
host: envMysqlHost,
|
||||||
port: process.env.DB_MYSQL_PORT || 3306,
|
port: process.env.DB_MYSQL_PORT || 3306,
|
||||||
user: envMysqlUser,
|
user: envMysqlUser,
|
||||||
@ -49,33 +46,13 @@ const configure = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envPostgresHost = process.env.DB_POSTGRES_HOST || null;
|
|
||||||
const envPostgresUser = process.env.DB_POSTGRES_USER || null;
|
|
||||||
const envPostgresName = process.env.DB_POSTGRES_NAME || null;
|
|
||||||
if (envPostgresHost && envPostgresUser && envPostgresName) {
|
|
||||||
// we have enough postgres creds to go with postgres
|
|
||||||
logger.info('Using Postgres configuration');
|
|
||||||
instance = {
|
|
||||||
database: {
|
|
||||||
engine: postgresEngine,
|
|
||||||
host: envPostgresHost,
|
|
||||||
port: process.env.DB_POSTGRES_PORT || 5432,
|
|
||||||
user: envPostgresUser,
|
|
||||||
password: process.env.DB_POSTGRES_PASSWORD,
|
|
||||||
name: envPostgresName,
|
|
||||||
},
|
|
||||||
keys: getKeys(),
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
|
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
|
||||||
logger.info(`Using Sqlite: ${envSqliteFile}`);
|
logger.info(`Using Sqlite: ${envSqliteFile}`);
|
||||||
instance = {
|
instance = {
|
||||||
database: {
|
database: {
|
||||||
engine: 'knex-native',
|
engine: 'knex-native',
|
||||||
knex: {
|
knex: {
|
||||||
client: sqliteClientName,
|
client: 'sqlite3',
|
||||||
connection: {
|
connection: {
|
||||||
filename: envSqliteFile
|
filename: envSqliteFile
|
||||||
},
|
},
|
||||||
@ -166,27 +143,7 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
isSqlite: function () {
|
isSqlite: function () {
|
||||||
instance === null && configure();
|
instance === null && configure();
|
||||||
return instance.database.knex && instance.database.knex.client === sqliteClientName;
|
return instance.database.knex && instance.database.knex.client === 'sqlite3';
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this a mysql configuration?
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isMysql: function () {
|
|
||||||
instance === null && configure();
|
|
||||||
return instance.database.engine === mysqlEngine;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this a postgres configuration?
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isPostgres: function () {
|
|
||||||
instance === null && configure();
|
|
||||||
return instance.database.engine === postgresEngine;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,7 +4,14 @@ module.exports = () => {
|
|||||||
return function (req, res, next) {
|
return function (req, res, next) {
|
||||||
res.locals.access = null;
|
res.locals.access = null;
|
||||||
let access = new Access(res.locals.token || null);
|
let access = new Access(res.locals.token || null);
|
||||||
access.load()
|
|
||||||
|
// Allow unauthenticated access to get the oidc configuration
|
||||||
|
let oidc_access =
|
||||||
|
req.url === '/oidc-config' &&
|
||||||
|
req.method === 'GET' &&
|
||||||
|
!access.token.getUserId();
|
||||||
|
|
||||||
|
access.load(oidc_access)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
res.locals.access = access;
|
res.locals.access = access;
|
||||||
next();
|
next();
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const {isPostgres} = require('./config');
|
|
||||||
const {ref} = require('objection');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@ -47,16 +45,6 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return obj;
|
return obj;
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Casts a column to json if using postgres
|
|
||||||
*
|
|
||||||
* @param {string} colName
|
|
||||||
* @returns {string|Objection.ReferenceBuilder}
|
|
||||||
*/
|
|
||||||
castJsonIfNeed: function (colName) {
|
|
||||||
return isPostgres() ? ref(colName).castText() : colName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -10,5 +10,6 @@ module.exports = {
|
|||||||
certbot: new Signale({scope: 'Certbot '}),
|
certbot: new Signale({scope: 'Certbot '}),
|
||||||
import: new Signale({scope: 'Importer '}),
|
import: new Signale({scope: 'Importer '}),
|
||||||
setup: new Signale({scope: 'Setup '}),
|
setup: new Signale({scope: 'Setup '}),
|
||||||
ip_ranges: new Signale({scope: 'IP Ranges'})
|
ip_ranges: new Signale({scope: 'IP Ranges'}),
|
||||||
|
oidc: new Signale({scope: 'OIDC '})
|
||||||
};
|
};
|
||||||
|
@ -17,9 +17,6 @@ const boolFields = [
|
|||||||
'preserve_path',
|
'preserve_path',
|
||||||
'ssl_forced',
|
'ssl_forced',
|
||||||
'block_exploits',
|
'block_exploits',
|
||||||
'hsts_enabled',
|
|
||||||
'hsts_subdomains',
|
|
||||||
'http2_support',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
class RedirectionHost extends Model {
|
class RedirectionHost extends Model {
|
||||||
|
@ -21,9 +21,10 @@
|
|||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"mysql2": "^3.11.1",
|
"mysql2": "^3.11.1",
|
||||||
"node-rsa": "^1.0.8",
|
"node-rsa": "^1.0.8",
|
||||||
|
"nodemon": "^2.0.2",
|
||||||
|
"openid-client": "^5.4.0",
|
||||||
"objection": "3.0.1",
|
"objection": "3.0.1",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pg": "^8.13.1",
|
|
||||||
"signale": "1.4.0",
|
"signale": "1.4.0",
|
||||||
"sqlite3": "5.1.6",
|
"sqlite3": "5.1.6",
|
||||||
"temp-write": "^4.0.0"
|
"temp-write": "^4.0.0"
|
||||||
|
@ -27,6 +27,7 @@ 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('/oidc', require('./oidc'));
|
||||||
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'));
|
||||||
|
168
backend/routes/oidc.js
Normal file
168
backend/routes/oidc.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
const error = require('../lib/error');
|
||||||
|
const express = require('express');
|
||||||
|
const jwtdecode = require('../lib/express/jwt-decode');
|
||||||
|
const logger = require('../logger').oidc;
|
||||||
|
const oidc = require('openid-client');
|
||||||
|
const settingModel = require('../models/setting');
|
||||||
|
const internalToken = require('../internal/token');
|
||||||
|
|
||||||
|
let router = express.Router({
|
||||||
|
caseSensitive: true,
|
||||||
|
strict: true,
|
||||||
|
mergeParams: true
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route('/')
|
||||||
|
.options((req, res) => {
|
||||||
|
res.sendStatus(204);
|
||||||
|
})
|
||||||
|
.all(jwtdecode())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/oidc
|
||||||
|
*
|
||||||
|
* OAuth Authorization Code flow initialisation
|
||||||
|
*/
|
||||||
|
.get(jwtdecode(), async (req, res) => {
|
||||||
|
logger.info('Initializing OAuth flow');
|
||||||
|
settingModel
|
||||||
|
.query()
|
||||||
|
.where({id: 'oidc-config'})
|
||||||
|
.first()
|
||||||
|
.then((row) => getInitParams(req, row))
|
||||||
|
.then((params) => redirectToAuthorizationURL(res, params))
|
||||||
|
.catch((err) => redirectWithError(res, err));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router
|
||||||
|
.route('/callback')
|
||||||
|
.options((req, res) => {
|
||||||
|
res.sendStatus(204);
|
||||||
|
})
|
||||||
|
.all(jwtdecode())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/oidc/callback
|
||||||
|
*
|
||||||
|
* Oauth Authorization Code flow callback
|
||||||
|
*/
|
||||||
|
.get(jwtdecode(), async (req, res) => {
|
||||||
|
logger.info('Processing callback');
|
||||||
|
settingModel
|
||||||
|
.query()
|
||||||
|
.where({id: 'oidc-config'})
|
||||||
|
.first()
|
||||||
|
.then((settings) => validateCallback(req, settings))
|
||||||
|
.then((token) => redirectWithJwtToken(res, token))
|
||||||
|
.catch((err) => redirectWithError(res, err));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes discovery and returns the configured `openid-client` client
|
||||||
|
*
|
||||||
|
* @param {Setting} row
|
||||||
|
* */
|
||||||
|
let getClient = async (row) => {
|
||||||
|
let issuer;
|
||||||
|
try {
|
||||||
|
issuer = await oidc.Issuer.discover(row.meta.issuerURL);
|
||||||
|
} catch (err) {
|
||||||
|
throw new error.AuthError(`Discovery failed for the specified URL with message: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new issuer.Client({
|
||||||
|
client_id: row.meta.clientID,
|
||||||
|
client_secret: row.meta.clientSecret,
|
||||||
|
redirect_uris: [row.meta.redirectURL],
|
||||||
|
response_types: ['code'],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates state, nonce and authorization url.
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Setting} row
|
||||||
|
* @return { {String}, {String}, {String} } state, nonce and url
|
||||||
|
* */
|
||||||
|
let getInitParams = async (req, row) => {
|
||||||
|
let client = await getClient(row),
|
||||||
|
state = crypto.randomUUID(),
|
||||||
|
nonce = crypto.randomUUID(),
|
||||||
|
url = client.authorizationUrl({
|
||||||
|
scope: 'openid email profile',
|
||||||
|
resource: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
|
||||||
|
state,
|
||||||
|
nonce,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { state, nonce, url };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses state and nonce from cookie during the callback phase.
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @return { {String}, {String} } state and nonce
|
||||||
|
* */
|
||||||
|
let parseStateFromCookie = (req) => {
|
||||||
|
let state, nonce;
|
||||||
|
let cookies = req.headers.cookie.split(';');
|
||||||
|
for (let cookie of cookies) {
|
||||||
|
if (cookie.split('=')[0].trim() === 'npm_oidc') {
|
||||||
|
let raw = cookie.split('=')[1],
|
||||||
|
val = raw.split('--');
|
||||||
|
state = val[0].trim();
|
||||||
|
nonce = val[1].trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { state, nonce };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes validation of callback parameters.
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Setting} settings
|
||||||
|
* @return {Promise} a promise resolving to a jwt token
|
||||||
|
* */
|
||||||
|
let validateCallback = async (req, settings) => {
|
||||||
|
let client = await getClient(settings);
|
||||||
|
let { state, nonce } = parseStateFromCookie(req);
|
||||||
|
|
||||||
|
const params = client.callbackParams(req);
|
||||||
|
const tokenSet = await client.callback(settings.meta.redirectURL, params, { state, nonce });
|
||||||
|
let claims = tokenSet.claims();
|
||||||
|
|
||||||
|
if (!claims.email) {
|
||||||
|
throw new error.AuthError('The Identity Provider didn\'t send the \'email\' claim');
|
||||||
|
} else {
|
||||||
|
logger.info('Successful authentication for email ' + claims.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
return internalToken.getTokenFromOAuthClaim({ identity: claims.email });
|
||||||
|
};
|
||||||
|
|
||||||
|
let redirectToAuthorizationURL = (res, params) => {
|
||||||
|
logger.info('Authorization URL: ' + params.url);
|
||||||
|
res.cookie('npm_oidc', params.state + '--' + params.nonce);
|
||||||
|
res.redirect(params.url);
|
||||||
|
};
|
||||||
|
|
||||||
|
let redirectWithJwtToken = (res, token) => {
|
||||||
|
res.cookie('npm_oidc', token.token + '---' + token.expires);
|
||||||
|
res.redirect('/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
let redirectWithError = (res, error) => {
|
||||||
|
logger.error('Callback error: ' + error.message);
|
||||||
|
res.cookie('npm_oidc_error', error.message);
|
||||||
|
res.redirect('/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = router;
|
@ -71,6 +71,18 @@ router
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
|
if (row.id === 'oidc-config') {
|
||||||
|
// Redact oidc configuration via api (unauthenticated get call)
|
||||||
|
let m = row.meta;
|
||||||
|
row.meta = {
|
||||||
|
name: m.name,
|
||||||
|
enabled: m.enabled === true && !!(m.clientID && m.clientSecret && m.issuerURL && m.redirectURL && m.name)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove these temporary cookies used during oidc authentication
|
||||||
|
res.clearCookie('npm_oidc');
|
||||||
|
res.clearCookie('npm_oidc_error');
|
||||||
|
}
|
||||||
res.status(200)
|
res.status(200)
|
||||||
.send(row);
|
.send(row);
|
||||||
})
|
})
|
||||||
|
@ -29,6 +29,8 @@ router
|
|||||||
scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null)
|
scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null)
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
// clear this temporary cookie following a successful oidc authentication
|
||||||
|
res.clearCookie('npm_oidc');
|
||||||
res.status(200)
|
res.status(200)
|
||||||
.send(data);
|
.send(data);
|
||||||
})
|
})
|
||||||
|
@ -19,9 +19,7 @@
|
|||||||
"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": [
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"enum": ["default-site"]
|
"enum": ["default-site", "oidc-config"]
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "Setting ID",
|
"description": "Setting ID",
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"minProperties": 1,
|
"minProperties": 1,
|
||||||
@ -34,7 +36,13 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"enum": ["congratulations", "404", "444", "redirect", "html"]
|
"enum": [
|
||||||
|
"congratulations",
|
||||||
|
"404",
|
||||||
|
"444",
|
||||||
|
"redirect",
|
||||||
|
"html"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -49,6 +57,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1,
|
||||||
|
"properties": {
|
||||||
|
"meta": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"clientID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientSecret": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"issuerURL": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"redirectURL": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,18 +15,18 @@ const certbot = require('./lib/certbot');
|
|||||||
const setupDefaultUser = () => {
|
const setupDefaultUser = () => {
|
||||||
return userModel
|
return userModel
|
||||||
.query()
|
.query()
|
||||||
.select('id', )
|
.select(userModel.raw('COUNT(`id`) as `count`'))
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.first()
|
.first()
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row || !row.id) {
|
if (!row.count) {
|
||||||
// Create a new user and set password
|
// Create a new user and set password
|
||||||
const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
|
let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
|
||||||
const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
|
let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
|
||||||
|
|
||||||
logger.info('Creating a new user: ' + email + ' with password: ' + password);
|
logger.info('Creating a new user: ' + email + ' with password: ' + password);
|
||||||
|
|
||||||
const data = {
|
let data = {
|
||||||
is_deleted: 0,
|
is_deleted: 0,
|
||||||
email: email,
|
email: email,
|
||||||
name: 'Administrator',
|
name: 'Administrator',
|
||||||
@ -75,13 +75,14 @@ const setupDefaultUser = () => {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
const setupDefaultSettings = () => {
|
const setupDefaultSettings = () => {
|
||||||
return settingModel
|
return Promise.all([
|
||||||
|
settingModel
|
||||||
.query()
|
.query()
|
||||||
.select('id')
|
.select(settingModel.raw('COUNT(`id`) as `count`'))
|
||||||
.where({id: 'default-site'})
|
.where({id: 'default-site'})
|
||||||
.first()
|
.first()
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row || !row.id) {
|
if (!row.count) {
|
||||||
settingModel
|
settingModel
|
||||||
.query()
|
.query()
|
||||||
.insert({
|
.insert({
|
||||||
@ -92,13 +93,38 @@ const setupDefaultSettings = () => {
|
|||||||
meta: {},
|
meta: {},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Default settings added');
|
logger.info('Added default-site setting');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (config.debug()) {
|
if (config.debug()) {
|
||||||
logger.info('Default setting setup not required');
|
logger.info('Default setting setup not required');
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
settingModel
|
||||||
|
.query()
|
||||||
|
.select(settingModel.raw('COUNT(`id`) as `count`'))
|
||||||
|
.where({id: 'oidc-config'})
|
||||||
|
.first()
|
||||||
|
.then((row) => {
|
||||||
|
if (!row.count) {
|
||||||
|
settingModel
|
||||||
|
.query()
|
||||||
|
.insert({
|
||||||
|
id: 'oidc-config',
|
||||||
|
name: 'Open ID Connect',
|
||||||
|
description: 'Sign in to Nginx Proxy Manager with an external Identity Provider',
|
||||||
|
value: 'metadata',
|
||||||
|
meta: {},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info('Added oidc-config setting');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (config.debug()) {
|
||||||
|
logger.info('Default setting setup not required');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +22,5 @@ server {
|
|||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Custom
|
|
||||||
include /data/nginx/custom/server_dead[.]conf;
|
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
1886
backend/yarn.lock
1886
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
|||||||
AUTHENTIK_SECRET_KEY=gl8woZe8L6IIX8SC0c5Ocsj0xPkX5uJo5DVZCFl+L/QGbzuplfutYuua2ODNLEiDD3aFd9H2ylJmrke0
|
|
||||||
AUTHENTIK_REDIS__HOST=authentik-redis
|
|
||||||
AUTHENTIK_POSTGRESQL__HOST=db-postgres
|
|
||||||
AUTHENTIK_POSTGRESQL__USER=authentik
|
|
||||||
AUTHENTIK_POSTGRESQL__NAME=authentik
|
|
||||||
AUTHENTIK_POSTGRESQL__PASSWORD=07EKS5NLI6Tpv68tbdvrxfvj
|
|
||||||
AUTHENTIK_BOOTSTRAP_PASSWORD=admin
|
|
||||||
AUTHENTIK_BOOTSTRAP_EMAIL=admin@example.com
|
|
Binary file not shown.
@ -29,8 +29,7 @@ COPY scripts/install-s6 /tmp/install-s6
|
|||||||
RUN rm -f /etc/nginx/conf.d/production.conf \
|
RUN rm -f /etc/nginx/conf.d/production.conf \
|
||||||
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
||||||
&& /tmp/install-s6 "${TARGETPLATFORM}" \
|
&& /tmp/install-s6 "${TARGETPLATFORM}" \
|
||||||
&& rm -f /tmp/install-s6 \
|
&& rm -f /tmp/install-s6
|
||||||
&& chmod 644 -R /root/.cache
|
|
||||||
|
|
||||||
# Certs for testing purposes
|
# Certs for testing purposes
|
||||||
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
|
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
|
||||||
services:
|
|
||||||
|
|
||||||
cypress:
|
|
||||||
environment:
|
|
||||||
CYPRESS_stack: 'postgres'
|
|
||||||
|
|
||||||
fullstack:
|
|
||||||
environment:
|
|
||||||
DB_POSTGRES_HOST: 'db-postgres'
|
|
||||||
DB_POSTGRES_PORT: '5432'
|
|
||||||
DB_POSTGRES_USER: 'npm'
|
|
||||||
DB_POSTGRES_PASSWORD: 'npmpass'
|
|
||||||
DB_POSTGRES_NAME: 'npm'
|
|
||||||
depends_on:
|
|
||||||
- db-postgres
|
|
||||||
- authentik
|
|
||||||
- authentik-worker
|
|
||||||
- authentik-ldap
|
|
||||||
|
|
||||||
db-postgres:
|
|
||||||
image: postgres:latest
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: 'npm'
|
|
||||||
POSTGRES_PASSWORD: 'npmpass'
|
|
||||||
POSTGRES_DB: 'npm'
|
|
||||||
volumes:
|
|
||||||
- psql_vol:/var/lib/postgresql/data
|
|
||||||
- ./ci/postgres:/docker-entrypoint-initdb.d
|
|
||||||
networks:
|
|
||||||
- fulltest
|
|
||||||
|
|
||||||
authentik-redis:
|
|
||||||
image: 'redis:alpine'
|
|
||||||
command: --save 60 1 --loglevel warning
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
|
|
||||||
start_period: 20s
|
|
||||||
interval: 30s
|
|
||||||
retries: 5
|
|
||||||
timeout: 3s
|
|
||||||
volumes:
|
|
||||||
- redis_vol:/data
|
|
||||||
|
|
||||||
authentik:
|
|
||||||
image: ghcr.io/goauthentik/server:2024.10.1
|
|
||||||
restart: unless-stopped
|
|
||||||
command: server
|
|
||||||
env_file:
|
|
||||||
- ci.env
|
|
||||||
depends_on:
|
|
||||||
- authentik-redis
|
|
||||||
- db-postgres
|
|
||||||
|
|
||||||
authentik-worker:
|
|
||||||
image: ghcr.io/goauthentik/server:2024.10.1
|
|
||||||
restart: unless-stopped
|
|
||||||
command: worker
|
|
||||||
env_file:
|
|
||||||
- ci.env
|
|
||||||
depends_on:
|
|
||||||
- authentik-redis
|
|
||||||
- db-postgres
|
|
||||||
|
|
||||||
authentik-ldap:
|
|
||||||
image: ghcr.io/goauthentik/ldap:2024.10.1
|
|
||||||
environment:
|
|
||||||
AUTHENTIK_HOST: 'http://authentik:9000'
|
|
||||||
AUTHENTIK_INSECURE: 'true'
|
|
||||||
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp'
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- authentik
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
psql_vol:
|
|
||||||
redis_vol:
|
|
@ -2,8 +2,8 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
fullstack:
|
fullstack:
|
||||||
image: npm2dev:core
|
image: nginxproxymanager:dev
|
||||||
container_name: npm2dev.core
|
container_name: npm_core
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./dev/Dockerfile
|
dockerfile: ./dev/Dockerfile
|
||||||
@ -26,17 +26,11 @@ services:
|
|||||||
DEVELOPMENT: 'true'
|
DEVELOPMENT: 'true'
|
||||||
LE_STAGING: 'true'
|
LE_STAGING: 'true'
|
||||||
# db:
|
# db:
|
||||||
# DB_MYSQL_HOST: 'db'
|
DB_MYSQL_HOST: 'db'
|
||||||
# DB_MYSQL_PORT: '3306'
|
DB_MYSQL_PORT: '3306'
|
||||||
# DB_MYSQL_USER: 'npm'
|
DB_MYSQL_USER: 'npm'
|
||||||
# DB_MYSQL_PASSWORD: 'npm'
|
DB_MYSQL_PASSWORD: 'npm'
|
||||||
# DB_MYSQL_NAME: 'npm'
|
DB_MYSQL_NAME: 'npm'
|
||||||
# db-postgres:
|
|
||||||
DB_POSTGRES_HOST: 'db-postgres'
|
|
||||||
DB_POSTGRES_PORT: '5432'
|
|
||||||
DB_POSTGRES_USER: 'npm'
|
|
||||||
DB_POSTGRES_PASSWORD: 'npmpass'
|
|
||||||
DB_POSTGRES_NAME: 'npm'
|
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
# DISABLE_IPV6: "true"
|
# DISABLE_IPV6: "true"
|
||||||
# Required for DNS Certificate provisioning testing:
|
# Required for DNS Certificate provisioning testing:
|
||||||
@ -55,15 +49,11 @@ services:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
- db-postgres
|
|
||||||
- authentik
|
|
||||||
- authentik-worker
|
|
||||||
- authentik-ldap
|
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
container_name: npm2dev.db
|
container_name: npm_db
|
||||||
ports:
|
ports:
|
||||||
- 33306:3306
|
- 33306:3306
|
||||||
networks:
|
networks:
|
||||||
@ -76,22 +66,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
db-postgres:
|
|
||||||
image: postgres:latest
|
|
||||||
container_name: npm2dev.db-postgres
|
|
||||||
networks:
|
|
||||||
- nginx_proxy_manager
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: 'npm'
|
|
||||||
POSTGRES_PASSWORD: 'npmpass'
|
|
||||||
POSTGRES_DB: 'npm'
|
|
||||||
volumes:
|
|
||||||
- psql_data:/var/lib/postgresql/data
|
|
||||||
- ./ci/postgres:/docker-entrypoint-initdb.d
|
|
||||||
|
|
||||||
stepca:
|
stepca:
|
||||||
image: jc21/testca
|
image: jc21/testca
|
||||||
container_name: npm2dev.stepca
|
|
||||||
volumes:
|
volumes:
|
||||||
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
@ -102,7 +78,6 @@ services:
|
|||||||
|
|
||||||
dnsrouter:
|
dnsrouter:
|
||||||
image: jc21/dnsrouter
|
image: jc21/dnsrouter
|
||||||
container_name: npm2dev.dnsrouter
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
|
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
|
||||||
networks:
|
networks:
|
||||||
@ -110,7 +85,7 @@ services:
|
|||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
image: swaggerapi/swagger-ui:latest
|
image: swaggerapi/swagger-ui:latest
|
||||||
container_name: npm2dev.swagger
|
container_name: npm_swagger
|
||||||
ports:
|
ports:
|
||||||
- 3082:80
|
- 3082:80
|
||||||
environment:
|
environment:
|
||||||
@ -121,7 +96,7 @@ services:
|
|||||||
|
|
||||||
squid:
|
squid:
|
||||||
image: ubuntu/squid
|
image: ubuntu/squid
|
||||||
container_name: npm2dev.squid
|
container_name: npm_squid
|
||||||
volumes:
|
volumes:
|
||||||
- './dev/squid.conf:/etc/squid/squid.conf:ro'
|
- './dev/squid.conf:/etc/squid/squid.conf:ro'
|
||||||
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
@ -133,7 +108,6 @@ services:
|
|||||||
|
|
||||||
pdns:
|
pdns:
|
||||||
image: pschiffe/pdns-mysql
|
image: pschiffe/pdns-mysql
|
||||||
container_name: npm2dev.pdns
|
|
||||||
volumes:
|
volumes:
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
environment:
|
environment:
|
||||||
@ -162,7 +136,6 @@ services:
|
|||||||
|
|
||||||
pdns-db:
|
pdns-db:
|
||||||
image: mariadb
|
image: mariadb
|
||||||
container_name: npm2dev.pdns-db
|
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: 'pdns'
|
MYSQL_ROOT_PASSWORD: 'pdns'
|
||||||
MYSQL_DATABASE: 'pdns'
|
MYSQL_DATABASE: 'pdns'
|
||||||
@ -176,8 +149,7 @@ services:
|
|||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
|
|
||||||
cypress:
|
cypress:
|
||||||
image: npm2dev:cypress
|
image: "npm_dev_cypress"
|
||||||
container_name: npm2dev.cypress
|
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
dockerfile: test/cypress/Dockerfile
|
dockerfile: test/cypress/Dockerfile
|
||||||
@ -192,77 +164,16 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
|
|
||||||
authentik-redis:
|
|
||||||
image: 'redis:alpine'
|
|
||||||
container_name: npm2dev.authentik-redis
|
|
||||||
command: --save 60 1 --loglevel warning
|
|
||||||
networks:
|
|
||||||
- nginx_proxy_manager
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
|
|
||||||
start_period: 20s
|
|
||||||
interval: 30s
|
|
||||||
retries: 5
|
|
||||||
timeout: 3s
|
|
||||||
volumes:
|
|
||||||
- redis_data:/data
|
|
||||||
|
|
||||||
authentik:
|
|
||||||
image: ghcr.io/goauthentik/server:2024.10.1
|
|
||||||
container_name: npm2dev.authentik
|
|
||||||
restart: unless-stopped
|
|
||||||
command: server
|
|
||||||
networks:
|
|
||||||
- nginx_proxy_manager
|
|
||||||
env_file:
|
|
||||||
- ci.env
|
|
||||||
ports:
|
|
||||||
- 9000:9000
|
|
||||||
depends_on:
|
|
||||||
- authentik-redis
|
|
||||||
- db-postgres
|
|
||||||
|
|
||||||
authentik-worker:
|
|
||||||
image: ghcr.io/goauthentik/server:2024.10.1
|
|
||||||
container_name: npm2dev.authentik-worker
|
|
||||||
restart: unless-stopped
|
|
||||||
command: worker
|
|
||||||
networks:
|
|
||||||
- nginx_proxy_manager
|
|
||||||
env_file:
|
|
||||||
- ci.env
|
|
||||||
depends_on:
|
|
||||||
- authentik-redis
|
|
||||||
- db-postgres
|
|
||||||
|
|
||||||
authentik-ldap:
|
|
||||||
image: ghcr.io/goauthentik/ldap:2024.10.1
|
|
||||||
container_name: npm2dev.authentik-ldap
|
|
||||||
networks:
|
|
||||||
- nginx_proxy_manager
|
|
||||||
environment:
|
|
||||||
AUTHENTIK_HOST: 'http://authentik:9000'
|
|
||||||
AUTHENTIK_INSECURE: 'true'
|
|
||||||
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp'
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- authentik
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
npm_data:
|
npm_data:
|
||||||
name: npm2dev_core_data
|
name: npm_core_data
|
||||||
le_data:
|
le_data:
|
||||||
name: npm2dev_le_data
|
name: npm_le_data
|
||||||
db_data:
|
db_data:
|
||||||
name: npm2dev_db_data
|
name: npm_db_data
|
||||||
pdns_mysql:
|
pdns_mysql:
|
||||||
name: npnpm2dev_pdns_mysql
|
name: npm_pdns_mysql
|
||||||
psql_data:
|
|
||||||
name: npm2dev_psql_data
|
|
||||||
redis_data:
|
|
||||||
name: npm2dev_redis_data
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
nginx_proxy_manager:
|
nginx_proxy_manager:
|
||||||
name: npm2dev_network
|
name: npm_network
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ {
|
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
|
||||||
if_modified_since off;
|
if_modified_since off;
|
||||||
|
|
||||||
# use the public cache
|
# use the public cache
|
||||||
|
@ -50,6 +50,7 @@ networks:
|
|||||||
Let's look at a Portainer example:
|
Let's look at a Portainer example:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
portainer:
|
portainer:
|
||||||
@ -91,6 +92,8 @@ This image supports the use of Docker secrets to import from files and keep sens
|
|||||||
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
|
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
# Secrets are single-line text files where the sole content is the secret
|
# Secrets are single-line text files where the sole content is the secret
|
||||||
# Paths in this example assume that secrets are kept in local folder called ".secrets"
|
# Paths in this example assume that secrets are kept in local folder called ".secrets"
|
||||||
@ -181,7 +184,6 @@ You can add your custom configuration snippet files at `/data/nginx/custom` as f
|
|||||||
- `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
|
- `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
|
||||||
- `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
|
- `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
|
||||||
- `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
|
- `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
|
||||||
- `/data/nginx/custom/server_dead.conf`: Included at the end of every 404 server block
|
|
||||||
|
|
||||||
Every file is optional.
|
Every file is optional.
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ outline: deep
|
|||||||
Create a `docker-compose.yml` file:
|
Create a `docker-compose.yml` file:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
@ -21,7 +22,8 @@ services:
|
|||||||
# Add any other Stream port you want to expose
|
# Add any other Stream port you want to expose
|
||||||
# - '21:21' # FTP
|
# - '21:21' # FTP
|
||||||
|
|
||||||
environment:
|
# Uncomment the next line if you uncomment anything in the section
|
||||||
|
# environment:
|
||||||
# Uncomment this if you want to change the location of
|
# Uncomment this if you want to change the location of
|
||||||
# the SQLite DB file within the container
|
# the SQLite DB file within the container
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
@ -53,6 +55,7 @@ are going to use.
|
|||||||
Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container:
|
Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
@ -98,53 +101,6 @@ Please note, that `DB_MYSQL_*` environment variables will take precedent over `D
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Using Postgres database
|
|
||||||
|
|
||||||
Similar to the MySQL server setup:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
# These ports are in format <host-port>:<container-port>
|
|
||||||
- '80:80' # Public HTTP Port
|
|
||||||
- '443:443' # Public HTTPS Port
|
|
||||||
- '81:81' # Admin Web Port
|
|
||||||
# Add any other Stream port you want to expose
|
|
||||||
# - '21:21' # FTP
|
|
||||||
environment:
|
|
||||||
# Postgres parameters:
|
|
||||||
DB_POSTGRES_HOST: 'db'
|
|
||||||
DB_POSTGRES_PORT: '5432'
|
|
||||||
DB_POSTGRES_USER: 'npm'
|
|
||||||
DB_POSTGRES_PASSWORD: 'npmpass'
|
|
||||||
DB_POSTGRES_NAME: 'npm'
|
|
||||||
# Uncomment this if IPv6 is not enabled on your host
|
|
||||||
# DISABLE_IPV6: 'true'
|
|
||||||
volumes:
|
|
||||||
- ./data:/data
|
|
||||||
- ./letsencrypt:/etc/letsencrypt
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: postgres:latest
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: 'npm'
|
|
||||||
POSTGRES_PASSWORD: 'npmpass'
|
|
||||||
POSTGRES_DB: 'npm'
|
|
||||||
volumes:
|
|
||||||
- ./postgres:/var/lib/postgresql/data
|
|
||||||
```
|
|
||||||
|
|
||||||
::: warning
|
|
||||||
|
|
||||||
Custom Postgres schema is not supported, as such `public` will be used.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Running on Raspberry PI / ARM devices
|
## Running on Raspberry PI / ARM devices
|
||||||
|
|
||||||
The docker images support the following architectures:
|
The docker images support the following architectures:
|
||||||
|
1
docs/src/third-party/index.md
vendored
1
docs/src/third-party/index.md
vendored
@ -12,7 +12,6 @@ Known integrations:
|
|||||||
- [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager)
|
- [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager)
|
||||||
- [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager)
|
- [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager)
|
||||||
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager)
|
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager)
|
||||||
- [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/scripts?id=nginxproxymanager)
|
|
||||||
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)
|
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)
|
||||||
|
|
||||||
|
|
||||||
|
@ -873,9 +873,9 @@ mitt@^3.0.1:
|
|||||||
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
|
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
|
||||||
|
|
||||||
nanoid@^3.3.7:
|
nanoid@^3.3.7:
|
||||||
version "3.3.8"
|
version "3.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||||
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||||
|
|
||||||
oniguruma-to-js@0.4.3:
|
oniguruma-to-js@0.4.3:
|
||||||
version "0.4.3"
|
version "0.4.3"
|
||||||
|
@ -59,6 +59,11 @@ function fetch(verb, path, data, options) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeSend: function (xhr) {
|
beforeSend: function (xhr) {
|
||||||
|
// Allow unauthenticated access to get the oidc configuration
|
||||||
|
if (path === 'settings/oidc-config' && verb === "get") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
|
xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -434,6 +434,11 @@ module.exports = {
|
|||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (model.get('id') === 'oidc-config') {
|
||||||
|
require(['./main', './settings/oidc-config/main'], function (App, View) {
|
||||||
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
||||||
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
||||||
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
||||||
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
||||||
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
||||||
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
||||||
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,7 +1,19 @@
|
|||||||
<td>
|
<td>
|
||||||
<div><%- i18n('settings', 'default-site') %></div>
|
<div>
|
||||||
|
<% if (id === 'default-site') { %>
|
||||||
|
<%- i18n('settings', 'default-site') %>
|
||||||
|
<% } %>
|
||||||
|
<% if (id === 'oidc-config') { %>
|
||||||
|
<%- i18n('settings', 'oidc-config') %>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
<div class="small text-muted">
|
<div class="small text-muted">
|
||||||
|
<% if (id === 'default-site') { %>
|
||||||
<%- i18n('settings', 'default-site-description') %>
|
<%- i18n('settings', 'default-site-description') %>
|
||||||
|
<% } %>
|
||||||
|
<% if (id === 'oidc-config') { %>
|
||||||
|
<%- i18n('settings', 'oidc-config-description') %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -9,6 +21,14 @@
|
|||||||
<% if (id === 'default-site') { %>
|
<% if (id === 'default-site') { %>
|
||||||
<%- i18n('settings', 'default-site-' + value) %>
|
<%- i18n('settings', 'default-site-' + value) %>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% if (id === 'oidc-config' && meta && meta.name && meta.clientID && meta.clientSecret && meta.issuerURL && meta.redirectURL) { %>
|
||||||
|
<%- meta.name %>
|
||||||
|
<% if (!meta.enabled) { %>
|
||||||
|
(<%- i18n('str', 'disabled') %>)
|
||||||
|
<% } %>
|
||||||
|
<% } else if (id === 'oidc-config') { %>
|
||||||
|
<%- i18n('settings', 'oidc-not-configured') %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
56
frontend/js/app/settings/oidc-config/main.ejs
Normal file
56
frontend/js/app/settings/oidc-config/main.ejs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><%- i18n('settings', id) %></h5>
|
||||||
|
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-label"><%- description %></div>
|
||||||
|
<div>
|
||||||
|
<p><%- i18n('settings', 'oidc-config-hint-1') %></p>
|
||||||
|
<p><%- i18n('settings', 'oidc-config-hint-2') %></p>
|
||||||
|
</div>
|
||||||
|
<div class="custom-controls-stacked">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span>
|
||||||
|
<input class="form-control name-input" name="meta[name]" required type="text" value="<%- meta && typeof meta.name !== 'undefined' ? meta.name : '' %>">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Client ID <span class="form-required">*</span>
|
||||||
|
<input class="form-control id-input" name="meta[clientID]" required type="text" value="<%- meta && typeof meta.clientID !== 'undefined' ? meta.clientID : '' %>">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Client Secret <span class="form-required">*</span>
|
||||||
|
<input class="form-control secret-input" name="meta[clientSecret]" required type="text" value="<%- meta && typeof meta.clientSecret !== 'undefined' ? meta.clientSecret : '' %>">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Issuer URL <span class="form-required">*</span>
|
||||||
|
<input class="form-control issuer-input" name="meta[issuerURL]" required placeholder="https://" type="url" value="<%- meta && typeof meta.issuerURL !== 'undefined' ? meta.issuerURL : '' %>">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Redirect URL <span class="form-required">*</span>
|
||||||
|
<input class="form-control redirect-url-input" name="meta[redirectURL]" required placeholder="https://" type="url" value="<%- meta && typeof meta.redirectURL !== 'undefined' ? meta.redirectURL : document.location.origin + '/api/oidc/callback' %>">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-label"><%- i18n('str', 'enable') %></div>
|
||||||
|
<input class="form-check enabled-input" name="meta[enabled]" placeholder="" type="checkbox" <%- meta && (typeof meta.enabled !== 'undefined' && meta.enabled === true) || (JSON.stringify(meta) === '{}') ? 'checked="checked"' : '' %> >
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
||||||
|
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
46
frontend/js/app/settings/oidc-config/main.js
Normal file
46
frontend/js/app/settings/oidc-config/main.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const Mn = require('backbone.marionette');
|
||||||
|
const App = require('../../main');
|
||||||
|
const template = require('./main.ejs');
|
||||||
|
|
||||||
|
require('jquery-serializejson');
|
||||||
|
|
||||||
|
module.exports = Mn.View.extend({
|
||||||
|
template: template,
|
||||||
|
className: 'modal-dialog wide',
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
form: 'form',
|
||||||
|
buttons: '.modal-footer button',
|
||||||
|
cancel: 'button.cancel',
|
||||||
|
save: 'button.save',
|
||||||
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click @ui.save': function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!this.ui.form[0].checkValidity()) {
|
||||||
|
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let view = this;
|
||||||
|
let data = this.ui.form.serializeJSON();
|
||||||
|
data.id = this.model.get('id');
|
||||||
|
if (data.meta.enabled) {
|
||||||
|
data.meta.enabled = data.meta.enabled === 'on' || data.meta.enabled === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||||
|
App.Api.Settings.update(data)
|
||||||
|
.then((result) => {
|
||||||
|
view.model.set(result);
|
||||||
|
App.UI.closeModal();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert(err.message);
|
||||||
|
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -1,10 +1,10 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form>
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5>
|
<h5 class="modal-title"><%- i18n('users', '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">
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 col-md-6">
|
<div class="col-sm-6 col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -49,10 +49,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
||||||
<button type="submit" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@ module.exports = Mn.View.extend({
|
|||||||
|
|
||||||
events: {
|
events: {
|
||||||
|
|
||||||
'submit @ui.form': function (e) {
|
'click @ui.save': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.ui.error.hide();
|
this.ui.error.hide();
|
||||||
let view = this;
|
let view = this;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"sign-in": "Sign in",
|
"sign-in": "Sign in",
|
||||||
|
"sign-in-with": "Sign in with",
|
||||||
"sign-out": "Sign out",
|
"sign-out": "Sign out",
|
||||||
"try-again": "Try again",
|
"try-again": "Try again",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -290,7 +291,12 @@
|
|||||||
"default-site-404": "404 Page",
|
"default-site-404": "404 Page",
|
||||||
"default-site-444": "No Response (444)",
|
"default-site-444": "No Response (444)",
|
||||||
"default-site-html": "Custom Page",
|
"default-site-html": "Custom Page",
|
||||||
"default-site-redirect": "Redirect"
|
"default-site-redirect": "Redirect",
|
||||||
|
"oidc-config": "Open ID Conncect Configuration",
|
||||||
|
"oidc-config-description": "Sign in to Nginx Proxy Manager with an external Identity Provider",
|
||||||
|
"oidc-not-configured": "Not configured",
|
||||||
|
"oidc-config-hint-1": "Provide configuration for an IdP that supports Open ID Connect Discovery.",
|
||||||
|
"oidc-config-hint-2": "The 'RedirectURL' must be set to '[base URL]/api/oidc/callback', the IdP must send the 'email' claim and a user with matching email address must exist in Nginx Proxy Manager."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-6">
|
<div class="col-sm-12 col-md-6 margin-auto">
|
||||||
<div class="text-center p-6">
|
<div class="text-center p-6">
|
||||||
<img src="/images/logo-text-vertical-grey.png" alt="Logo" />
|
<img src="/images/logo-text-vertical-grey.png" alt="Logo" />
|
||||||
<div class="text-center text-muted mt-5">
|
<div class="text-center text-muted mt-5">
|
||||||
@ -27,6 +27,13 @@
|
|||||||
<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>
|
||||||
|
<div class="form-footer login-oidc">
|
||||||
|
<div class="separator"><slot>OR</slot></div>
|
||||||
|
<button type="button" id="login-oidc" class="btn btn-teal btn-block">
|
||||||
|
<%- i18n('str', 'sign-in-with') %> <span class="oidc-provider"></span>
|
||||||
|
</button>
|
||||||
|
<div class="invalid-feedback oidc-error"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ const Mn = require('backbone.marionette');
|
|||||||
const template = require('./login.ejs');
|
const template = require('./login.ejs');
|
||||||
const Api = require('../../app/api');
|
const Api = require('../../app/api');
|
||||||
const i18n = require('../../app/i18n');
|
const i18n = require('../../app/i18n');
|
||||||
|
const Tokens = require('../../app/tokens');
|
||||||
|
|
||||||
module.exports = Mn.View.extend({
|
module.exports = Mn.View.extend({
|
||||||
template: template,
|
template: template,
|
||||||
@ -13,7 +14,11 @@ 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',
|
||||||
button: 'button'
|
button: 'button[type=submit]',
|
||||||
|
oidcLogin: 'div.login-oidc',
|
||||||
|
oidcButton: 'button#login-oidc',
|
||||||
|
oidcError: '.oidc-error',
|
||||||
|
oidcProvider: 'span.oidc-provider'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@ -26,10 +31,56 @@ module.exports = Mn.View.extend({
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
window.location = '/';
|
window.location = '/';
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
this.ui.error.text(err.message).show();
|
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);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
'click @ui.oidcButton': function() {
|
||||||
|
this.ui.identity.prop('disabled', true);
|
||||||
|
this.ui.secret.prop('disabled', true);
|
||||||
|
this.ui.button.prop('disabled', true);
|
||||||
|
this.ui.oidcButton.addClass('btn-loading').prop('disabled', true);
|
||||||
|
// redirect to initiate oauth flow
|
||||||
|
document.location.replace('/api/oidc/');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async onRender() {
|
||||||
|
// read oauth callback state cookies
|
||||||
|
let cookies = document.cookie.split(';'),
|
||||||
|
token, expiry, error;
|
||||||
|
for (cookie of cookies) {
|
||||||
|
let raw = cookie.split('='),
|
||||||
|
name = raw[0].trim(),
|
||||||
|
value = raw[1];
|
||||||
|
if (name === 'npm_oidc') {
|
||||||
|
let v = value.split('---');
|
||||||
|
token = v[0];
|
||||||
|
expiry = v[1];
|
||||||
|
}
|
||||||
|
if (name === 'npm_oidc_error') {
|
||||||
|
error = decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register a newly acquired jwt token following successful oidc authentication
|
||||||
|
if (token && expiry && (new Date(Date.parse(decodeURIComponent(expiry)))) > new Date() ) {
|
||||||
|
Tokens.addToken(token);
|
||||||
|
document.location.replace('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// show error message following a failed oidc authentication
|
||||||
|
if (error) {
|
||||||
|
this.ui.oidcError.html(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch oidc configuration and show alternative action button if enabled
|
||||||
|
let response = await Api.Settings.getById('oidc-config');
|
||||||
|
if (response && response.meta && response.meta.enabled === true) {
|
||||||
|
this.ui.oidcProvider.html(response.meta.name);
|
||||||
|
this.ui.oidcLogin.show();
|
||||||
|
this.ui.oidcError.show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,3 +40,33 @@ a:hover {
|
|||||||
.col-login {
|
.col-login {
|
||||||
max-width: 48rem;
|
max-width: 48rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-auto {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator::before, .separator::after {
|
||||||
|
content: "";
|
||||||
|
flex: 1 1 0%;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator:not(:empty)::before {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator:not(:empty)::after {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-oidc {
|
||||||
|
display: none;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
@ -2648,9 +2648,9 @@ electron-to-chromium@^1.3.47:
|
|||||||
integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q==
|
integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q==
|
||||||
|
|
||||||
elliptic@^6.5.3, elliptic@^6.5.4:
|
elliptic@^6.5.3, elliptic@^6.5.4:
|
||||||
version "6.6.0"
|
version "6.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210"
|
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
|
||||||
integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==
|
integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
bn.js "^4.11.9"
|
bn.js "^4.11.9"
|
||||||
brorand "^1.1.0"
|
brorand "^1.1.0"
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"aliyun": {
|
"aliyun": {
|
||||||
"name": "Aliyun",
|
"name": "Aliyun",
|
||||||
"package_name": "certbot-dns-aliyun",
|
"package_name": "certbot-dns-aliyun",
|
||||||
"version": "~=2.0.0",
|
"version": "~=0.38.1",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef",
|
"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef",
|
||||||
"full_plugin_name": "dns-aliyun"
|
"full_plugin_name": "dns-aliyun"
|
||||||
@ -31,14 +31,6 @@
|
|||||||
"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2",
|
"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2",
|
||||||
"full_plugin_name": "dns-azure"
|
"full_plugin_name": "dns-azure"
|
||||||
},
|
},
|
||||||
"beget": {
|
|
||||||
"name":"Beget",
|
|
||||||
"package_name": "certbot-beget-plugin",
|
|
||||||
"version": "~=1.0.0.dev9",
|
|
||||||
"dependencies": "",
|
|
||||||
"credentials": "# Beget API credentials used by Certbot\nbeget_plugin_username = username\nbeget_plugin_password = password",
|
|
||||||
"full_plugin_name": "beget-plugin"
|
|
||||||
},
|
|
||||||
"bunny": {
|
"bunny": {
|
||||||
"name": "bunny.net",
|
"name": "bunny.net",
|
||||||
"package_name": "certbot-dns-bunny",
|
"package_name": "certbot-dns-bunny",
|
||||||
@ -255,14 +247,6 @@
|
|||||||
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
|
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
|
||||||
"full_plugin_name": "dns-hetzner"
|
"full_plugin_name": "dns-hetzner"
|
||||||
},
|
},
|
||||||
"hostingnl": {
|
|
||||||
"name": "Hosting.nl",
|
|
||||||
"package_name": "certbot-dns-hostingnl",
|
|
||||||
"version": "~=0.1.5",
|
|
||||||
"dependencies": "",
|
|
||||||
"credentials": "dns_hostingnl_api_key = 0123456789abcdef0123456789abcdef",
|
|
||||||
"full_plugin_name": "dns-hostingnl"
|
|
||||||
},
|
|
||||||
"hover": {
|
"hover": {
|
||||||
"name": "Hover",
|
"name": "Hover",
|
||||||
"package_name": "certbot-dns-hover",
|
"package_name": "certbot-dns-hover",
|
||||||
@ -418,7 +402,7 @@
|
|||||||
"porkbun": {
|
"porkbun": {
|
||||||
"name": "Porkbun",
|
"name": "Porkbun",
|
||||||
"package_name": "certbot-dns-porkbun",
|
"package_name": "certbot-dns-porkbun",
|
||||||
"version": "~=0.9",
|
"version": "~=0.2",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret",
|
"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret",
|
||||||
"full_plugin_name": "dns-porkbun"
|
"full_plugin_name": "dns-porkbun"
|
||||||
|
@ -11,7 +11,7 @@ YELLOW='\E[1;33m'
|
|||||||
export BLUE CYAN GREEN RED RESET YELLOW
|
export BLUE CYAN GREEN RED RESET YELLOW
|
||||||
|
|
||||||
# Docker Compose
|
# Docker Compose
|
||||||
COMPOSE_PROJECT_NAME="npm2dev"
|
COMPOSE_PROJECT_NAME="npmdev"
|
||||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||||
|
|
||||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
||||||
|
@ -67,8 +67,6 @@ printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}"
|
|||||||
# bring up all remaining containers, except cypress!
|
# bring up all remaining containers, except cypress!
|
||||||
docker-compose up -d --remove-orphans stepca squid
|
docker-compose up -d --remove-orphans stepca squid
|
||||||
docker-compose pull db-mysql || true # ok to fail
|
docker-compose pull db-mysql || true # ok to fail
|
||||||
docker-compose pull db-postgres || true # ok to fail
|
|
||||||
docker-compose pull authentik authentik-redis authentik-ldap || true # ok to fail
|
|
||||||
docker-compose up -d --remove-orphans --pull=never fullstack
|
docker-compose up -d --remove-orphans --pull=never fullstack
|
||||||
|
|
||||||
# wait for main container to be healthy
|
# wait for main container to be healthy
|
||||||
|
@ -36,11 +36,12 @@ if hash docker-compose 2>/dev/null; then
|
|||||||
|
|
||||||
# bring up all remaining containers, except cypress!
|
# bring up all remaining containers, except cypress!
|
||||||
docker-compose up -d --remove-orphans stepca squid
|
docker-compose up -d --remove-orphans stepca squid
|
||||||
docker-compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap
|
docker-compose pull db
|
||||||
docker-compose build --pull --parallel fullstack
|
docker-compose up -d --remove-orphans --pull=never fullstack
|
||||||
docker-compose up -d --remove-orphans fullstack
|
|
||||||
docker-compose up -d --remove-orphans swagger
|
docker-compose up -d --remove-orphans swagger
|
||||||
|
|
||||||
|
# docker-compose up -d --remove-orphans --force-recreate --build
|
||||||
|
|
||||||
# wait for main container to be healthy
|
# wait for main container to be healthy
|
||||||
bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
|
bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
|
||||||
|
|
||||||
@ -52,10 +53,10 @@ if hash docker-compose 2>/dev/null; then
|
|||||||
|
|
||||||
if [ "$1" == "-f" ]; then
|
if [ "$1" == "-f" ]; then
|
||||||
echo -e "${BLUE}❯ ${YELLOW}Following Backend Container:${RESET}"
|
echo -e "${BLUE}❯ ${YELLOW}Following Backend Container:${RESET}"
|
||||||
docker logs -f npm2dev.core
|
docker logs -f npm_core
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:"
|
echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:"
|
||||||
echo " docker logs -f npm2dev.core"
|
echo " docker logs -f npm_core"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${RED}❯ docker-compose command is not available${RESET}"
|
echo -e "${RED}❯ docker-compose command is not available${RESET}"
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
describe('LDAP with Authentik', () => {
|
|
||||||
let token;
|
|
||||||
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.getToken().then((tok) => {
|
|
||||||
token = tok;
|
|
||||||
|
|
||||||
// cy.task('backendApiPut', {
|
|
||||||
// token: token,
|
|
||||||
// path: '/api/settings/ldap-auth',
|
|
||||||
// data: {
|
|
||||||
// value: {
|
|
||||||
// host: 'authentik-ldap:3389',
|
|
||||||
// base_dn: 'ou=users,DC=ldap,DC=goauthentik,DC=io',
|
|
||||||
// user_dn: 'cn={{USERNAME}},ou=users,DC=ldap,DC=goauthentik,DC=io',
|
|
||||||
// email_property: 'mail',
|
|
||||||
// name_property: 'sn',
|
|
||||||
// self_filter: '(&(cn={{USERNAME}})(ak-active=TRUE))',
|
|
||||||
// auto_create_user: true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }).then((data) => {
|
|
||||||
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
|
||||||
// expect(data.result).to.have.property('id');
|
|
||||||
// expect(data.result.id).to.be.greaterThan(0);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// cy.task('backendApiPut', {
|
|
||||||
// token: token,
|
|
||||||
// path: '/api/settings/auth-methods',
|
|
||||||
// data: {
|
|
||||||
// value: [
|
|
||||||
// 'local',
|
|
||||||
// 'ldap'
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }).then((data) => {
|
|
||||||
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
|
||||||
// expect(data.result).to.have.property('id');
|
|
||||||
// expect(data.result.id).to.be.greaterThan(0);
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('Should log in with LDAP', function() {
|
|
||||||
// cy.task('backendApiPost', {
|
|
||||||
// token: token,
|
|
||||||
// path: '/api/auth',
|
|
||||||
// data: {
|
|
||||||
// // Authentik LDAP creds:
|
|
||||||
// type: 'ldap',
|
|
||||||
// identity: 'cypress',
|
|
||||||
// secret: 'fqXBfUYqHvYqiwBHWW7f'
|
|
||||||
// }
|
|
||||||
// }).then((data) => {
|
|
||||||
// cy.validateSwaggerSchema('post', 200, '/auth', data);
|
|
||||||
// expect(data.result).to.have.property('token');
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,97 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
describe('OAuth with Authentik', () => {
|
|
||||||
let token;
|
|
||||||
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.getToken().then((tok) => {
|
|
||||||
token = tok;
|
|
||||||
|
|
||||||
// cy.task('backendApiPut', {
|
|
||||||
// token: token,
|
|
||||||
// path: '/api/settings/oauth-auth',
|
|
||||||
// data: {
|
|
||||||
// value: {
|
|
||||||
// client_id: '7iO2AvuUp9JxiSVkCcjiIbQn4mHmUMBj7yU8EjqU',
|
|
||||||
// client_secret: 'VUMZzaGTrmXJ8PLksyqzyZ6lrtz04VvejFhPMBP9hGZNCMrn2LLBanySs4ta7XGrDr05xexPyZT1XThaf4ubg00WqvHRVvlu4Naa1aMootNmSRx3VAk6RSslUJmGyHzq',
|
|
||||||
// authorization_url: 'http://authentik:9000/application/o/authorize/',
|
|
||||||
// resource_url: 'http://authentik:9000/application/o/userinfo/',
|
|
||||||
// token_url: 'http://authentik:9000/application/o/token/',
|
|
||||||
// logout_url: 'http://authentik:9000/application/o/npm/end-session/',
|
|
||||||
// identifier: 'preferred_username',
|
|
||||||
// scopes: [],
|
|
||||||
// auto_create_user: true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }).then((data) => {
|
|
||||||
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
|
||||||
// expect(data.result).to.have.property('id');
|
|
||||||
// expect(data.result.id).to.be.greaterThan(0);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// cy.task('backendApiPut', {
|
|
||||||
// token: token,
|
|
||||||
// path: '/api/settings/auth-methods',
|
|
||||||
// data: {
|
|
||||||
// value: [
|
|
||||||
// 'local',
|
|
||||||
// 'oauth'
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }).then((data) => {
|
|
||||||
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
|
||||||
// expect(data.result).to.have.property('id');
|
|
||||||
// expect(data.result.id).to.be.greaterThan(0);
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('Should log in with OAuth', function() {
|
|
||||||
// cy.task('backendApiGet', {
|
|
||||||
// path: '/oauth/login?redirect_base=' + encodeURI(Cypress.config('baseUrl')),
|
|
||||||
// }).then((data) => {
|
|
||||||
// expect(data).to.have.property('result');
|
|
||||||
|
|
||||||
// cy.origin('http://authentik:9000', {args: data.result}, (url) => {
|
|
||||||
// cy.visit(url);
|
|
||||||
// cy.get('ak-flow-executor')
|
|
||||||
// .shadow()
|
|
||||||
// .find('ak-stage-identification')
|
|
||||||
// .shadow()
|
|
||||||
// .find('input[name="uidField"]', { visible: true })
|
|
||||||
// .type('cypress');
|
|
||||||
|
|
||||||
// cy.get('ak-flow-executor')
|
|
||||||
// .shadow()
|
|
||||||
// .find('ak-stage-identification')
|
|
||||||
// .shadow()
|
|
||||||
// .find('button[type="submit"]', { visible: true })
|
|
||||||
// .click();
|
|
||||||
|
|
||||||
// cy.get('ak-flow-executor')
|
|
||||||
// .shadow()
|
|
||||||
// .find('ak-stage-password')
|
|
||||||
// .shadow()
|
|
||||||
// .find('input[name="password"]', { visible: true })
|
|
||||||
// .type('fqXBfUYqHvYqiwBHWW7f');
|
|
||||||
|
|
||||||
// cy.get('ak-flow-executor')
|
|
||||||
// .shadow()
|
|
||||||
// .find('ak-stage-password')
|
|
||||||
// .shadow()
|
|
||||||
// .find('button[type="submit"]', { visible: true })
|
|
||||||
// .click();
|
|
||||||
// })
|
|
||||||
|
|
||||||
// // we should be logged in
|
|
||||||
// cy.get('#root p.chakra-text')
|
|
||||||
// .first()
|
|
||||||
// .should('have.text', 'Nginx Proxy Manager');
|
|
||||||
|
|
||||||
// // logout:
|
|
||||||
// cy.clearLocalStorage();
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -132,9 +132,9 @@
|
|||||||
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
|
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
|
||||||
|
|
||||||
"@eslint/plugin-kit@^0.2.0":
|
"@eslint/plugin-kit@^0.2.0":
|
||||||
version "0.2.3"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz#812980a6a41ecf3a8341719f92a6d1e784a2e0e8"
|
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz#8712dccae365d24e9eeecb7b346f85e750ba343d"
|
||||||
integrity sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==
|
integrity sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==
|
||||||
dependencies:
|
dependencies:
|
||||||
levn "^0.4.1"
|
levn "^0.4.1"
|
||||||
|
|
||||||
@ -628,9 +628,9 @@ core-util-is@1.0.2:
|
|||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||||
|
|
||||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||||
version "7.0.6"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.1.0"
|
path-key "^3.1.0"
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
|
Reference in New Issue
Block a user