mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 15:53:33 +00:00 
			
		
		
		
	Compare commits
	
		
			197 Commits
		
	
	
		
			07a4e5791f
			...
			dependabot
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 08bdc23131 | ||
|  | 487fa6d31b | ||
|  | 5b6ca1bf00 | ||
|  | 5039738aa3 | ||
|  | 4451be8f1c | ||
|  | bee2fd1978 | ||
|  | c8adbdfc15 | ||
|  | aff4182ab8 | ||
|  | 8c9d2745e2 | ||
|  | 076d14b5e4 | ||
|  | 8a6d815152 | ||
|  | 54d463ac36 | ||
|  | a23dc24021 | ||
|  | 4f9df893c8 | ||
|  | 304b38e82b | ||
|  | 1b0929ade6 | ||
|  | ddbafb62a6 | ||
|  | 9a0383bc73 | ||
|  | 307cb94e84 | ||
|  | 63ae924fbc | ||
|  | 1710a263c0 | ||
|  | 1357774f21 | ||
|  | 5f54490d86 | ||
|  | c97b8a339d | ||
|  | ed1d90ee7f | ||
|  | 70894e55b8 | ||
|  | 817021a43d | ||
|  | 36e3449a56 | ||
|  | db9f25638f | ||
|  | ddd3355d95 | ||
|  | aade8b42fc | ||
|  | 3735f3c11d | ||
|  | b84762b5b9 | ||
|  | 953faeac15 | ||
|  | c58f3f3ec9 | ||
|  | 0ee4d04d5f | ||
|  | 94f6756250 | ||
|  | 27e3f73854 | ||
|  | d98f4b43dc | ||
|  | ff3116a626 | ||
|  | 7047750b04 | ||
|  | 0792fc0768 | ||
|  | 9758c12ca3 | ||
|  | ccd69c8867 | ||
|  | 23fd1fec6c | ||
|  | 6f04543744 | ||
|  | cbb1fe44ca | ||
|  | 4c23f22d5b | ||
|  | af5d3eccd6 | ||
|  | a87283b030 | ||
|  | 97dbbdd60f | ||
|  | ec81f2489a | ||
|  | d0ec8e89aa | ||
|  | 9a96fbb5f4 | ||
|  | a573450bb8 | ||
|  | 60a25ffbd5 | ||
|  | 7d2369b380 | ||
|  | 64f00e8dba | ||
|  | c99143f548 | ||
|  | cc4ee6919a | ||
|  | 8a69c65b40 | ||
|  | 95ee5ca958 | ||
|  | 40f22d30c4 | ||
|  | 30dfa9e3de | ||
|  | b873499feb | ||
|  | ef69be2036 | ||
|  | 7580e65dd4 | ||
|  | f11dc5d7c1 | ||
|  | 77061a7bd6 | ||
|  | b6afc19135 | ||
|  | 09ba400d09 | ||
|  | 0291cfc270 | ||
|  | 34267e0af9 | ||
|  | f327c1e825 | ||
|  | 6f539979ec | ||
|  | 3d8079a137 | ||
|  | 6d6d83c0d0 | ||
|  | 100a4888d0 | ||
|  | 34a46bd733 | ||
|  | 7f8adc7e50 | ||
|  | 98d118cb74 | ||
|  | 4fb93542c3 | ||
|  | 4fe305520a | ||
|  | 76be31cf76 | ||
|  | 55dadb2004 | ||
|  | d9cdb3dc2c | ||
|  | 0cab720f23 | ||
|  | f5879dff6c | ||
|  | 5e66d677f1 | ||
|  | 18830f81b0 | ||
|  | 341ac65587 | ||
|  | 078baa255a | ||
|  | bf9d9bd43b | ||
|  | a394b25e61 | ||
|  | 1c47fc2ba4 | ||
|  | 312e2ab80c | ||
|  | d147ccd88d | ||
|  | 03fd292c61 | ||
|  | 79d28f03d0 | ||
|  | b09147eca8 | ||
|  | c5a319cb20 | ||
|  | c4df89df1f | ||
|  | 34c703f8b4 | ||
|  | 0a05d8f0ad | ||
|  | 0a9141fad5 | ||
|  | 42836774b7 | ||
|  | 2a07544f58 | ||
|  | dc9d884743 | ||
|  | 0d5d2b1b7c | ||
|  | df48b835c4 | ||
|  | 8a1557154a | ||
|  | a6af5ec2c7 | ||
|  | 14d7c35fd7 | ||
|  | cfcf78aaee | ||
|  | 3a01b2c84f | ||
|  | e1c84a5c10 | ||
|  | c56c95a59a | ||
|  | 6a60627833 | ||
|  | b4793d3c16 | ||
|  | 68a7803513 | ||
|  | 2657af97cf | ||
|  | 4452f014b9 | ||
|  | cd80cc8e4d | ||
|  | ee4250d770 | ||
|  | 3dbc70faa6 | ||
|  | 3091c21cae | ||
|  | 57cd2a1919 | ||
|  | ad5936c530 | ||
|  | 498109addb | ||
|  | 3f3aacd7ec | ||
|  | bb4ecf812d | ||
|  | c05f9695d0 | ||
|  | 6343b398f0 | ||
|  | 59362b7477 | ||
|  | aedaaa18e0 | ||
|  | 080bd0b749 | ||
|  | 9687e9e450 | ||
|  | 5a234bb88c | ||
|  | 4de4b65036 | ||
|  | f1c97c7c36 | ||
|  | b4f49969d6 | ||
|  | ec12d8f9bf | ||
|  | e50e3def9d | ||
|  | 6415f284f9 | ||
|  | 98e5997f0a | ||
|  | fc30a92bd4 | ||
|  | e2011ee45c | ||
|  | 1406e75c2c | ||
|  | ca3ee98c68 | ||
|  | f90d839ebe | ||
|  | be5278f31e | ||
|  | 73110d5e1e | ||
|  | 356b98bf7e | ||
|  | 3eecf7a38b | ||
|  | 7f9240dda7 | ||
|  | f537619ffe | ||
|  | 805968aac6 | ||
|  | 2a4093c1b8 | ||
|  | ae2ac8a733 | ||
|  | 5d087f1256 | ||
|  | c6eca2578e | ||
|  | 56033bee9c | ||
|  | c6630e87bb | ||
|  | d6b98f51b0 | ||
|  | 1e322804ce | ||
|  | b3de76c945 | ||
|  | fcf4117f8e | ||
|  | d26e8c1d0c | ||
|  | 19ed4c1212 | ||
|  | 03018d252b | ||
|  | 8351dd41f6 | ||
|  | 97212f2686 | ||
|  | fe068a8b51 | ||
|  | 61e2bde98f | ||
|  | 81c9038929 | ||
|  | 4ea50ca40c | ||
|  | 53ed12bcf2 | ||
|  | cb3e4ed59c | ||
|  | b20dc5eade | ||
|  | 586afc0c91 | ||
|  | 93ea17a9bb | ||
|  | 151160a834 | ||
|  | 2075f98cad | ||
|  | 126d3d44ca | ||
|  | 20646e7bb5 | ||
|  | 87998a03ce | ||
|  | 2cee211fb0 | ||
|  | a56342c76a | ||
|  | 4c89379671 | ||
|  | 10b9a49274 | ||
|  | 595a742c40 | ||
|  | c171752137 | ||
|  | a0b26b9e98 | ||
|  | d6791f4e38 | ||
|  | 62c94f3099 | ||
|  | 5084cb7296 | ||
|  | e677bfa2e8 | 
							
								
								
									
										57
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										57
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							| @@ -128,7 +128,7 @@ pipeline { | |||||||
| 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | ||||||
| 				} | 				} | ||||||
| 				unstable { | 				unstable { | ||||||
| 					dir(path: 'testing/results') { | 					dir(path: 'test/results') { | ||||||
| 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -161,7 +161,45 @@ pipeline { | |||||||
| 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | ||||||
| 				} | 				} | ||||||
| 				unstable { | 				unstable { | ||||||
| 					dir(path: 'testing/results') { | 					dir(path: 'test/results') { | ||||||
|  | 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		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: 'test/results') { | ||||||
| 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -203,12 +241,17 @@ pipeline { | |||||||
| 					} | 					} | ||||||
| 					steps { | 					steps { | ||||||
| 						script { | 						script { | ||||||
| 							npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on | 							npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev): | ||||||
| [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev) | ``` | ||||||
| as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}` | nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER} | ||||||
|  | ``` | ||||||
|  |  | ||||||
| **Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes | > [!NOTE] | ||||||
| **Note:** this is a different docker image namespace than the official image | > Ensure you backup your NPM instance before testing this image! Especially if there are database changes. | ||||||
|  | > This is a different docker image namespace than the official image. | ||||||
|  |  | ||||||
|  | > [!WARNING] | ||||||
|  | > Changes and additions to DNS Providers require verification by at least 2 members of the community! | ||||||
| """, true) | """, true) | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|   | |||||||
| @@ -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.1-green.svg?style=for-the-badge"> | 	<img src="https://img.shields.io/badge/version-2.12.6-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> | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ | |||||||
| const schema = require('./schema'); | const schema = require('./schema'); | ||||||
| const logger = require('./logger').global; | const logger = require('./logger').global; | ||||||
|  |  | ||||||
|  | const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== 'false'; | ||||||
|  |  | ||||||
| async function appStart () { | async function appStart () { | ||||||
| 	const migrate             = require('./migrate'); | 	const migrate             = require('./migrate'); | ||||||
| 	const setup               = require('./setup'); | 	const setup               = require('./setup'); | ||||||
| @@ -13,7 +15,16 @@ async function appStart () { | |||||||
| 	return migrate.latest() | 	return migrate.latest() | ||||||
| 		.then(setup) | 		.then(setup) | ||||||
| 		.then(schema.getCompiledSchema) | 		.then(schema.getCompiledSchema) | ||||||
| 		.then(internalIpRanges.fetch) | 		.then(() => { | ||||||
|  | 			if (IP_RANGES_FETCH_ENABLED) { | ||||||
|  | 				logger.info('IP Ranges fetch is enabled'); | ||||||
|  | 				return internalIpRanges.fetch().catch((err) => { | ||||||
|  | 					logger.error('IP Ranges fetch failed, continuing anyway:', err.message); | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				logger.info('IP Ranges fetch is disabled by environment variable'); | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
| 		.then(() => { | 		.then(() => { | ||||||
| 			internalCertificate.initTimer(); | 			internalCertificate.initTimer(); | ||||||
| 			internalIpRanges.initTimer(); | 			internalIpRanges.initTimer(); | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| const _                     = require('lodash'); | const _                     = require('lodash'); | ||||||
| const fs                    = require('fs'); | const fs                    = require('node:fs'); | ||||||
| const batchflow             = require('batchflow'); | const batchflow             = require('batchflow'); | ||||||
| const logger                = require('../logger').access; | const logger                = require('../logger').access; | ||||||
| const error                 = require('../lib/error'); | const error                 = require('../lib/error'); | ||||||
| @@ -38,7 +38,7 @@ const internalAccessList = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				data.id = row.id; | 				data.id = row.id; | ||||||
|  |  | ||||||
| 				let promises = []; | 				const promises = []; | ||||||
|  |  | ||||||
| 				// Now add the items | 				// Now add the items | ||||||
| 				data.items.map((item) => { | 				data.items.map((item) => { | ||||||
| @@ -81,7 +81,7 @@ const internalAccessList = { | |||||||
|  |  | ||||||
| 				return internalAccessList.build(row) | 				return internalAccessList.build(row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						if (row.proxy_host_count) { | 						if (parseInt(row.proxy_host_count, 10)) { | ||||||
| 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | ||||||
| 						} | 						} | ||||||
| 					}) | 					}) | ||||||
| @@ -116,7 +116,7 @@ const internalAccessList = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (row.id !== data.id) { | 				if (row.id !== data.id) { | ||||||
| 					// Sanity check that something crazy hasn't happened | 					// Sanity check that something crazy hasn't happened | ||||||
| 					throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | 					throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`); | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| @@ -135,10 +135,10 @@ const internalAccessList = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				// Check for items and add/update/remove them | 				// Check for items and add/update/remove them | ||||||
| 				if (typeof data.items !== 'undefined' && data.items) { | 				if (typeof data.items !== 'undefined' && data.items) { | ||||||
| 					let promises      = []; | 					const promises      = []; | ||||||
| 					let items_to_keep = []; | 					const items_to_keep = []; | ||||||
|  |  | ||||||
| 					data.items.map(function (item) { | 					data.items.map((item) => { | ||||||
| 						if (item.password) { | 						if (item.password) { | ||||||
| 							promises.push(accessListAuthModel | 							promises.push(accessListAuthModel | ||||||
| 								.query() | 								.query() | ||||||
| @@ -154,7 +154,7 @@ const internalAccessList = { | |||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
|  |  | ||||||
| 					let query = accessListAuthModel | 					const query = accessListAuthModel | ||||||
| 						.query() | 						.query() | ||||||
| 						.delete() | 						.delete() | ||||||
| 						.where('access_list_id', data.id); | 						.where('access_list_id', data.id); | ||||||
| @@ -175,9 +175,9 @@ const internalAccessList = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				// Check for clients and add/update/remove them | 				// Check for clients and add/update/remove them | ||||||
| 				if (typeof data.clients !== 'undefined' && data.clients) { | 				if (typeof data.clients !== 'undefined' && data.clients) { | ||||||
| 					let promises = []; | 					const promises = []; | ||||||
|  |  | ||||||
| 					data.clients.map(function (client) { | 					data.clients.map((client) => { | ||||||
| 						if (client.address) { | 						if (client.address) { | ||||||
| 							promises.push(accessListClientModel | 							promises.push(accessListClientModel | ||||||
| 								.query() | 								.query() | ||||||
| @@ -190,7 +190,7 @@ const internalAccessList = { | |||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
|  |  | ||||||
| 					let query = accessListClientModel | 					const query = accessListClientModel | ||||||
| 						.query() | 						.query() | ||||||
| 						.delete() | 						.delete() | ||||||
| 						.where('access_list_id', data.id); | 						.where('access_list_id', data.id); | ||||||
| @@ -223,7 +223,7 @@ const internalAccessList = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				return internalAccessList.build(row) | 				return internalAccessList.build(row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						if (row.proxy_host_count) { | 						if (parseInt(row.proxy_host_count, 10)) { | ||||||
| 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | ||||||
| 						} | 						} | ||||||
| 					}).then(internalNginx.reload) | 					}).then(internalNginx.reload) | ||||||
| @@ -249,12 +249,16 @@ const internalAccessList = { | |||||||
|  |  | ||||||
| 		return access.can('access_lists:get', data.id) | 		return access.can('access_lists:get', data.id) | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				let query = accessListModel | 				const 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')) | ||||||
| 					.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0') | 					.leftJoin('proxy_host', function() { | ||||||
|  | 						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) | ||||||
|  | 					.groupBy('access_list.id') | ||||||
| 					.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') | 					.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| @@ -263,7 +267,7 @@ const internalAccessList = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||||
| 					query.withGraphFetched('[' + data.expand.join(', ') + ']'); | 					query.withGraphFetched(`[${data.expand.join(', ')}]`); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRow(omissions())); | 				return query.then(utils.omitRow(omissions())); | ||||||
| @@ -323,7 +327,7 @@ const internalAccessList = { | |||||||
| 									// 3. reconfigure those hosts, then reload nginx | 									// 3. reconfigure those hosts, then reload nginx | ||||||
|  |  | ||||||
| 									// set the access_list_id to zero for these items | 									// set the access_list_id to zero for these items | ||||||
| 									row.proxy_hosts.map(function (val, idx) { | 									row.proxy_hosts.map((_val, idx) => { | ||||||
| 										row.proxy_hosts[idx].access_list_id = 0; | 										row.proxy_hosts[idx].access_list_id = 0; | ||||||
| 									}); | 									}); | ||||||
|  |  | ||||||
| @@ -336,11 +340,11 @@ const internalAccessList = { | |||||||
| 					}) | 					}) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						// delete the htpasswd file | 						// delete the htpasswd file | ||||||
| 						let htpasswd_file = internalAccessList.getFilename(row); | 						const htpasswd_file = internalAccessList.getFilename(row); | ||||||
|  |  | ||||||
| 						try { | 						try { | ||||||
| 							fs.unlinkSync(htpasswd_file); | 							fs.unlinkSync(htpasswd_file); | ||||||
| 						} catch (err) { | 						} catch (_err) { | ||||||
| 							// do nothing | 							// do nothing | ||||||
| 						} | 						} | ||||||
| 					}) | 					}) | ||||||
| @@ -370,10 +374,13 @@ const internalAccessList = { | |||||||
| 	getAll: (access, expand, search_query) => { | 	getAll: (access, expand, search_query) => { | ||||||
| 		return access.can('access_lists:list') | 		return access.can('access_lists:list') | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				let query = accessListModel | 				const 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')) | ||||||
| 					.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0') | 					.leftJoin('proxy_host', function() { | ||||||
|  | 						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]') | ||||||
| @@ -386,19 +393,19 @@ const internalAccessList = { | |||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string') { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('name', 'like', '%' + search_query + '%'); | 						this.where('name', 'like', `%${search_query}%`); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof expand !== 'undefined' && expand !== null) { | 				if (typeof expand !== 'undefined' && expand !== null) { | ||||||
| 					query.withGraphFetched('[' + expand.join(', ') + ']'); | 					query.withGraphFetched(`[${expand.join(', ')}]`); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
| 			}) | 			}) | ||||||
| 			.then((rows) => { | 			.then((rows) => { | ||||||
| 				if (rows) { | 				if (rows) { | ||||||
| 					rows.map(function (row, idx) { | 					rows.map((row, idx) => { | ||||||
| 						if (typeof row.items !== 'undefined' && row.items) { | 						if (typeof row.items !== 'undefined' && row.items) { | ||||||
| 							rows[idx] = internalAccessList.maskItems(row); | 							rows[idx] = internalAccessList.maskItems(row); | ||||||
| 						} | 						} | ||||||
| @@ -417,7 +424,7 @@ const internalAccessList = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: (user_id, visibility) => { | 	getCount: (user_id, visibility) => { | ||||||
| 		let query = accessListModel | 		const query = accessListModel | ||||||
| 			.query() | 			.query() | ||||||
| 			.count('id as count') | 			.count('id as count') | ||||||
| 			.where('is_deleted', 0); | 			.where('is_deleted', 0); | ||||||
| @@ -438,7 +445,7 @@ const internalAccessList = { | |||||||
| 	 */ | 	 */ | ||||||
| 	maskItems: (list) => { | 	maskItems: (list) => { | ||||||
| 		if (list && typeof list.items !== 'undefined') { | 		if (list && typeof list.items !== 'undefined') { | ||||||
| 			list.items.map(function (val, idx) { | 			list.items.map((val, idx) => { | ||||||
| 				let repeat_for = 8; | 				let repeat_for = 8; | ||||||
| 				let first_char = '*'; | 				let first_char = '*'; | ||||||
|  |  | ||||||
| @@ -461,7 +468,7 @@ const internalAccessList = { | |||||||
| 	 * @returns {String} | 	 * @returns {String} | ||||||
| 	 */ | 	 */ | ||||||
| 	getFilename: (list) => { | 	getFilename: (list) => { | ||||||
| 		return '/data/access/' + list.id; | 		return `/data/access/${list.id}`; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -472,15 +479,15 @@ const internalAccessList = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	build: (list) => { | 	build: (list) => { | ||||||
| 		logger.info('Building Access file #' + list.id + ' for: ' + list.name); | 		logger.info(`Building Access file #${list.id} for: ${list.name}`); | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			let htpasswd_file = internalAccessList.getFilename(list); | 			const htpasswd_file = internalAccessList.getFilename(list); | ||||||
|  |  | ||||||
| 			// 1. remove any existing access file | 			// 1. remove any existing access file | ||||||
| 			try { | 			try { | ||||||
| 				fs.unlinkSync(htpasswd_file); | 				fs.unlinkSync(htpasswd_file); | ||||||
| 			} catch (err) { | 			} catch (_err) { | ||||||
| 				// do nothing | 				// do nothing | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -497,12 +504,17 @@ const internalAccessList = { | |||||||
| 				if (list.items.length) { | 				if (list.items.length) { | ||||||
| 					return new Promise((resolve, reject) => { | 					return new Promise((resolve, reject) => { | ||||||
| 						batchflow(list.items).sequential() | 						batchflow(list.items).sequential() | ||||||
| 							.each((i, item, next) => { | 							.each((_i, item, next) => { | ||||||
| 								if (typeof item.password !== 'undefined' && item.password.length) { | 								if (typeof item.password !== 'undefined' && item.password.length) { | ||||||
| 									logger.info('Adding: ' + item.username); | 									logger.info(`Adding: ${item.username}`); | ||||||
|  |  | ||||||
| 									utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password]) | 									utils.execFile('openssl', ['passwd', '-apr1', item.password]) | ||||||
| 										.then((/*result*/) => { | 										.then((res) => { | ||||||
|  | 											try { | ||||||
|  | 												fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {encoding: 'utf8'}); | ||||||
|  | 											} catch (err) { | ||||||
|  | 												reject(err); | ||||||
|  | 											} | ||||||
| 											next(); | 											next(); | ||||||
| 										}) | 										}) | ||||||
| 										.catch((err) => { | 										.catch((err) => { | ||||||
| @@ -516,7 +528,7 @@ const internalAccessList = { | |||||||
| 								reject(err); | 								reject(err); | ||||||
| 							}) | 							}) | ||||||
| 							.end((results) => { | 							.end((results) => { | ||||||
| 								logger.success('Built Access file #' + list.id + ' for: ' + list.name); | 								logger.success(`Built Access file #${list.id} for: ${list.name}`); | ||||||
| 								resolve(results); | 								resolve(results); | ||||||
| 							}); | 							}); | ||||||
| 					}); | 					}); | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| 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 = { | ||||||
|  |  | ||||||
| @@ -22,9 +23,9 @@ const internalAuditLog = { | |||||||
| 					.allowGraph('[user]'); | 					.allowGraph('[user]'); | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string') { | 				if (typeof search_query === 'string' && search_query.length > 0) { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('meta', 'like', '%' + search_query + '%'); | 						this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| const _                = require('lodash'); | const _                = require('lodash'); | ||||||
| const fs               = require('fs'); | const fs               = require('node:fs'); | ||||||
| const https            = require('https'); | const https            = require('node:https'); | ||||||
| const tempWrite        = require('temp-write'); | const tempWrite        = require('temp-write'); | ||||||
| const moment           = require('moment'); | const moment           = require('moment'); | ||||||
| const archiver         = require('archiver'); | const archiver         = require('archiver'); | ||||||
| @@ -49,7 +49,7 @@ const internalCertificate = { | |||||||
| 	processExpiringHosts: () => { | 	processExpiringHosts: () => { | ||||||
| 		if (!internalCertificate.intervalProcessing) { | 		if (!internalCertificate.intervalProcessing) { | ||||||
| 			internalCertificate.intervalProcessing = true; | 			internalCertificate.intervalProcessing = true; | ||||||
| 			logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...'); | 			logger.info(`Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`); | ||||||
|  |  | ||||||
| 			const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); | 			const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); | ||||||
|  |  | ||||||
| @@ -70,7 +70,7 @@ const internalCertificate = { | |||||||
| 					 */ | 					 */ | ||||||
| 					let sequence = Promise.resolve(); | 					let sequence = Promise.resolve(); | ||||||
|  |  | ||||||
| 					certificates.forEach(function (certificate) { | 					certificates.forEach((certificate) => { | ||||||
| 						sequence = sequence.then(() => | 						sequence = sequence.then(() => | ||||||
| 							internalCertificate | 							internalCertificate | ||||||
| 								.renew( | 								.renew( | ||||||
| @@ -202,7 +202,7 @@ const internalCertificate = { | |||||||
| 						.then(() => { | 						.then(() => { | ||||||
| 							// At this point, the letsencrypt cert should exist on disk. | 							// At this point, the letsencrypt cert should exist on disk. | ||||||
| 							// Lets get the expiry date from the file and update the row silently | 							// Lets get the expiry date from the file and update the row silently | ||||||
| 							return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem') | 							return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`) | ||||||
| 								.then((cert_info) => { | 								.then((cert_info) => { | ||||||
| 									return certificateModel | 									return certificateModel | ||||||
| 										.query() | 										.query() | ||||||
| @@ -263,7 +263,7 @@ const internalCertificate = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (row.id !== data.id) { | 				if (row.id !== data.id) { | ||||||
| 					// Sanity check that something crazy hasn't happened | 					// Sanity check that something crazy hasn't happened | ||||||
| 					throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | 					throw new error.InternalValidationError(`Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return certificateModel | 				return certificateModel | ||||||
| @@ -308,11 +308,14 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		return access.can('certificates:get', data.id) | 		return access.can('certificates:get', data.id) | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				let query = certificateModel | 				const query = certificateModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.andWhere('id', data.id) | 					.andWhere('id', data.id) | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner]') | ||||||
|  | 					.allowGraph('[proxy_hosts]') | ||||||
|  | 					.allowGraph('[redirection_hosts]') | ||||||
|  | 					.allowGraph('[dead_hosts]') | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -320,7 +323,7 @@ const internalCertificate = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||||
| 					query.withGraphFetched('[' + data.expand.join(', ') + ']'); | 					query.withGraphFetched(`[${data.expand.join(', ')}]`); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRow(omissions())); | 				return query.then(utils.omitRow(omissions())); | ||||||
| @@ -351,17 +354,17 @@ const internalCertificate = { | |||||||
| 				}) | 				}) | ||||||
| 				.then((certificate) => { | 				.then((certificate) => { | ||||||
| 					if (certificate.provider === 'letsencrypt') { | 					if (certificate.provider === 'letsencrypt') { | ||||||
| 						const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id; | 						const zipDirectory = internalCertificate.getLiveCertPath(data.id); | ||||||
|  |  | ||||||
| 						if (!fs.existsSync(zipDirectory)) { | 						if (!fs.existsSync(zipDirectory)) { | ||||||
| 							throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists'); | 							throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						let certFiles      = fs.readdirSync(zipDirectory) | 						const certFiles    = fs.readdirSync(zipDirectory) | ||||||
| 							.filter((fn) => fn.endsWith('.pem')) | 							.filter((fn) => fn.endsWith('.pem')) | ||||||
| 							.map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); | 							.map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); | ||||||
| 						const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`; | 						const downloadName = `npm-${data.id}-${Date.now()}.zip`; | ||||||
| 						const opName       = '/tmp/' + downloadName; | 						const opName       = `/tmp/${downloadName}`; | ||||||
| 						internalCertificate.zipFiles(certFiles, opName) | 						internalCertificate.zipFiles(certFiles, opName) | ||||||
| 							.then(() => { | 							.then(() => { | ||||||
| 								logger.debug('zip completed : ', opName); | 								logger.debug('zip completed : ', opName); | ||||||
| @@ -389,7 +392,7 @@ const internalCertificate = { | |||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			source | 			source | ||||||
| 				.map((fl) => { | 				.map((fl) => { | ||||||
| 					let fileName = path.basename(fl); | 					const fileName = path.basename(fl); | ||||||
| 					logger.debug(fl, 'added to certificate zip'); | 					logger.debug(fl, 'added to certificate zip'); | ||||||
| 					archive.file(fl, { name: fileName }); | 					archive.file(fl, { name: fileName }); | ||||||
| 				}); | 				}); | ||||||
| @@ -459,11 +462,14 @@ const internalCertificate = { | |||||||
| 	getAll: (access, expand, search_query) => { | 	getAll: (access, expand, search_query) => { | ||||||
| 		return access.can('certificates:list') | 		return access.can('certificates:list') | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				let query = certificateModel | 				const query = certificateModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner]') | ||||||
|  | 					.allowGraph('[proxy_hosts]') | ||||||
|  | 					.allowGraph('[redirection_hosts]') | ||||||
|  | 					.allowGraph('[dead_hosts]') | ||||||
| 					.orderBy('nice_name', 'ASC'); | 					.orderBy('nice_name', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -473,12 +479,12 @@ const internalCertificate = { | |||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string') { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('nice_name', 'like', '%' + search_query + '%'); | 						this.where('nice_name', 'like', `%${search_query}%`); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof expand !== 'undefined' && expand !== null) { | 				if (typeof expand !== 'undefined' && expand !== null) { | ||||||
| 					query.withGraphFetched('[' + expand.join(', ') + ']'); | 					query.withGraphFetched(`[${expand.join(', ')}]`); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
| @@ -493,7 +499,7 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: (user_id, visibility) => { | 	getCount: (user_id, visibility) => { | ||||||
| 		let query = certificateModel | 		const query = certificateModel | ||||||
| 			.query() | 			.query() | ||||||
| 			.count('id as count') | 			.count('id as count') | ||||||
| 			.where('is_deleted', 0); | 			.where('is_deleted', 0); | ||||||
| @@ -515,7 +521,7 @@ const internalCertificate = { | |||||||
| 	writeCustomCert: (certificate) => { | 	writeCustomCert: (certificate) => { | ||||||
| 		logger.info('Writing Custom Certificate:', certificate); | 		logger.info('Writing Custom Certificate:', certificate); | ||||||
|  |  | ||||||
| 		const dir = '/data/custom_ssl/npm-' + certificate.id; | 		const dir = `/data/custom_ssl/npm-${certificate.id}`; | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			if (certificate.provider === 'letsencrypt') { | 			if (certificate.provider === 'letsencrypt') { | ||||||
| @@ -525,7 +531,7 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 			let certData = certificate.meta.certificate; | 			let certData = certificate.meta.certificate; | ||||||
| 			if (typeof certificate.meta.intermediate_certificate !== 'undefined') { | 			if (typeof certificate.meta.intermediate_certificate !== 'undefined') { | ||||||
| 				certData = certData + '\n' + certificate.meta.intermediate_certificate; | 				certData = `${certData}\n${certificate.meta.intermediate_certificate}`; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| @@ -537,7 +543,7 @@ const internalCertificate = { | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			fs.writeFile(dir + '/fullchain.pem', certData, function (err) { | 			fs.writeFile(`${dir}/fullchain.pem`, certData, (err) => { | ||||||
| 				if (err) { | 				if (err) { | ||||||
| 					reject(err); | 					reject(err); | ||||||
| 				} else { | 				} else { | ||||||
| @@ -547,7 +553,7 @@ const internalCertificate = { | |||||||
| 		}) | 		}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return new Promise((resolve, reject) => { | 				return new Promise((resolve, reject) => { | ||||||
| 					fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) { | 					fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { | ||||||
| 						if (err) { | 						if (err) { | ||||||
| 							reject(err); | 							reject(err); | ||||||
| 						} else { | 						} else { | ||||||
| @@ -585,7 +591,7 @@ const internalCertificate = { | |||||||
| 	validate: (data) => { | 	validate: (data) => { | ||||||
| 		return new Promise((resolve) => { | 		return new Promise((resolve) => { | ||||||
| 			// Put file contents into an object | 			// Put file contents into an object | ||||||
| 			let files = {}; | 			const files = {}; | ||||||
| 			_.map(data.files, (file, name) => { | 			_.map(data.files, (file, name) => { | ||||||
| 				if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { | 				if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { | ||||||
| 					files[name] = file.data.toString(); | 					files[name] = file.data.toString(); | ||||||
| @@ -597,7 +603,7 @@ const internalCertificate = { | |||||||
| 			.then((files) => { | 			.then((files) => { | ||||||
| 				// For each file, create a temp file and write the contents to it | 				// For each file, create a temp file and write the contents to it | ||||||
| 				// Then test it depending on the file type | 				// Then test it depending on the file type | ||||||
| 				let promises = []; | 				const promises = []; | ||||||
| 				_.map(files, (content, type) => { | 				_.map(files, (content, type) => { | ||||||
| 					promises.push(new Promise((resolve) => { | 					promises.push(new Promise((resolve) => { | ||||||
| 						if (type === 'certificate_key') { | 						if (type === 'certificate_key') { | ||||||
| @@ -682,11 +688,11 @@ const internalCertificate = { | |||||||
| 						reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); | 						reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); | ||||||
| 					}, 10000); | 					}, 10000); | ||||||
| 					utils | 					utils | ||||||
| 						.exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ') | 						.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) | ||||||
| 						.then((result) => { | 						.then((result) => { | ||||||
| 							clearTimeout(failTimeout); | 							clearTimeout(failTimeout); | ||||||
| 							if (!result.toLowerCase().includes('key is valid')) { | 							if (!result.toLowerCase().includes('key is valid')) { | ||||||
| 								reject(new error.ValidationError('Result Validation Error: ' + result)); | 								reject(new error.ValidationError(`Result Validation Error: ${result}`)); | ||||||
| 							} | 							} | ||||||
| 							fs.unlinkSync(filepath); | 							fs.unlinkSync(filepath); | ||||||
| 							resolve(true); | 							resolve(true); | ||||||
| @@ -694,7 +700,7 @@ const internalCertificate = { | |||||||
| 						.catch((err) => { | 						.catch((err) => { | ||||||
| 							clearTimeout(failTimeout); | 							clearTimeout(failTimeout); | ||||||
| 							fs.unlinkSync(filepath); | 							fs.unlinkSync(filepath); | ||||||
| 							reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err)); | 							reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); | ||||||
| 						}); | 						}); | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| @@ -729,9 +735,9 @@ const internalCertificate = { | |||||||
| 	 * @param {Boolean} [throw_expired]  Throw when the certificate is out of date | 	 * @param {Boolean} [throw_expired]  Throw when the certificate is out of date | ||||||
| 	 */ | 	 */ | ||||||
| 	getCertificateInfoFromFile: (certificate_file, throw_expired) => { | 	getCertificateInfoFromFile: (certificate_file, throw_expired) => { | ||||||
| 		let certData = {}; | 		const certData = {}; | ||||||
|  |  | ||||||
| 		return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') | 		return utils.execFile('openssl', ['x509', '-in', certificate_file, '-subject', '-noout']) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				// Examples: | 				// Examples: | ||||||
| 				// subject=CN = *.jc21.com | 				// subject=CN = *.jc21.com | ||||||
| @@ -739,11 +745,11 @@ const internalCertificate = { | |||||||
| 				const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; | 				const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; | ||||||
| 				const match = regex.exec(result); | 				const match = regex.exec(result); | ||||||
| 				if (match && typeof match[1] !== 'undefined') { | 				if (match && typeof match[1] !== 'undefined') { | ||||||
| 					certData['cn'] = match[1]; | 					certData.cn = match[1]; | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); | 				return utils.execFile('openssl', ['x509', '-in', certificate_file, '-issuer', '-noout']); | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| @@ -754,11 +760,11 @@ const internalCertificate = { | |||||||
| 				const regex = /^(?:issuer=)?(.*)$/gim; | 				const regex = /^(?:issuer=)?(.*)$/gim; | ||||||
| 				const match = regex.exec(result); | 				const match = regex.exec(result); | ||||||
| 				if (match && typeof match[1] !== 'undefined') { | 				if (match && typeof match[1] !== 'undefined') { | ||||||
| 					certData['issuer'] = match[1]; | 					certData.issuer = match[1]; | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); | 				return utils.execFile('openssl', ['x509', '-in', certificate_file, '-dates', '-noout']); | ||||||
| 			}) | 			}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				// notBefore=Jul 14 04:04:29 2018 GMT | 				// notBefore=Jul 14 04:04:29 2018 GMT | ||||||
| @@ -767,7 +773,7 @@ const internalCertificate = { | |||||||
| 				let validTo   = null; | 				let validTo   = null; | ||||||
|  |  | ||||||
| 				const lines = result.split('\n'); | 				const lines = result.split('\n'); | ||||||
| 				lines.map(function (str) { | 				lines.map((str) => { | ||||||
| 					const regex = /^(\S+)=(.*)$/gim; | 					const regex = /^(\S+)=(.*)$/gim; | ||||||
| 					const match = regex.exec(str.trim()); | 					const match = regex.exec(str.trim()); | ||||||
|  |  | ||||||
| @@ -783,21 +789,21 @@ const internalCertificate = { | |||||||
| 				}); | 				}); | ||||||
|  |  | ||||||
| 				if (!validFrom || !validTo) { | 				if (!validFrom || !validTo) { | ||||||
| 					throw new error.ValidationError('Could not determine dates from certificate: ' + result); | 					throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { | 				if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { | ||||||
| 					throw new error.ValidationError('Certificate has expired'); | 					throw new error.ValidationError('Certificate has expired'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				certData['dates'] = { | 				certData.dates = { | ||||||
| 					from: validFrom, | 					from: validFrom, | ||||||
| 					to:   validTo | 					to:   validTo | ||||||
| 				}; | 				}; | ||||||
|  |  | ||||||
| 				return certData; | 				return certData; | ||||||
| 			}).catch((err) => { | 			}).catch((err) => { | ||||||
| 				throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); | 				throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -808,7 +814,7 @@ const internalCertificate = { | |||||||
| 	 * @param   {Boolean} [remove] | 	 * @param   {Boolean} [remove] | ||||||
| 	 * @returns {Object} | 	 * @returns {Object} | ||||||
| 	 */ | 	 */ | ||||||
| 	cleanMeta: function (meta, remove) { | 	cleanMeta: (meta, remove) => { | ||||||
| 		internalCertificate.allowedSslFiles.map((key) => { | 		internalCertificate.allowedSslFiles.map((key) => { | ||||||
| 			if (typeof meta[key] !== 'undefined' && meta[key]) { | 			if (typeof meta[key] !== 'undefined' && meta[key]) { | ||||||
| 				if (remove) { | 				if (remove) { | ||||||
| @@ -828,24 +834,35 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	requestLetsEncryptSsl: (certificate) => { | 	requestLetsEncryptSsl: (certificate) => { | ||||||
| 		logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); | 		logger.info(`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		const cmd = `${certbotCommand} certonly ` + | 		const args = [ | ||||||
| 			`--config '${letsencryptConfig}' ` + | 			'certonly', | ||||||
| 			'--work-dir "/tmp/letsencrypt-lib" ' + | 			'--config', | ||||||
| 			'--logs-dir "/tmp/letsencrypt-log" ' + | 			letsencryptConfig, | ||||||
| 			`--cert-name "npm-${certificate.id}" ` + | 			'--work-dir', | ||||||
| 			'--agree-tos ' + | 			'/tmp/letsencrypt-lib', | ||||||
| 			'--authenticator webroot ' + | 			'--logs-dir', | ||||||
| 			`--email '${certificate.meta.letsencrypt_email}' ` + | 			'/tmp/letsencrypt-log', | ||||||
| 			'--preferred-challenges "dns,http" ' + | 			'--cert-name', | ||||||
| 			`--domains "${certificate.domain_names.join(',')}" ` + | 			`npm-${certificate.id}`, | ||||||
| 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | 			'--agree-tos', | ||||||
| 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | 			'--authenticator', | ||||||
|  | 			'webroot', | ||||||
|  | 			'--email', | ||||||
|  | 			certificate.meta.letsencrypt_email, | ||||||
|  | 			'--preferred-challenges', | ||||||
|  | 			'dns,http', | ||||||
|  | 			'--domains', | ||||||
|  | 			certificate.domain_names.join(','), | ||||||
|  | 		]; | ||||||
|  |  | ||||||
| 		logger.info('Command:', cmd); | 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); | ||||||
|  | 		args.push(...adds.args); | ||||||
|  |  | ||||||
| 		return utils.exec(cmd) | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | ||||||
|  |  | ||||||
|  | 		return utils.execFile(certbotCommand, args, adds.opts) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				logger.success(result); | 				logger.success(result); | ||||||
| 				return result; | 				return result; | ||||||
| @@ -862,50 +879,48 @@ const internalCertificate = { | |||||||
| 	requestLetsEncryptSslWithDnsChallenge: async (certificate) => { | 	requestLetsEncryptSslWithDnsChallenge: async (certificate) => { | ||||||
| 		await certbot.installPlugin(certificate.meta.dns_provider); | 		await certbot.installPlugin(certificate.meta.dns_provider); | ||||||
| 		const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; | 		const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; | ||||||
| 		logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info(`Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; | 		const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; | ||||||
| 		fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); | 		fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); | ||||||
| 		fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); | 		fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); | ||||||
|  |  | ||||||
| 		// Whether the plugin has a --<name>-credentials argument | 		// Whether the plugin has a --<name>-credentials argument | ||||||
| 		const hasConfigArg = certificate.meta.dns_provider !== 'route53'; | 		const hasConfigArg = certificate.meta.dns_provider !== 'route53'; | ||||||
|  |  | ||||||
| 		let mainCmd = certbotCommand + ' certonly ' + | 		const args = [ | ||||||
| 			`--config '${letsencryptConfig}' ` + | 			'certonly', | ||||||
| 			'--work-dir "/tmp/letsencrypt-lib" ' + | 			'--config', | ||||||
| 			'--logs-dir "/tmp/letsencrypt-log" ' + | 			letsencryptConfig, | ||||||
| 			`--cert-name 'npm-${certificate.id}' ` + | 			'--work-dir', | ||||||
| 			'--agree-tos ' + | 			'/tmp/letsencrypt-lib', | ||||||
| 			`--email '${certificate.meta.letsencrypt_email}' ` + | 			'--logs-dir', | ||||||
| 			`--domains '${certificate.domain_names.join(',')}' ` + | 			'/tmp/letsencrypt-log', | ||||||
| 			`--authenticator '${dnsPlugin.full_plugin_name}' ` + | 			'--cert-name', | ||||||
| 			( | 			`npm-${certificate.id}`, | ||||||
| 				hasConfigArg | 			'--agree-tos', | ||||||
| 					? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' ` | 			'--email', | ||||||
| 					: '' | 			certificate.meta.letsencrypt_email, | ||||||
| 			) + | 			'--domains', | ||||||
| 			( | 			certificate.domain_names.join(','), | ||||||
| 				certificate.meta.propagation_seconds !== undefined | 			'--authenticator', | ||||||
| 					? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' ` | 			dnsPlugin.full_plugin_name, | ||||||
| 					: '' | 		]; | ||||||
| 			) + |  | ||||||
| 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + |  | ||||||
| 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); |  | ||||||
|  |  | ||||||
| 		// Prepend the path to the credentials file as an environment variable | 		if (hasConfigArg) { | ||||||
| 		if (certificate.meta.dns_provider === 'route53') { | 			args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); | ||||||
| 			mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; | 		} | ||||||
|  | 		if (certificate.meta.propagation_seconds !== undefined) { | ||||||
|  | 			args.push(`--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (certificate.meta.dns_provider === 'duckdns') { | 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | ||||||
| 			mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore'; | 		args.push(...adds.args); | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		logger.info('Command:', mainCmd); | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | ||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| 			const result = await utils.exec(mainCmd); | 			const result = await utils.execFile(certbotCommand, args, adds.opts); | ||||||
| 			logger.info(result); | 			logger.info(result); | ||||||
| 			return result; | 			return result; | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -933,7 +948,7 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 					return renewMethod(certificate) | 					return renewMethod(certificate) | ||||||
| 						.then(() => { | 						.then(() => { | ||||||
| 							return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem'); | 							return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`); | ||||||
| 						}) | 						}) | ||||||
| 						.then((cert_info) => { | 						.then((cert_info) => { | ||||||
| 							return certificateModel | 							return certificateModel | ||||||
| @@ -965,22 +980,31 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	renewLetsEncryptSsl: (certificate) => { | 	renewLetsEncryptSsl: (certificate) => { | ||||||
| 		logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); | 		logger.info(`Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		const cmd = certbotCommand + ' renew --force-renewal ' + | 		const args = [ | ||||||
| 			`--config '${letsencryptConfig}' ` + | 			'renew', | ||||||
| 			'--work-dir "/tmp/letsencrypt-lib" ' + | 			'--force-renewal', | ||||||
| 			'--logs-dir "/tmp/letsencrypt-log" ' + | 			'--config', | ||||||
| 			`--cert-name 'npm-${certificate.id}' ` + | 			letsencryptConfig, | ||||||
| 			'--preferred-challenges "dns,http" ' + | 			'--work-dir', | ||||||
| 			'--no-random-sleep-on-renew ' + | 			'/tmp/letsencrypt-lib', | ||||||
| 			'--disable-hook-validation ' + | 			'--logs-dir', | ||||||
| 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | 			'/tmp/letsencrypt-log', | ||||||
| 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | 			'--cert-name', | ||||||
|  | 			`npm-${certificate.id}`, | ||||||
|  | 			'--preferred-challenges', | ||||||
|  | 			'dns,http', | ||||||
|  | 			'--no-random-sleep-on-renew', | ||||||
|  | 			'--disable-hook-validation', | ||||||
|  | 		]; | ||||||
|  |  | ||||||
| 		logger.info('Command:', cmd); | 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | ||||||
|  | 		args.push(...adds.args); | ||||||
|  |  | ||||||
| 		return utils.exec(cmd) | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | ||||||
|  |  | ||||||
|  | 		return utils.execFile(certbotCommand, args, adds.opts) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				logger.info(result); | 				logger.info(result); | ||||||
| 				return result; | 				return result; | ||||||
| @@ -998,27 +1022,29 @@ const internalCertificate = { | |||||||
| 			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); | 			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info(`Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		let mainCmd = certbotCommand + ' renew --force-renewal ' + | 		const args = [ | ||||||
| 			`--config "${letsencryptConfig}" ` + | 			'renew', | ||||||
| 			'--work-dir "/tmp/letsencrypt-lib" ' + | 			'--force-renewal', | ||||||
| 			'--logs-dir "/tmp/letsencrypt-log" ' + | 			'--config', | ||||||
| 			`--cert-name 'npm-${certificate.id}' ` + | 			letsencryptConfig, | ||||||
| 			'--disable-hook-validation ' + | 			'--work-dir', | ||||||
| 			'--no-random-sleep-on-renew ' + | 			'/tmp/letsencrypt-lib', | ||||||
| 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | 			'--logs-dir', | ||||||
| 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | 			'/tmp/letsencrypt-log', | ||||||
|  | 			'--cert-name', | ||||||
|  | 			`npm-${certificate.id}`, | ||||||
|  | 			'--disable-hook-validation', | ||||||
|  | 			'--no-random-sleep-on-renew', | ||||||
|  | 		]; | ||||||
|  |  | ||||||
| 		// Prepend the path to the credentials file as an environment variable | 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | ||||||
| 		if (certificate.meta.dns_provider === 'route53') { | 		args.push(...adds.args); | ||||||
| 			const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; |  | ||||||
| 			mainCmd                   = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		logger.info('Command:', mainCmd); | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | ||||||
|  |  | ||||||
| 		return utils.exec(mainCmd) | 		return utils.execFile(certbotCommand, args, adds.opts) | ||||||
| 			.then(async (result) => { | 			.then(async (result) => { | ||||||
| 				logger.info(result); | 				logger.info(result); | ||||||
| 				return result; | 				return result; | ||||||
| @@ -1031,25 +1057,29 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	revokeLetsEncryptSsl: (certificate, throw_errors) => { | 	revokeLetsEncryptSsl: (certificate, throw_errors) => { | ||||||
| 		logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); | 		logger.info(`Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		const mainCmd = certbotCommand + ' revoke ' + | 		const args = [ | ||||||
| 			`--config '${letsencryptConfig}' ` + | 			'revoke', | ||||||
| 			'--work-dir "/tmp/letsencrypt-lib" ' + | 			'--config', | ||||||
| 			'--logs-dir "/tmp/letsencrypt-log" ' + | 			letsencryptConfig, | ||||||
| 			`--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` + | 			'--work-dir', | ||||||
| 			'--delete-after-revoke ' + | 			'/tmp/letsencrypt-lib', | ||||||
| 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | 			'--logs-dir', | ||||||
| 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | 			'/tmp/letsencrypt-log', | ||||||
|  | 			'--cert-path', | ||||||
|  | 			`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, | ||||||
|  | 			'--delete-after-revoke', | ||||||
|  | 		]; | ||||||
|  |  | ||||||
| 		// Don't fail command if file does not exist | 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); | ||||||
| 		const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; | 		args.push(...adds.args); | ||||||
|  |  | ||||||
| 		logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | ||||||
|  |  | ||||||
| 		return utils.exec(mainCmd) | 		return utils.execFile(certbotCommand, args, adds.opts) | ||||||
| 			.then(async (result) => { | 			.then(async (result) => { | ||||||
| 				await utils.exec(delete_credentialsCmd); | 				await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); | ||||||
| 				logger.info(result); | 				logger.info(result); | ||||||
| 				return result; | 				return result; | ||||||
| 			}) | 			}) | ||||||
| @@ -1067,9 +1097,8 @@ const internalCertificate = { | |||||||
| 	 * @returns {Boolean} | 	 * @returns {Boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	hasLetsEncryptSslCerts: (certificate) => { | 	hasLetsEncryptSslCerts: (certificate) => { | ||||||
| 		const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id; | 		const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); | ||||||
|  | 		return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`); | ||||||
| 		return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem'); |  | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -1081,7 +1110,7 @@ const internalCertificate = { | |||||||
| 	 */ | 	 */ | ||||||
| 	disableInUseHosts: (in_use_result) => { | 	disableInUseHosts: (in_use_result) => { | ||||||
| 		if (in_use_result.total_count) { | 		if (in_use_result.total_count) { | ||||||
| 			let promises = []; | 			const promises = []; | ||||||
|  |  | ||||||
| 			if (in_use_result.proxy_hosts.length) { | 			if (in_use_result.proxy_hosts.length) { | ||||||
| 				promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); | 				promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); | ||||||
| @@ -1111,7 +1140,7 @@ const internalCertificate = { | |||||||
| 	 */ | 	 */ | ||||||
| 	enableInUseHosts: (in_use_result) => { | 	enableInUseHosts: (in_use_result) => { | ||||||
| 		if (in_use_result.total_count) { | 		if (in_use_result.total_count) { | ||||||
| 			let promises = []; | 			const promises = []; | ||||||
|  |  | ||||||
| 			if (in_use_result.proxy_hosts.length) { | 			if (in_use_result.proxy_hosts.length) { | ||||||
| 				promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); | 				promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); | ||||||
| @@ -1144,12 +1173,12 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		// Create a test challenge file | 		// Create a test challenge file | ||||||
| 		const testChallengeDir  = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge'; | 		const testChallengeDir  = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge'; | ||||||
| 		const testChallengeFile = testChallengeDir + '/test-challenge'; | 		const testChallengeFile = `${testChallengeDir}/test-challenge`; | ||||||
| 		fs.mkdirSync(testChallengeDir, {recursive: true}); | 		fs.mkdirSync(testChallengeDir, {recursive: true}); | ||||||
| 		fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); | 		fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); | ||||||
|  |  | ||||||
| 		async function performTestForDomain (domain) { | 		async function performTestForDomain (domain) { | ||||||
| 			logger.info('Testing http challenge for ' + domain); | 			logger.info(`Testing http challenge for ${domain}`); | ||||||
| 			const url      = `http://${domain}/.well-known/acme-challenge/test-challenge`; | 			const url      = `http://${domain}/.well-known/acme-challenge/test-challenge`; | ||||||
| 			const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; | 			const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; | ||||||
| 			const options  = { | 			const options  = { | ||||||
| @@ -1163,13 +1192,16 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 			const result = await new Promise((resolve) => { | 			const result = await new Promise((resolve) => { | ||||||
|  |  | ||||||
| 				const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) { | 				const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, (res) => { | ||||||
| 					let responseBody = ''; | 					let responseBody = ''; | ||||||
|  |  | ||||||
| 					res.on('data', (chunk) => responseBody = responseBody + chunk); | 					res.on('data', (chunk) => { | ||||||
| 					res.on('end', function () { | 						responseBody = responseBody + chunk; | ||||||
|  | 					}); | ||||||
|  |  | ||||||
|  | 					res.on('end', () => { | ||||||
| 						try { | 						try { | ||||||
| 							const parsedBody = JSON.parse(responseBody + ''); | 							const parsedBody = JSON.parse(`${responseBody}`); | ||||||
| 							if (res.statusCode !== 200) { | 							if (res.statusCode !== 200) { | ||||||
| 								logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); | 								logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); | ||||||
| 								resolve(undefined); | 								resolve(undefined); | ||||||
| @@ -1190,7 +1222,7 @@ const internalCertificate = { | |||||||
| 				// Make sure to write the request body. | 				// Make sure to write the request body. | ||||||
| 				req.write(formBody); | 				req.write(formBody); | ||||||
| 				req.end(); | 				req.end(); | ||||||
| 				req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); | 				req.on('error', (e) => { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); | ||||||
| 					resolve(undefined); }); | 					resolve(undefined); }); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| @@ -1232,6 +1264,34 @@ const internalCertificate = { | |||||||
| 		fs.unlinkSync(testChallengeFile); | 		fs.unlinkSync(testChallengeFile); | ||||||
|  |  | ||||||
| 		return results; | 		return results; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	getAdditionalCertbotArgs: (certificate_id, dns_provider) => { | ||||||
|  | 		const args = []; | ||||||
|  | 		if (letsencryptServer !== null) { | ||||||
|  | 			args.push('--server', letsencryptServer); | ||||||
|  | 		} | ||||||
|  | 		if (letsencryptStaging && letsencryptServer === null) { | ||||||
|  | 			args.push('--staging'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// For route53, add the credentials file as an environment variable, | ||||||
|  | 		// inheriting the process env | ||||||
|  | 		const opts = {}; | ||||||
|  | 		if (certificate_id && dns_provider === 'route53') { | ||||||
|  | 			opts.env                 = process.env; | ||||||
|  | 			opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (dns_provider === 'duckdns') { | ||||||
|  | 			args.push('--dns-duckdns-no-txt-restore'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return {args: args, opts: opts}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	getLiveCertPath: (certificate_id) => { | ||||||
|  | 		return `/etc/letsencrypt/live/npm-${certificate_id}`; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ 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']; | ||||||
| @@ -409,16 +410,16 @@ const internalDeadHost = { | |||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner,certificate]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.orderBy('domain_names', 'ASC'); | 					.orderBy(castJsonIfNeed('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') { | 				if (typeof search_query === 'string' && search_query.length > 0) { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('domain_names', 'like', '%' + search_query + '%'); | 						this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ 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 = { | ||||||
|  |  | ||||||
| @@ -17,7 +18,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; | ||||||
|  |  | ||||||
| 		let combined_data = _.assign({}, existing_data, data); | 		const 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; | ||||||
| @@ -73,7 +74,7 @@ const internalHost = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getHostsWithDomains: function (domain_names) { | 	getHostsWithDomains: function (domain_names) { | ||||||
| 		let promises = [ | 		const promises = [ | ||||||
| 			proxyHostModel | 			proxyHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0), | 				.where('is_deleted', 0), | ||||||
| @@ -125,19 +126,19 @@ const internalHost = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	isHostnameTaken: function (hostname, ignore_type, ignore_id) { | 	isHostnameTaken: function (hostname, ignore_type, ignore_id) { | ||||||
| 		let promises = [ | 		const promises = [ | ||||||
| 			proxyHostModel | 			proxyHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0) | 				.where('is_deleted', 0) | ||||||
| 				.andWhere('domain_names', 'like', '%' + hostname + '%'), | 				.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), | ||||||
| 			redirectionHostModel | 			redirectionHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0) | 				.where('is_deleted', 0) | ||||||
| 				.andWhere('domain_names', 'like', '%' + hostname + '%'), | 				.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), | ||||||
| 			deadHostModel | 			deadHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0) | 				.where('is_deleted', 0) | ||||||
| 				.andWhere('domain_names', 'like', '%' + hostname + '%') | 				.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%') | ||||||
| 		]; | 		]; | ||||||
|  |  | ||||||
| 		return Promise.all(promises) | 		return Promise.all(promises) | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| const _      = require('lodash'); | const _      = require('lodash'); | ||||||
| const fs     = require('fs'); | const fs     = require('node:fs'); | ||||||
| const logger = require('../logger').nginx; | const logger = require('../logger').nginx; | ||||||
| const config = require('../lib/config'); | const config = require('../lib/config'); | ||||||
| const utils  = require('../lib/utils'); | const utils  = require('../lib/utils'); | ||||||
| @@ -57,9 +57,9 @@ const internalNginx = { | |||||||
| 						// It will always look like this: | 						// It will always look like this: | ||||||
| 						//   nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) | 						//   nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) | ||||||
|  |  | ||||||
| 						let valid_lines = []; | 						const valid_lines = []; | ||||||
| 						let err_lines   = err.message.split('\n'); | 						const err_lines   = err.message.split('\n'); | ||||||
| 						err_lines.map(function (line) { | 						err_lines.map((line) => { | ||||||
| 							if (line.indexOf('/var/log/nginx/error.log') === -1) { | 							if (line.indexOf('/var/log/nginx/error.log') === -1) { | ||||||
| 								valid_lines.push(line); | 								valid_lines.push(line); | ||||||
| 							} | 							} | ||||||
| @@ -105,7 +105,7 @@ const internalNginx = { | |||||||
| 			logger.info('Testing Nginx configuration'); | 			logger.info('Testing Nginx configuration'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return utils.exec('/usr/sbin/nginx -t -g "error_log off;"'); | 		return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -115,7 +115,7 @@ const internalNginx = { | |||||||
| 		return internalNginx.test() | 		return internalNginx.test() | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				logger.info('Reloading Nginx'); | 				logger.info('Reloading Nginx'); | ||||||
| 				return utils.exec('/usr/sbin/nginx -s reload'); | 				return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']); | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -128,7 +128,7 @@ const internalNginx = { | |||||||
| 		if (host_type === 'default') { | 		if (host_type === 'default') { | ||||||
| 			return '/data/nginx/default_host/site.conf'; | 			return '/data/nginx/default_host/site.conf'; | ||||||
| 		} | 		} | ||||||
| 		return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf'; | 		return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -141,7 +141,7 @@ const internalNginx = { | |||||||
| 			let template; | 			let template; | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| 				template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'}); | 				template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'}); | ||||||
| 			} catch (err) { | 			} catch (err) { | ||||||
| 				reject(new error.ConfigurationError(err.message)); | 				reject(new error.ConfigurationError(err.message)); | ||||||
| 				return; | 				return; | ||||||
| @@ -152,7 +152,7 @@ const internalNginx = { | |||||||
|  |  | ||||||
| 			const locationRendering = async () => { | 			const locationRendering = async () => { | ||||||
| 				for (let i = 0; i < host.locations.length; i++) { | 				for (let i = 0; i < host.locations.length; i++) { | ||||||
| 					let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, | 					const locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, | ||||||
| 						{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, | 						{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, | ||||||
| 						{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, | 						{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, | ||||||
| 						{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, | 						{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, | ||||||
| @@ -183,21 +183,21 @@ const internalNginx = { | |||||||
| 	 */ | 	 */ | ||||||
| 	generateConfig: (host_type, host_row) => { | 	generateConfig: (host_type, host_row) => { | ||||||
| 		// Prevent modifying the original object: | 		// Prevent modifying the original object: | ||||||
| 		let host             = JSON.parse(JSON.stringify(host_row)); | 		const host           = JSON.parse(JSON.stringify(host_row)); | ||||||
| 		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); | 		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); | ||||||
|  |  | ||||||
| 		if (config.debug()) { | 		if (config.debug()) { | ||||||
| 			logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); | 			logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const renderEngine = utils.getRenderEngine(); | 		const renderEngine = utils.getRenderEngine(); | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			let template = null; | 			let template   = null; | ||||||
| 			let filename = internalNginx.getConfigName(nice_host_type, host.id); | 			const filename = internalNginx.getConfigName(nice_host_type, host.id); | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| 				template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'}); | 				template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'}); | ||||||
| 			} catch (err) { | 			} catch (err) { | ||||||
| 				reject(new error.ConfigurationError(err.message)); | 				reject(new error.ConfigurationError(err.message)); | ||||||
| 				return; | 				return; | ||||||
| @@ -252,7 +252,7 @@ const internalNginx = { | |||||||
| 					}) | 					}) | ||||||
| 					.catch((err) => { | 					.catch((err) => { | ||||||
| 						if (config.debug()) { | 						if (config.debug()) { | ||||||
| 							logger.warn('Could not write ' + filename + ':', err.message); | 							logger.warn(`Could not write ${filename}:`, err.message); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						reject(new error.ConfigurationError(err.message)); | 						reject(new error.ConfigurationError(err.message)); | ||||||
| @@ -277,11 +277,11 @@ const internalNginx = { | |||||||
| 		const renderEngine = utils.getRenderEngine(); | 		const renderEngine = utils.getRenderEngine(); | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			let template = null; | 			let template   = null; | ||||||
| 			let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; | 			const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| 				template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'}); | 				template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'}); | ||||||
| 			} catch (err) { | 			} catch (err) { | ||||||
| 				reject(new error.ConfigurationError(err.message)); | 				reject(new error.ConfigurationError(err.message)); | ||||||
| 				return; | 				return; | ||||||
| @@ -302,7 +302,7 @@ const internalNginx = { | |||||||
| 				}) | 				}) | ||||||
| 				.catch((err) => { | 				.catch((err) => { | ||||||
| 					if (config.debug()) { | 					if (config.debug()) { | ||||||
| 						logger.warn('Could not write ' + filename + ':', err.message); | 						logger.warn(`Could not write ${filename}:`, err.message); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					reject(new error.ConfigurationError(err.message)); | 					reject(new error.ConfigurationError(err.message)); | ||||||
| @@ -316,7 +316,7 @@ const internalNginx = { | |||||||
| 	 * @param   {String}  filename | 	 * @param   {String}  filename | ||||||
| 	 */ | 	 */ | ||||||
| 	deleteFile: (filename) => { | 	deleteFile: (filename) => { | ||||||
| 		logger.debug('Deleting file: ' + filename); | 		logger.debug(`Deleting file: ${filename}`); | ||||||
| 		try { | 		try { | ||||||
| 			fs.unlinkSync(filename); | 			fs.unlinkSync(filename); | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -330,7 +330,7 @@ const internalNginx = { | |||||||
| 	 * @returns String | 	 * @returns String | ||||||
| 	 */ | 	 */ | ||||||
| 	getFileFriendlyHostType: (host_type) => { | 	getFileFriendlyHostType: (host_type) => { | ||||||
| 		return host_type.replace(new RegExp('-', 'g'), '_'); | 		return host_type.replace(/-/g, '_'); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -340,7 +340,7 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	deleteLetsEncryptRequestConfig: (certificate) => { | 	deleteLetsEncryptRequestConfig: (certificate) => { | ||||||
| 		const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; | 		const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; | ||||||
| 		return new Promise((resolve/*, reject*/) => { | 		return new Promise((resolve/*, reject*/) => { | ||||||
| 			internalNginx.deleteFile(config_file); | 			internalNginx.deleteFile(config_file); | ||||||
| 			resolve(); | 			resolve(); | ||||||
| @@ -355,7 +355,7 @@ const internalNginx = { | |||||||
| 	 */ | 	 */ | ||||||
| 	deleteConfig: (host_type, host, delete_err_file) => { | 	deleteConfig: (host_type, host, delete_err_file) => { | ||||||
| 		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | 		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | ||||||
| 		const config_file_err = config_file + '.err'; | 		const config_file_err = `${config_file}.err`; | ||||||
|  |  | ||||||
| 		return new Promise((resolve/*, reject*/) => { | 		return new Promise((resolve/*, reject*/) => { | ||||||
| 			internalNginx.deleteFile(config_file); | 			internalNginx.deleteFile(config_file); | ||||||
| @@ -373,7 +373,7 @@ const internalNginx = { | |||||||
| 	 */ | 	 */ | ||||||
| 	renameConfigAsError: (host_type, host) => { | 	renameConfigAsError: (host_type, host) => { | ||||||
| 		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | 		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | ||||||
| 		const config_file_err = config_file + '.err'; | 		const config_file_err = `${config_file}.err`; | ||||||
|  |  | ||||||
| 		return new Promise((resolve/*, reject*/) => { | 		return new Promise((resolve/*, reject*/) => { | ||||||
| 			fs.unlink(config_file, () => { | 			fs.unlink(config_file, () => { | ||||||
| @@ -392,8 +392,8 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	bulkGenerateConfigs: (host_type, hosts) => { | 	bulkGenerateConfigs: (host_type, hosts) => { | ||||||
| 		let promises = []; | 		const promises = []; | ||||||
| 		hosts.map(function (host) { | 		hosts.map((host) => { | ||||||
| 			promises.push(internalNginx.generateConfig(host_type, host)); | 			promises.push(internalNginx.generateConfig(host_type, host)); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| @@ -406,8 +406,8 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	bulkDeleteConfigs: (host_type, hosts) => { | 	bulkDeleteConfigs: (host_type, hosts) => { | ||||||
| 		let promises = []; | 		const promises = []; | ||||||
| 		hosts.map(function (host) { | 		hosts.map((host) => { | ||||||
| 			promises.push(internalNginx.deleteConfig(host_type, host, true)); | 			promises.push(internalNginx.deleteConfig(host_type, host, true)); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| @@ -418,14 +418,12 @@ const internalNginx = { | |||||||
| 	 * @param   {string}  config | 	 * @param   {string}  config | ||||||
| 	 * @returns {boolean} | 	 * @returns {boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	advancedConfigHasDefaultLocation: function (cfg) { | 	advancedConfigHasDefaultLocation: (cfg) => !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im), | ||||||
| 		return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * @returns {boolean} | 	 * @returns {boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	ipv6Enabled: function () { | 	ipv6Enabled: () => { | ||||||
| 		if (typeof process.env.DISABLE_IPV6 !== 'undefined') { | 		if (typeof process.env.DISABLE_IPV6 !== 'undefined') { | ||||||
| 			const disabled = process.env.DISABLE_IPV6.toLowerCase(); | 			const disabled = process.env.DISABLE_IPV6.toLowerCase(); | ||||||
| 			return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); | 			return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ 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']; | ||||||
| @@ -416,16 +417,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('domain_names', 'ASC'); | 					.orderBy(castJsonIfNeed('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') { | 				if (typeof search_query === 'string' && search_query.length > 0) { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('domain_names', 'like', '%' + search_query + '%'); | 						this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ 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']; | ||||||
| @@ -409,16 +410,16 @@ const internalRedirectionHost = { | |||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner,certificate]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.orderBy('domain_names', 'ASC'); | 					.orderBy(castJsonIfNeed('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') { | 				if (typeof search_query === 'string' && search_query.length > 0) { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('domain_names', 'like', '%' + search_query + '%'); | 						this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,15 @@ | |||||||
| const _                = require('lodash'); | const _                   = require('lodash'); | ||||||
| const error            = require('../lib/error'); | const error               = require('../lib/error'); | ||||||
| const utils            = require('../lib/utils'); | const utils               = require('../lib/utils'); | ||||||
| const streamModel      = require('../models/stream'); | const streamModel         = require('../models/stream'); | ||||||
| const internalNginx    = require('./nginx'); | const internalNginx       = require('./nginx'); | ||||||
| const internalAuditLog = require('./audit-log'); | const internalAuditLog    = require('./audit-log'); | ||||||
|  | const internalCertificate = require('./certificate'); | ||||||
|  | const internalHost        = require('./host'); | ||||||
|  | const {castJsonIfNeed}    = require('../lib/helpers'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| 	return ['is_deleted']; | 	return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; | ||||||
| } | } | ||||||
|  |  | ||||||
| const internalStream = { | const internalStream = { | ||||||
| @@ -17,6 +20,12 @@ const internalStream = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	create: (access, data) => { | 	create: (access, data) => { | ||||||
|  | 		const create_certificate = data.certificate_id === 'new'; | ||||||
|  |  | ||||||
|  | 		if (create_certificate) { | ||||||
|  | 			delete data.certificate_id; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return access.can('streams:create', data) | 		return access.can('streams:create', data) | ||||||
| 			.then((/*access_data*/) => { | 			.then((/*access_data*/) => { | ||||||
| 				// TODO: At this point the existing ports should have been checked | 				// TODO: At this point the existing ports should have been checked | ||||||
| @@ -26,16 +35,44 @@ const internalStream = { | |||||||
| 					data.meta = {}; | 					data.meta = {}; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				// streams aren't routed by domain name so don't store domain names in the DB | ||||||
|  | 				let data_no_domains = structuredClone(data); | ||||||
|  | 				delete data_no_domains.domain_names; | ||||||
|  |  | ||||||
| 				return streamModel | 				return streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.insertAndFetch(data) | 					.insertAndFetch(data_no_domains) | ||||||
| 					.then(utils.omitRow(omissions())); | 					.then(utils.omitRow(omissions())); | ||||||
| 			}) | 			}) | ||||||
|  | 			.then((row) => { | ||||||
|  | 				if (create_certificate) { | ||||||
|  | 					return internalCertificate.createQuickCertificate(access, data) | ||||||
|  | 						.then((cert) => { | ||||||
|  | 							// update host with cert id | ||||||
|  | 							return internalStream.update(access, { | ||||||
|  | 								id:             row.id, | ||||||
|  | 								certificate_id: cert.id | ||||||
|  | 							}); | ||||||
|  | 						}) | ||||||
|  | 						.then(() => { | ||||||
|  | 							return row; | ||||||
|  | 						}); | ||||||
|  | 				} else { | ||||||
|  | 					return row; | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 			.then((row) => { | ||||||
|  | 				// re-fetch with cert | ||||||
|  | 				return internalStream.get(access, { | ||||||
|  | 					id:     row.id, | ||||||
|  | 					expand: ['certificate', 'owner'] | ||||||
|  | 				}); | ||||||
|  | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				// Configure nginx | 				// Configure nginx | ||||||
| 				return internalNginx.configure(streamModel, 'stream', row) | 				return internalNginx.configure(streamModel, 'stream', row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						return internalStream.get(access, {id: row.id, expand: ['owner']}); | 						return row; | ||||||
| 					}); | 					}); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| @@ -59,6 +96,12 @@ const internalStream = { | |||||||
| 	 * @return {Promise} | 	 * @return {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	update: (access, data) => { | 	update: (access, data) => { | ||||||
|  | 		const create_certificate = data.certificate_id === 'new'; | ||||||
|  |  | ||||||
|  | 		if (create_certificate) { | ||||||
|  | 			delete data.certificate_id; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return access.can('streams:update', data.id) | 		return access.can('streams:update', data.id) | ||||||
| 			.then((/*access_data*/) => { | 			.then((/*access_data*/) => { | ||||||
| 				// TODO: at this point the existing streams should have been checked | 				// TODO: at this point the existing streams should have been checked | ||||||
| @@ -70,16 +113,32 @@ const internalStream = { | |||||||
| 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				if (create_certificate) { | ||||||
|  | 					return internalCertificate.createQuickCertificate(access, { | ||||||
|  | 						domain_names: data.domain_names || row.domain_names, | ||||||
|  | 						meta:         _.assign({}, row.meta, data.meta) | ||||||
|  | 					}) | ||||||
|  | 						.then((cert) => { | ||||||
|  | 							// update host with cert id | ||||||
|  | 							data.certificate_id = cert.id; | ||||||
|  | 						}) | ||||||
|  | 						.then(() => { | ||||||
|  | 							return row; | ||||||
|  | 						}); | ||||||
|  | 				} else { | ||||||
|  | 					return row; | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 			.then((row) => { | ||||||
|  | 				// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. | ||||||
|  | 				data = _.assign({}, { | ||||||
|  | 					domain_names: row.domain_names | ||||||
|  | 				}, data); | ||||||
|  |  | ||||||
| 				return streamModel | 				return streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.patchAndFetchById(row.id, data) | 					.patchAndFetchById(row.id, data) | ||||||
| 					.then(utils.omitRow(omissions())) | 					.then(utils.omitRow(omissions())) | ||||||
| 					.then((saved_row) => { |  | ||||||
| 						return internalNginx.configure(streamModel, 'stream', saved_row) |  | ||||||
| 							.then(() => { |  | ||||||
| 								return internalStream.get(access, {id: row.id, expand: ['owner']}); |  | ||||||
| 							}); |  | ||||||
| 					}) |  | ||||||
| 					.then((saved_row) => { | 					.then((saved_row) => { | ||||||
| 						// Add to audit log | 						// Add to audit log | ||||||
| 						return internalAuditLog.add(access, { | 						return internalAuditLog.add(access, { | ||||||
| @@ -92,6 +151,17 @@ const internalStream = { | |||||||
| 								return saved_row; | 								return saved_row; | ||||||
| 							}); | 							}); | ||||||
| 					}); | 					}); | ||||||
|  | 			}) | ||||||
|  | 			.then(() => { | ||||||
|  | 				return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']}) | ||||||
|  | 					.then((row) => { | ||||||
|  | 						return internalNginx.configure(streamModel, 'stream', row) | ||||||
|  | 							.then((new_meta) => { | ||||||
|  | 								row.meta = new_meta; | ||||||
|  | 								row      = internalHost.cleanRowCertificateMeta(row); | ||||||
|  | 								return _.omit(row, omissions()); | ||||||
|  | 							}); | ||||||
|  | 					}); | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -114,7 +184,7 @@ const internalStream = { | |||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.andWhere('id', data.id) | 					.andWhere('id', data.id) | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -131,6 +201,7 @@ const internalStream = { | |||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} | 				} | ||||||
|  | 				row = internalHost.cleanRowCertificateMeta(row); | ||||||
| 				// Custom omissions | 				// Custom omissions | ||||||
| 				if (typeof data.omit !== 'undefined' && data.omit !== null) { | 				if (typeof data.omit !== 'undefined' && data.omit !== null) { | ||||||
| 					row = _.omit(row, data.omit); | 					row = _.omit(row, data.omit); | ||||||
| @@ -196,14 +267,14 @@ const internalStream = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return internalStream.get(access, { | 				return internalStream.get(access, { | ||||||
| 					id:     data.id, | 					id:     data.id, | ||||||
| 					expand: ['owner'] | 					expand: ['certificate', 'owner'] | ||||||
| 				}); | 				}); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} else if (row.enabled) { | 				} else if (row.enabled) { | ||||||
| 					throw new error.ValidationError('Host is already enabled'); | 					throw new error.ValidationError('Stream is already enabled'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				row.enabled = 1; | 				row.enabled = 1; | ||||||
| @@ -249,7 +320,7 @@ const internalStream = { | |||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} else if (!row.enabled) { | 				} else if (!row.enabled) { | ||||||
| 					throw new error.ValidationError('Host is already disabled'); | 					throw new error.ValidationError('Stream is already disabled'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				row.enabled = 0; | 				row.enabled = 0; | ||||||
| @@ -293,11 +364,11 @@ 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) => { | ||||||
| 				let query = streamModel | 				const query = streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.orderBy('incoming_port', 'ASC'); | 					.orderBy('incoming_port', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -305,9 +376,9 @@ const internalStream = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string') { | 				if (typeof search_query === 'string' && search_query.length > 0) { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('incoming_port', 'like', '%' + search_query + '%'); | 						this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| @@ -316,6 +387,13 @@ const internalStream = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
|  | 			}) | ||||||
|  | 			.then((rows) => { | ||||||
|  | 				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { | ||||||
|  | 					return internalHost.cleanAllRowsCertificateMeta(rows); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				return rows; | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -327,9 +405,9 @@ const internalStream = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: (user_id, visibility) => { | 	getCount: (user_id, visibility) => { | ||||||
| 		let query = streamModel | 		const 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') { | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ const certbot = { | |||||||
| 	/** | 	/** | ||||||
| 	 * @param {array} pluginKeys | 	 * @param {array} pluginKeys | ||||||
| 	 */ | 	 */ | ||||||
| 	installPlugins: async function (pluginKeys) { | 	installPlugins: async (pluginKeys) => { | ||||||
| 		let hasErrors = false; | 		let hasErrors = false; | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| @@ -21,7 +21,7 @@ const certbot = { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			batchflow(pluginKeys).sequential() | 			batchflow(pluginKeys).sequential() | ||||||
| 				.each((i, pluginKey, next) => { | 				.each((_i, pluginKey, next) => { | ||||||
| 					certbot.installPlugin(pluginKey) | 					certbot.installPlugin(pluginKey) | ||||||
| 						.then(() => { | 						.then(() => { | ||||||
| 							next(); | 							next(); | ||||||
| @@ -51,7 +51,7 @@ const certbot = { | |||||||
| 	 * @param   {string}  pluginKey | 	 * @param   {string}  pluginKey | ||||||
| 	 * @returns {Object} | 	 * @returns {Object} | ||||||
| 	 */ | 	 */ | ||||||
| 	installPlugin: async function (pluginKey) { | 	installPlugin: async (pluginKey) => { | ||||||
| 		if (typeof dnsPlugins[pluginKey] === 'undefined') { | 		if (typeof dnsPlugins[pluginKey] === 'undefined') { | ||||||
| 			// throw Error(`Certbot plugin ${pluginKey} not found`); | 			// throw Error(`Certbot plugin ${pluginKey} not found`); | ||||||
| 			throw new error.ItemNotFoundError(pluginKey); | 			throw new error.ItemNotFoundError(pluginKey); | ||||||
| @@ -63,8 +63,15 @@ const certbot = { | |||||||
| 		plugin.version      = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | 		plugin.version      = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | ||||||
| 		plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | 		plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | ||||||
|  |  | ||||||
| 		const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate'; | 		// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly | ||||||
| 		return utils.exec(cmd) | 		// in new versions of Python | ||||||
|  | 		let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'}); | ||||||
|  | 		if (typeof plugin.env === 'object') { | ||||||
|  | 			env = Object.assign(env, plugin.env); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version}  && deactivate`; | ||||||
|  | 		return utils.exec(cmd, {env}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				logger.complete(`Installed ${pluginKey}`); | 				logger.complete(`Installed ${pluginKey}`); | ||||||
| 				return result; | 				return result; | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ const fs      = require('fs'); | |||||||
| const NodeRSA = require('node-rsa'); | 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; | ||||||
|  |  | ||||||
| @@ -14,7 +17,7 @@ const configure = () => { | |||||||
| 		let configData; | 		let configData; | ||||||
| 		try { | 		try { | ||||||
| 			configData = require(filename); | 			configData = require(filename); | ||||||
| 		} catch (err) { | 		} catch (_) { | ||||||
| 			// do nothing | 			// do nothing | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -34,7 +37,7 @@ const configure = () => { | |||||||
| 		logger.info('Using MySQL configuration'); | 		logger.info('Using MySQL configuration'); | ||||||
| 		instance = { | 		instance = { | ||||||
| 			database: { | 			database: { | ||||||
| 				engine:   'mysql2', | 				engine:   mysqlEngine, | ||||||
| 				host:     envMysqlHost, | 				host:     envMysqlHost, | ||||||
| 				port:     process.env.DB_MYSQL_PORT || 3306, | 				port:     process.env.DB_MYSQL_PORT || 3306, | ||||||
| 				user:     envMysqlUser, | 				user:     envMysqlUser, | ||||||
| @@ -46,13 +49,33 @@ 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:     'sqlite3', | 				client:     sqliteClientName, | ||||||
| 				connection: { | 				connection: { | ||||||
| 					filename: envSqliteFile | 					filename: envSqliteFile | ||||||
| 				}, | 				}, | ||||||
| @@ -143,7 +166,27 @@ module.exports = { | |||||||
| 	 */ | 	 */ | ||||||
| 	isSqlite: function () { | 	isSqlite: function () { | ||||||
| 		instance === null && configure(); | 		instance === null && configure(); | ||||||
| 		return instance.database.knex && instance.database.knex.client === 'sqlite3'; | 		return instance.database.knex && instance.database.knex.client === sqliteClientName; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 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; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| const moment = require('moment'); | const moment       = require('moment'); | ||||||
|  | const {isPostgres} = require('./config'); | ||||||
|  | const {ref}        = require('objection'); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
| @@ -45,6 +47,16 @@ 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; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| const _          = require('lodash'); | const _          = require('lodash'); | ||||||
| const exec       = require('child_process').exec; | const exec       = require('node:child_process').exec; | ||||||
| const execFile   = require('child_process').execFile; | const execFile   = require('node:child_process').execFile; | ||||||
| const { Liquid } = require('liquidjs'); | const { Liquid } = require('liquidjs'); | ||||||
| const logger     = require('../logger').global; | const logger     = require('../logger').global; | ||||||
| const error      = require('./error'); | const error      = require('./error'); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
| 	exec: async function(cmd, options = {}) { | 	exec: async (cmd, options = {}) => { | ||||||
| 		logger.debug('CMD:', cmd); | 		logger.debug('CMD:', cmd); | ||||||
|  |  | ||||||
| 		const { stdout, stderr } = await new Promise((resolve, reject) => { | 		const { stdout, stderr } = await new Promise((resolve, reject) => { | ||||||
| @@ -29,15 +29,19 @@ module.exports = { | |||||||
| 	/** | 	/** | ||||||
| 	 * @param   {String} cmd | 	 * @param   {String} cmd | ||||||
| 	 * @param   {Array}  args | 	 * @param   {Array}  args | ||||||
|  | 	 * @param   {Object|undefined}  options | ||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	execFile: function (cmd, args) { | 	execFile: (cmd, args, options) => { | ||||||
| 		// logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); | 		logger.debug(`CMD: ${cmd} ${args ? args.join(' ') : ''}`); | ||||||
|  | 		if (typeof options === 'undefined') { | ||||||
|  | 			options = {}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			execFile(cmd, args, function (err, stdout, /*stderr*/) { | 			execFile(cmd, args, options, (err, stdout, stderr) => { | ||||||
| 				if (err && typeof err === 'object') { | 				if (err && typeof err === 'object') { | ||||||
| 					reject(err); | 					reject(new error.CommandError(stderr, 1, err)); | ||||||
| 				} else { | 				} else { | ||||||
| 					resolve(stdout.trim()); | 					resolve(stdout.trim()); | ||||||
| 				} | 				} | ||||||
| @@ -51,7 +55,7 @@ module.exports = { | |||||||
| 	 * @param   {Array}  omissions | 	 * @param   {Array}  omissions | ||||||
| 	 * @returns {Function} | 	 * @returns {Function} | ||||||
| 	 */ | 	 */ | ||||||
| 	omitRow: function (omissions) { | 	omitRow: (omissions) => { | ||||||
| 		/** | 		/** | ||||||
| 		 * @param   {Object} row | 		 * @param   {Object} row | ||||||
| 		 * @returns {Object} | 		 * @returns {Object} | ||||||
| @@ -67,7 +71,7 @@ module.exports = { | |||||||
| 	 * @param   {Array}  omissions | 	 * @param   {Array}  omissions | ||||||
| 	 * @returns {Function} | 	 * @returns {Function} | ||||||
| 	 */ | 	 */ | ||||||
| 	omitRows: function (omissions) { | 	omitRows: (omissions) => { | ||||||
| 		/** | 		/** | ||||||
| 		 * @param   {Array} rows | 		 * @param   {Array} rows | ||||||
| 		 * @returns {Object} | 		 * @returns {Object} | ||||||
| @@ -83,9 +87,9 @@ module.exports = { | |||||||
| 	/** | 	/** | ||||||
| 	 * @returns {Object} Liquid render engine | 	 * @returns {Object} Liquid render engine | ||||||
| 	 */ | 	 */ | ||||||
| 	getRenderEngine: function () { | 	getRenderEngine: () => { | ||||||
| 		const renderEngine = new Liquid({ | 		const renderEngine = new Liquid({ | ||||||
| 			root: __dirname + '/../templates/' | 			root: `${__dirname}/../templates/` | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		/** | 		/** | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								backend/migrations/20240427161436_stream_ssl.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								backend/migrations/20240427161436_stream_ssl.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | const migrate_name = 'stream_ssl'; | ||||||
|  | const logger       = require('../logger').migrate; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Migrate | ||||||
|  |  * | ||||||
|  |  * @see http://knexjs.org/#Schema | ||||||
|  |  * | ||||||
|  |  * @param   {Object} knex | ||||||
|  |  * @returns {Promise} | ||||||
|  |  */ | ||||||
|  | exports.up = function (knex) { | ||||||
|  | 	logger.info('[' + migrate_name + '] Migrating Up...'); | ||||||
|  |  | ||||||
|  | 	return knex.schema.table('stream', (table) => { | ||||||
|  | 		table.integer('certificate_id').notNull().unsigned().defaultTo(0); | ||||||
|  | 	}) | ||||||
|  | 		.then(function () { | ||||||
|  | 			logger.info('[' + migrate_name + '] stream Table altered'); | ||||||
|  | 		}); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Undo Migrate | ||||||
|  |  * | ||||||
|  |  * @param   {Object} knex | ||||||
|  |  * @returns {Promise} | ||||||
|  |  */ | ||||||
|  | exports.down = function (knex) { | ||||||
|  | 	logger.info('[' + migrate_name + '] Migrating Down...'); | ||||||
|  |  | ||||||
|  | 	return knex.schema.table('stream', (table) => { | ||||||
|  | 		table.dropColumn('certificate_id'); | ||||||
|  | 	}) | ||||||
|  | 		.then(function () { | ||||||
|  | 			logger.info('[' + migrate_name + '] stream Table altered'); | ||||||
|  | 		}); | ||||||
|  | }; | ||||||
| @@ -4,7 +4,6 @@ | |||||||
| const db      = require('../db'); | const db      = require('../db'); | ||||||
| const helpers = require('../lib/helpers'); | const helpers = require('../lib/helpers'); | ||||||
| const Model   = require('objection').Model; | const Model   = require('objection').Model; | ||||||
| const User    = require('./user'); |  | ||||||
| const now     = require('./now_helper'); | const now     = require('./now_helper'); | ||||||
|  |  | ||||||
| Model.knex(db); | Model.knex(db); | ||||||
| @@ -68,6 +67,11 @@ class Certificate extends Model { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	static get relationMappings () { | 	static get relationMappings () { | ||||||
|  | 		const ProxyHost       = require('./proxy_host'); | ||||||
|  | 		const DeadHost        = require('./dead_host'); | ||||||
|  | 		const User            = require('./user'); | ||||||
|  | 		const RedirectionHost = require('./redirection_host'); | ||||||
|  |  | ||||||
| 		return { | 		return { | ||||||
| 			owner: { | 			owner: { | ||||||
| 				relation:   Model.HasOneRelation, | 				relation:   Model.HasOneRelation, | ||||||
| @@ -79,6 +83,39 @@ class Certificate extends Model { | |||||||
| 				modify: function (qb) { | 				modify: function (qb) { | ||||||
| 					qb.where('user.is_deleted', 0); | 					qb.where('user.is_deleted', 0); | ||||||
| 				} | 				} | ||||||
|  | 			}, | ||||||
|  | 			proxy_hosts: { | ||||||
|  | 				relation:   Model.HasManyRelation, | ||||||
|  | 				modelClass: ProxyHost, | ||||||
|  | 				join:       { | ||||||
|  | 					from: 'certificate.id', | ||||||
|  | 					to:   'proxy_host.certificate_id' | ||||||
|  | 				}, | ||||||
|  | 				modify: function (qb) { | ||||||
|  | 					qb.where('proxy_host.is_deleted', 0); | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			dead_hosts: { | ||||||
|  | 				relation:   Model.HasManyRelation, | ||||||
|  | 				modelClass: DeadHost, | ||||||
|  | 				join:       { | ||||||
|  | 					from: 'certificate.id', | ||||||
|  | 					to:   'dead_host.certificate_id' | ||||||
|  | 				}, | ||||||
|  | 				modify: function (qb) { | ||||||
|  | 					qb.where('dead_host.is_deleted', 0); | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			redirection_hosts: { | ||||||
|  | 				relation:   Model.HasManyRelation, | ||||||
|  | 				modelClass: RedirectionHost, | ||||||
|  | 				join:       { | ||||||
|  | 					from: 'certificate.id', | ||||||
|  | 					to:   'redirection_host.certificate_id' | ||||||
|  | 				}, | ||||||
|  | 				modify: function (qb) { | ||||||
|  | 					qb.where('redirection_host.is_deleted', 0); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -12,7 +12,11 @@ Model.knex(db); | |||||||
|  |  | ||||||
| const boolFields = [ | const boolFields = [ | ||||||
| 	'is_deleted', | 	'is_deleted', | ||||||
|  | 	'ssl_forced', | ||||||
|  | 	'http2_support', | ||||||
| 	'enabled', | 	'enabled', | ||||||
|  | 	'hsts_enabled', | ||||||
|  | 	'hsts_subdomains', | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| class DeadHost extends Model { | class DeadHost extends Model { | ||||||
|   | |||||||
| @@ -17,6 +17,9 @@ 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 { | ||||||
|   | |||||||
| @@ -1,16 +1,15 @@ | |||||||
| // Objection Docs: | const Model       = require('objection').Model; | ||||||
| // http://vincit.github.io/objection.js/ | const db          = require('../db'); | ||||||
|  | const helpers     = require('../lib/helpers'); | ||||||
| const db      = require('../db'); | const User        = require('./user'); | ||||||
| const helpers = require('../lib/helpers'); | const Certificate = require('./certificate'); | ||||||
| const Model   = require('objection').Model; | const now         = require('./now_helper'); | ||||||
| const User    = require('./user'); |  | ||||||
| const now     = require('./now_helper'); |  | ||||||
|  |  | ||||||
| Model.knex(db); | Model.knex(db); | ||||||
|  |  | ||||||
| const boolFields = [ | const boolFields = [ | ||||||
| 	'is_deleted', | 	'is_deleted', | ||||||
|  | 	'enabled', | ||||||
| 	'tcp_forwarding', | 	'tcp_forwarding', | ||||||
| 	'udp_forwarding', | 	'udp_forwarding', | ||||||
| ]; | ]; | ||||||
| @@ -64,6 +63,17 @@ class Stream extends Model { | |||||||
| 				modify: function (qb) { | 				modify: function (qb) { | ||||||
| 					qb.where('user.is_deleted', 0); | 					qb.where('user.is_deleted', 0); | ||||||
| 				} | 				} | ||||||
|  | 			}, | ||||||
|  | 			certificate: { | ||||||
|  | 				relation:   Model.HasOneRelation, | ||||||
|  | 				modelClass: Certificate, | ||||||
|  | 				join:       { | ||||||
|  | 					from: 'stream.certificate_id', | ||||||
|  | 					to:   'certificate.id' | ||||||
|  | 				}, | ||||||
|  | 				modify: function (qb) { | ||||||
|  | 					qb.where('certificate.is_deleted', 0); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ | |||||||
| 		"node-rsa": "^1.0.8", | 		"node-rsa": "^1.0.8", | ||||||
| 		"objection": "3.0.1", | 		"objection": "3.0.1", | ||||||
| 		"path": "^0.12.7", | 		"path": "^0.12.7", | ||||||
|  | 		"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" | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ const apiValidator        = require('../../lib/validator/api'); | |||||||
| const internalCertificate = require('../../internal/certificate'); | const internalCertificate = require('../../internal/certificate'); | ||||||
| const schema              = require('../../schema'); | const schema              = require('../../schema'); | ||||||
|  |  | ||||||
| let router = express.Router({ | const router = express.Router({ | ||||||
| 	caseSensitive: true, | 	caseSensitive: true, | ||||||
| 	strict:        true, | 	strict:        true, | ||||||
| 	mergeParams:   true | 	mergeParams:   true | ||||||
| @@ -231,7 +231,7 @@ router | |||||||
|  */ |  */ | ||||||
| router | router | ||||||
| 	.route('/:certificate_id/download') | 	.route('/:certificate_id/download') | ||||||
| 	.options((req, res) => { | 	.options((_req, res) => { | ||||||
| 		res.sendStatus(204); | 		res.sendStatus(204); | ||||||
| 	}) | 	}) | ||||||
| 	.all(jwtdecode()) | 	.all(jwtdecode()) | ||||||
|   | |||||||
| @@ -181,7 +181,7 @@ router | |||||||
| 				return internalUser.setPassword(res.locals.access, payload); | 				return internalUser.setPassword(res.locals.access, payload); | ||||||
| 			}) | 			}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				res.status(201) | 				res.status(200) | ||||||
| 					.send(result); | 					.send(result); | ||||||
| 			}) | 			}) | ||||||
| 			.catch(next); | 			.catch(next); | ||||||
| @@ -212,7 +212,7 @@ router | |||||||
| 				return internalUser.setPermissions(res.locals.access, payload); | 				return internalUser.setPermissions(res.locals.access, payload); | ||||||
| 			}) | 			}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				res.status(201) | 				res.status(200) | ||||||
| 					.send(result); | 					.send(result); | ||||||
| 			}) | 			}) | ||||||
| 			.catch(next); | 			.catch(next); | ||||||
| @@ -238,7 +238,7 @@ router | |||||||
| 	.post((req, res, next) => { | 	.post((req, res, next) => { | ||||||
| 		internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) | 		internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				res.status(201) | 				res.status(200) | ||||||
| 					.send(result); | 					.send(result); | ||||||
| 			}) | 			}) | ||||||
| 			.catch(next); | 			.catch(next); | ||||||
|   | |||||||
| @@ -110,6 +110,11 @@ | |||||||
| 		"caching_enabled": { | 		"caching_enabled": { | ||||||
| 			"description": "Should we cache assets", | 			"description": "Should we cache assets", | ||||||
| 			"type": "boolean" | 			"type": "boolean" | ||||||
|  | 		}, | ||||||
|  | 		"email": { | ||||||
|  | 			"description": "Email address", | ||||||
|  | 			"type": "string", | ||||||
|  | 			"pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ | |||||||
| 					"type": "object" | 					"type": "object" | ||||||
| 				}, | 				}, | ||||||
| 				"letsencrypt_email": { | 				"letsencrypt_email": { | ||||||
| 					"type": "string" | 					"$ref": "../common.json#/properties/email" | ||||||
| 				}, | 				}, | ||||||
| 				"propagation_seconds": { | 				"propagation_seconds": { | ||||||
| 					"type": "integer", | 					"type": "integer", | ||||||
|   | |||||||
| @@ -22,8 +22,7 @@ | |||||||
| 		"enabled", | 		"enabled", | ||||||
| 		"locations", | 		"locations", | ||||||
| 		"hsts_enabled", | 		"hsts_enabled", | ||||||
| 		"hsts_subdomains", | 		"hsts_subdomains" | ||||||
| 		"certificate" |  | ||||||
| 	], | 	], | ||||||
| 	"additionalProperties": false, | 	"additionalProperties": false, | ||||||
| 	"properties": { | 	"properties": { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
| 	"type": "array", | 	"type": "array", | ||||||
| 	"description": "Proxy Hosts list", | 	"description": "Streams list", | ||||||
| 	"items": { | 	"items": { | ||||||
| 		"$ref": "./proxy-host-object.json" | 		"$ref": "./stream-object.json" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -53,8 +53,24 @@ | |||||||
| 		"enabled": { | 		"enabled": { | ||||||
| 			"$ref": "../common.json#/properties/enabled" | 			"$ref": "../common.json#/properties/enabled" | ||||||
| 		}, | 		}, | ||||||
|  | 		"certificate_id": { | ||||||
|  | 			"$ref": "../common.json#/properties/certificate_id" | ||||||
|  | 		}, | ||||||
| 		"meta": { | 		"meta": { | ||||||
| 			"type": "object" | 			"type": "object" | ||||||
|  | 		}, | ||||||
|  | 		"owner": { | ||||||
|  | 			"$ref": "./user-object.json" | ||||||
|  | 		}, | ||||||
|  | 		"certificate": { | ||||||
|  | 			"oneOf": [ | ||||||
|  | 				{ | ||||||
|  | 					"type": "null" | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					"$ref": "./certificate-object.json" | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,10 +5,9 @@ | |||||||
| 	"additionalProperties": false, | 	"additionalProperties": false, | ||||||
| 	"properties": { | 	"properties": { | ||||||
| 		"expires": { | 		"expires": { | ||||||
| 			"description": "Token Expiry Unix Time", | 			"description": "Token Expiry ISO Time String", | ||||||
| 			"example": 1566540249, | 			"example": "2025-02-04T20:40:46.340Z", | ||||||
| 			"minimum": 1, | 			"type": "string" | ||||||
| 			"type": "number" |  | ||||||
| 		}, | 		}, | ||||||
| 		"token": { | 		"token": { | ||||||
| 			"description": "JWT Token", | 			"description": "JWT Token", | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
| 			"description": "Expansions", | 			"description": "Expansions", | ||||||
| 			"schema": { | 			"schema": { | ||||||
| 				"type": "string", | 				"type": "string", | ||||||
| 				"enum": ["access_list", "owner", "certificate"] | 				"enum": ["owner", "certificate"] | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| @@ -40,7 +40,8 @@ | |||||||
| 										"nginx_online": true, | 										"nginx_online": true, | ||||||
| 										"nginx_err": null | 										"nginx_err": null | ||||||
| 									}, | 									}, | ||||||
| 									"enabled": true | 									"enabled": true, | ||||||
|  | 									"certificate_id": 0 | ||||||
| 								} | 								} | ||||||
| 							] | 							] | ||||||
| 						} | 						} | ||||||
|   | |||||||
| @@ -32,6 +32,9 @@ | |||||||
| 						"udp_forwarding": { | 						"udp_forwarding": { | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/udp_forwarding" | 							"$ref": "../../../components/stream-object.json#/properties/udp_forwarding" | ||||||
| 						}, | 						}, | ||||||
|  | 						"certificate_id": { | ||||||
|  | 							"$ref": "../../../components/stream-object.json#/properties/certificate_id" | ||||||
|  | 						}, | ||||||
| 						"meta": { | 						"meta": { | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/meta" | 							"$ref": "../../../components/stream-object.json#/properties/meta" | ||||||
| 						} | 						} | ||||||
| @@ -73,7 +76,8 @@ | |||||||
| 									"nickname": "Admin", | 									"nickname": "Admin", | ||||||
| 									"avatar": "", | 									"avatar": "", | ||||||
| 									"roles": ["admin"] | 									"roles": ["admin"] | ||||||
| 								} | 								}, | ||||||
|  | 								"certificate_id": 0 | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -40,7 +40,8 @@ | |||||||
| 									"nginx_online": true, | 									"nginx_online": true, | ||||||
| 									"nginx_err": null | 									"nginx_err": null | ||||||
| 								}, | 								}, | ||||||
| 								"enabled": true | 								"enabled": true, | ||||||
|  | 								"certificate_id": 0 | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -29,56 +29,26 @@ | |||||||
| 					"additionalProperties": false, | 					"additionalProperties": false, | ||||||
| 					"minProperties": 1, | 					"minProperties": 1, | ||||||
| 					"properties": { | 					"properties": { | ||||||
| 						"domain_names": { | 						"incoming_port": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/domain_names" | 							"$ref": "../../../../components/stream-object.json#/properties/incoming_port" | ||||||
| 						}, | 						}, | ||||||
| 						"forward_scheme": { | 						"forwarding_host": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme" | 							"$ref": "../../../../components/stream-object.json#/properties/forwarding_host" | ||||||
| 						}, | 						}, | ||||||
| 						"forward_host": { | 						"forwarding_port": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_host" | 							"$ref": "../../../../components/stream-object.json#/properties/forwarding_port" | ||||||
| 						}, | 						}, | ||||||
| 						"forward_port": { | 						"tcp_forwarding": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_port" | 							"$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding" | ||||||
|  | 						}, | ||||||
|  | 						"udp_forwarding": { | ||||||
|  | 							"$ref": "../../../../components/stream-object.json#/properties/udp_forwarding" | ||||||
| 						}, | 						}, | ||||||
| 						"certificate_id": { | 						"certificate_id": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id" | 							"$ref": "../../../../components/stream-object.json#/properties/certificate_id" | ||||||
| 						}, |  | ||||||
| 						"ssl_forced": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/ssl_forced" |  | ||||||
| 						}, |  | ||||||
| 						"hsts_enabled": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_enabled" |  | ||||||
| 						}, |  | ||||||
| 						"hsts_subdomains": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains" |  | ||||||
| 						}, |  | ||||||
| 						"http2_support": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support" |  | ||||||
| 						}, |  | ||||||
| 						"block_exploits": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/block_exploits" |  | ||||||
| 						}, |  | ||||||
| 						"caching_enabled": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/caching_enabled" |  | ||||||
| 						}, |  | ||||||
| 						"allow_websocket_upgrade": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/allow_websocket_upgrade" |  | ||||||
| 						}, |  | ||||||
| 						"access_list_id": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/access_list_id" |  | ||||||
| 						}, |  | ||||||
| 						"advanced_config": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/advanced_config" |  | ||||||
| 						}, |  | ||||||
| 						"enabled": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/enabled" |  | ||||||
| 						}, | 						}, | ||||||
| 						"meta": { | 						"meta": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/meta" | 							"$ref": "../../../../components/stream-object.json#/properties/meta" | ||||||
| 						}, |  | ||||||
| 						"locations": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/locations" |  | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -94,42 +64,32 @@ | |||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"id": 1, | 								"id": 1, | ||||||
| 								"created_on": "2024-10-08T23:23:03.000Z", | 								"created_on": "2024-10-09T02:33:45.000Z", | ||||||
| 								"modified_on": "2024-10-08T23:26:37.000Z", | 								"modified_on": "2024-10-09T02:33:45.000Z", | ||||||
| 								"owner_user_id": 1, | 								"owner_user_id": 1, | ||||||
| 								"domain_names": ["test.example.com"], | 								"incoming_port": 9090, | ||||||
| 								"forward_host": "192.168.0.10", | 								"forwarding_host": "router.internal", | ||||||
| 								"forward_port": 8989, | 								"forwarding_port": 80, | ||||||
| 								"access_list_id": 0, | 								"tcp_forwarding": true, | ||||||
| 								"certificate_id": 0, | 								"udp_forwarding": false, | ||||||
| 								"ssl_forced": false, |  | ||||||
| 								"caching_enabled": false, |  | ||||||
| 								"block_exploits": false, |  | ||||||
| 								"advanced_config": "", |  | ||||||
| 								"meta": { | 								"meta": { | ||||||
| 									"nginx_online": true, | 									"nginx_online": true, | ||||||
| 									"nginx_err": null | 									"nginx_err": null | ||||||
| 								}, | 								}, | ||||||
| 								"allow_websocket_upgrade": false, |  | ||||||
| 								"http2_support": false, |  | ||||||
| 								"forward_scheme": "http", |  | ||||||
| 								"enabled": true, | 								"enabled": true, | ||||||
| 								"hsts_enabled": false, |  | ||||||
| 								"hsts_subdomains": false, |  | ||||||
| 								"owner": { | 								"owner": { | ||||||
| 									"id": 1, | 									"id": 1, | ||||||
| 									"created_on": "2024-10-07T22:43:55.000Z", | 									"created_on": "2024-10-09T02:33:16.000Z", | ||||||
| 									"modified_on": "2024-10-08T12:52:54.000Z", | 									"modified_on": "2024-10-09T02:33:16.000Z", | ||||||
| 									"is_deleted": false, | 									"is_deleted": false, | ||||||
| 									"is_disabled": false, | 									"is_disabled": false, | ||||||
| 									"email": "admin@example.com", | 									"email": "admin@example.com", | ||||||
| 									"name": "Administrator", | 									"name": "Administrator", | ||||||
| 									"nickname": "some guy", | 									"nickname": "Admin", | ||||||
| 									"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", | 									"avatar": "", | ||||||
| 									"roles": ["admin"] | 									"roles": ["admin"] | ||||||
| 								}, | 								}, | ||||||
| 								"certificate": null, | 								"certificate_id": 0 | ||||||
| 								"access_list": null |  | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
| 					"examples": { | 					"examples": { | ||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"expires": 1566540510, | 								"expires": "2025-02-04T20:40:46.340Z", | ||||||
| 								"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | 								"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ | |||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"result": { | 								"result": { | ||||||
| 									"expires": 1566540510, | 									"expires": "2025-02-04T20:40:46.340Z", | ||||||
| 									"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | 									"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | ||||||
| 								} | 								} | ||||||
| 							} | 							} | ||||||
|   | |||||||
| @@ -9,6 +9,15 @@ | |||||||
| 			"url": "http://127.0.0.1:81/api" | 			"url": "http://127.0.0.1:81/api" | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
|  | 	"components": { | ||||||
|  | 		"securitySchemes": { | ||||||
|  | 			"bearerAuth": { | ||||||
|  | 				"type": "http", | ||||||
|  | 				"scheme": "bearer", | ||||||
|  | 				"bearerFormat": "JWT" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	"paths": { | 	"paths": { | ||||||
| 		"/": { | 		"/": { | ||||||
| 			"get": { | 			"get": { | ||||||
|   | |||||||
| @@ -15,18 +15,18 @@ const certbot             = require('./lib/certbot'); | |||||||
| const setupDefaultUser = () => { | const setupDefaultUser = () => { | ||||||
| 	return userModel | 	return userModel | ||||||
| 		.query() | 		.query() | ||||||
| 		.select(userModel.raw('COUNT(`id`) as `count`')) | 		.select('id', ) | ||||||
| 		.where('is_deleted', 0) | 		.where('is_deleted', 0) | ||||||
| 		.first() | 		.first() | ||||||
| 		.then((row) => { | 		.then((row) => { | ||||||
| 			if (!row.count) { | 			if (!row || !row.id) { | ||||||
| 				// Create a new user and set password | 				// Create a new user and set password | ||||||
| 				let email    = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com'; | 				const email    = (process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com').toLowerCase(); | ||||||
| 				let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; | 				const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; | ||||||
| 				 |  | ||||||
| 				logger.info('Creating a new user: ' + email + ' with password: ' + password); |  | ||||||
|  |  | ||||||
| 				let data = { | 				logger.info(`Creating a new user: ${email} with password: ${password}`); | ||||||
|  |  | ||||||
|  | 				const data = { | ||||||
| 					is_deleted: 0, | 					is_deleted: 0, | ||||||
| 					email:      email, | 					email:      email, | ||||||
| 					name:       'Administrator', | 					name:       'Administrator', | ||||||
| @@ -77,11 +77,11 @@ const setupDefaultUser = () => { | |||||||
| const setupDefaultSettings = () => { | const setupDefaultSettings = () => { | ||||||
| 	return settingModel | 	return settingModel | ||||||
| 		.query() | 		.query() | ||||||
| 		.select(settingModel.raw('COUNT(`id`) as `count`')) | 		.select('id') | ||||||
| 		.where({id: 'default-site'}) | 		.where({id: 'default-site'}) | ||||||
| 		.first() | 		.first() | ||||||
| 		.then((row) => { | 		.then((row) => { | ||||||
| 			if (!row.count) { | 			if (!row || !row.id) { | ||||||
| 				settingModel | 				settingModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.insert({ | 					.insert({ | ||||||
| @@ -113,20 +113,20 @@ const setupCertbotPlugins = () => { | |||||||
| 		.andWhere('provider', 'letsencrypt') | 		.andWhere('provider', 'letsencrypt') | ||||||
| 		.then((certificates) => { | 		.then((certificates) => { | ||||||
| 			if (certificates && certificates.length) { | 			if (certificates && certificates.length) { | ||||||
| 				let plugins  = []; | 				const plugins  = []; | ||||||
| 				let promises = []; | 				const promises = []; | ||||||
|  |  | ||||||
| 				certificates.map(function (certificate) { | 				certificates.map((certificate) => { | ||||||
| 					if (certificate.meta && certificate.meta.dns_challenge === true) { | 					if (certificate.meta && certificate.meta.dns_challenge === true) { | ||||||
| 						if (plugins.indexOf(certificate.meta.dns_provider) === -1) { | 						if (plugins.indexOf(certificate.meta.dns_provider) === -1) { | ||||||
| 							plugins.push(certificate.meta.dns_provider); | 							plugins.push(certificate.meta.dns_provider); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						// Make sure credentials file exists | 						// Make sure credentials file exists | ||||||
| 						const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id; | 						const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; | ||||||
| 						// Escape single quotes and backslashes | 						// Escape single quotes and backslashes | ||||||
| 						const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); | 						const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); | ||||||
| 						const credentials_cmd    = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }'; | 						const credentials_cmd    = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; | ||||||
| 						promises.push(utils.exec(credentials_cmd)); | 						promises.push(utils.exec(credentials_cmd)); | ||||||
| 					} | 					} | ||||||
| 				}); | 				}); | ||||||
| @@ -136,7 +136,7 @@ const setupCertbotPlugins = () => { | |||||||
| 						if (promises.length) { | 						if (promises.length) { | ||||||
| 							return Promise.all(promises) | 							return Promise.all(promises) | ||||||
| 								.then(() => { | 								.then(() => { | ||||||
| 									logger.info('Added Certbot plugins ' + plugins.join(', ')); | 									logger.info(`Added Certbot plugins ${plugins.join(', ')}`); | ||||||
| 								}); | 								}); | ||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
| @@ -165,9 +165,7 @@ const setupLogrotation = () => { | |||||||
| 	return runLogrotate(); | 	return runLogrotate(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = function () { | module.exports = () => setupDefaultUser() | ||||||
| 	return setupDefaultUser() | 	.then(setupDefaultSettings) | ||||||
| 		.then(setupDefaultSettings) | 	.then(setupCertbotPlugins) | ||||||
| 		.then(setupCertbotPlugins) | 	.then(setupLogrotation); | ||||||
| 		.then(setupLogrotation); |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| {% if certificate.provider == "letsencrypt" %} | {% if certificate.provider == "letsencrypt" %} | ||||||
|   # Let's Encrypt SSL |   # Let's Encrypt SSL | ||||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; |   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||||
|  |   include conf.d/include/ssl-cache.conf; | ||||||
|   include conf.d/include/ssl-ciphers.conf; |   include conf.d/include/ssl-ciphers.conf; | ||||||
|   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; |   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; |   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								backend/templates/_certificates_stream.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/templates/_certificates_stream.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | {% if certificate and certificate_id > 0 %} | ||||||
|  | {% if certificate.provider == "letsencrypt" %} | ||||||
|  |   # Let's Encrypt SSL | ||||||
|  |   include conf.d/include/ssl-cache-stream.conf; | ||||||
|  |   include conf.d/include/ssl-ciphers.conf; | ||||||
|  |   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|  |   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; | ||||||
|  | {%- else %} | ||||||
|  |   # Custom SSL | ||||||
|  |   ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|  |   ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem; | ||||||
|  | {%- endif -%} | ||||||
|  | {%- endif -%} | ||||||
| @@ -22,5 +22,7 @@ server { | |||||||
|   } |   } | ||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
|  |   # Custom | ||||||
|  |   include /data/nginx/custom/server_dead[.]conf; | ||||||
| } | } | ||||||
| {% endif %} | {% endif %} | ||||||
|   | |||||||
| @@ -5,12 +5,10 @@ | |||||||
| {% if enabled %} | {% if enabled %} | ||||||
| {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | ||||||
| server { | server { | ||||||
|   listen {{ incoming_port }}; |   listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %}; | ||||||
| {% if ipv6 -%} |   {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %}; | ||||||
|   listen [::]:{{ incoming_port }}; |  | ||||||
| {% else -%} |   {%- include "_certificates_stream.conf" %} | ||||||
|   #listen [::]:{{ incoming_port }}; |  | ||||||
| {% endif %} |  | ||||||
|  |  | ||||||
|   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; |   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; | ||||||
|  |  | ||||||
| @@ -19,14 +17,12 @@ server { | |||||||
|   include /data/nginx/custom/server_stream_tcp[.]conf; |   include /data/nginx/custom/server_stream_tcp[.]conf; | ||||||
| } | } | ||||||
| {% endif %} | {% endif %} | ||||||
| {% if udp_forwarding == 1 or udp_forwarding == true %} |  | ||||||
|  | {% if udp_forwarding == 1 or udp_forwarding == true -%} | ||||||
| server { | server { | ||||||
|   listen {{ incoming_port }} udp; |   listen {{ incoming_port }} udp; | ||||||
| {% if ipv6 -%} |   {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp; | ||||||
|   listen [::]:{{ incoming_port }} udp; |  | ||||||
| {% else -%} |  | ||||||
|   #listen [::]:{{ incoming_port }} udp; |  | ||||||
| {% endif %} |  | ||||||
|   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; |   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; | ||||||
|  |  | ||||||
|   # Custom |   # Custom | ||||||
|   | |||||||
| @@ -492,9 +492,9 @@ boxen@^4.2.0: | |||||||
|     widest-line "^3.1.0" |     widest-line "^3.1.0" | ||||||
|  |  | ||||||
| brace-expansion@^1.1.7: | brace-expansion@^1.1.7: | ||||||
|   version "1.1.11" |   version "1.1.12" | ||||||
|   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" |   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" | ||||||
|   integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== |   integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== | ||||||
|   dependencies: |   dependencies: | ||||||
|     balanced-match "^1.0.0" |     balanced-match "^1.0.0" | ||||||
|     concat-map "0.0.1" |     concat-map "0.0.1" | ||||||
| @@ -830,9 +830,9 @@ crc32-stream@^4.0.2: | |||||||
|     readable-stream "^3.4.0" |     readable-stream "^3.4.0" | ||||||
|  |  | ||||||
| cross-spawn@^7.0.2: | cross-spawn@^7.0.2: | ||||||
|   version "7.0.3" |   version "7.0.6" | ||||||
|   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" |   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" | ||||||
|   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== |   integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== | ||||||
|   dependencies: |   dependencies: | ||||||
|     path-key "^3.1.0" |     path-key "^3.1.0" | ||||||
|     shebang-command "^2.0.0" |     shebang-command "^2.0.0" | ||||||
| @@ -2735,11 +2735,67 @@ path@^0.12.7: | |||||||
|     process "^0.11.1" |     process "^0.11.1" | ||||||
|     util "^0.10.3" |     util "^0.10.3" | ||||||
|  |  | ||||||
|  | pg-cloudflare@^1.1.1: | ||||||
|  |   version "1.1.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" | ||||||
|  |   integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== | ||||||
|  |  | ||||||
| pg-connection-string@2.5.0: | pg-connection-string@2.5.0: | ||||||
|   version "2.5.0" |   version "2.5.0" | ||||||
|   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" |   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" | ||||||
|   integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== |   integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== | ||||||
|  |  | ||||||
|  | pg-connection-string@^2.7.0: | ||||||
|  |   version "2.7.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" | ||||||
|  |   integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== | ||||||
|  |  | ||||||
|  | pg-int8@1.0.1: | ||||||
|  |   version "1.0.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" | ||||||
|  |   integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== | ||||||
|  |  | ||||||
|  | pg-pool@^3.7.0: | ||||||
|  |   version "3.7.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" | ||||||
|  |   integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== | ||||||
|  |  | ||||||
|  | pg-protocol@^1.7.0: | ||||||
|  |   version "1.7.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" | ||||||
|  |   integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== | ||||||
|  |  | ||||||
|  | pg-types@^2.1.0: | ||||||
|  |   version "2.2.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" | ||||||
|  |   integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== | ||||||
|  |   dependencies: | ||||||
|  |     pg-int8 "1.0.1" | ||||||
|  |     postgres-array "~2.0.0" | ||||||
|  |     postgres-bytea "~1.0.0" | ||||||
|  |     postgres-date "~1.0.4" | ||||||
|  |     postgres-interval "^1.1.0" | ||||||
|  |  | ||||||
|  | pg@^8.13.1: | ||||||
|  |   version "8.13.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080" | ||||||
|  |   integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ== | ||||||
|  |   dependencies: | ||||||
|  |     pg-connection-string "^2.7.0" | ||||||
|  |     pg-pool "^3.7.0" | ||||||
|  |     pg-protocol "^1.7.0" | ||||||
|  |     pg-types "^2.1.0" | ||||||
|  |     pgpass "1.x" | ||||||
|  |   optionalDependencies: | ||||||
|  |     pg-cloudflare "^1.1.1" | ||||||
|  |  | ||||||
|  | pgpass@1.x: | ||||||
|  |   version "1.0.5" | ||||||
|  |   resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" | ||||||
|  |   integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== | ||||||
|  |   dependencies: | ||||||
|  |     split2 "^4.1.0" | ||||||
|  |  | ||||||
| picomatch@^2.0.4, picomatch@^2.2.1: | picomatch@^2.0.4, picomatch@^2.2.1: | ||||||
|   version "2.2.2" |   version "2.2.2" | ||||||
|   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" |   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" | ||||||
| @@ -2758,6 +2814,28 @@ pkg-conf@^2.1.0: | |||||||
|     find-up "^2.0.0" |     find-up "^2.0.0" | ||||||
|     load-json-file "^4.0.0" |     load-json-file "^4.0.0" | ||||||
|  |  | ||||||
|  | postgres-array@~2.0.0: | ||||||
|  |   version "2.0.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" | ||||||
|  |   integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== | ||||||
|  |  | ||||||
|  | postgres-bytea@~1.0.0: | ||||||
|  |   version "1.0.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" | ||||||
|  |   integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== | ||||||
|  |  | ||||||
|  | postgres-date@~1.0.4: | ||||||
|  |   version "1.0.7" | ||||||
|  |   resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" | ||||||
|  |   integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== | ||||||
|  |  | ||||||
|  | postgres-interval@^1.1.0: | ||||||
|  |   version "1.2.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" | ||||||
|  |   integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== | ||||||
|  |   dependencies: | ||||||
|  |     xtend "^4.0.0" | ||||||
|  |  | ||||||
| prelude-ls@^1.2.1: | prelude-ls@^1.2.1: | ||||||
|   version "1.2.1" |   version "1.2.1" | ||||||
|   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" |   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" | ||||||
| @@ -3194,6 +3272,11 @@ socks@^2.6.2: | |||||||
|     ip "^2.0.0" |     ip "^2.0.0" | ||||||
|     smart-buffer "^4.2.0" |     smart-buffer "^4.2.0" | ||||||
|  |  | ||||||
|  | split2@^4.1.0: | ||||||
|  |   version "4.2.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" | ||||||
|  |   integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== | ||||||
|  |  | ||||||
| sprintf-js@~1.0.2: | sprintf-js@~1.0.2: | ||||||
|   version "1.0.3" |   version "1.0.3" | ||||||
|   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" |   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" | ||||||
| @@ -3665,6 +3748,11 @@ xdg-basedir@^4.0.0: | |||||||
|   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" |   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" | ||||||
|   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== |   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== | ||||||
|  |  | ||||||
|  | xtend@^4.0.0: | ||||||
|  |   version "4.0.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" | ||||||
|  |   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== | ||||||
|  |  | ||||||
| y18n@^4.0.0: | y18n@^4.0.0: | ||||||
|   version "4.0.1" |   version "4.0.1" | ||||||
|   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" |   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								docker/ci.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docker/ci.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | 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 | ||||||
							
								
								
									
										
											BIN
										
									
								
								docker/ci/postgres/authentik.sql.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docker/ci/postgres/authentik.sql.gz
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -29,7 +29,8 @@ 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 | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ services: | |||||||
|       MYSQL_DATABASE: 'npm' |       MYSQL_DATABASE: 'npm' | ||||||
|       MYSQL_USER: 'npm' |       MYSQL_USER: 'npm' | ||||||
|       MYSQL_PASSWORD: 'npmpass' |       MYSQL_PASSWORD: 'npmpass' | ||||||
|  |       MARIADB_AUTO_UPGRADE: '1' | ||||||
|     volumes: |     volumes: | ||||||
|       - mysql_vol:/var/lib/mysql |       - mysql_vol:/var/lib/mysql | ||||||
|     networks: |     networks: | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								docker/docker-compose.ci.postgres.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								docker/docker-compose.ci.postgres.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | # 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: | ||||||
| @@ -22,6 +22,10 @@ services: | |||||||
|       test: ["CMD", "/usr/bin/check-health"] |       test: ["CMD", "/usr/bin/check-health"] | ||||||
|       interval: 10s |       interval: 10s | ||||||
|       timeout: 3s |       timeout: 3s | ||||||
|  |     expose: | ||||||
|  |       - '80-81/tcp' | ||||||
|  |       - '443/tcp' | ||||||
|  |       - '1500-1503/tcp' | ||||||
|     networks: |     networks: | ||||||
|       fulltest: |       fulltest: | ||||||
|         aliases: |         aliases: | ||||||
| @@ -40,7 +44,7 @@ services: | |||||||
|           - ca.internal |           - ca.internal | ||||||
|  |  | ||||||
|   pdns: |   pdns: | ||||||
|     image: pschiffe/pdns-mysql |     image: pschiffe/pdns-mysql:4.8 | ||||||
|     volumes: |     volumes: | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     environment: |     environment: | ||||||
| @@ -97,7 +101,7 @@ services: | |||||||
|       HTTP_PROXY: 'squid:3128' |       HTTP_PROXY: 'squid:3128' | ||||||
|       HTTPS_PROXY: 'squid:3128' |       HTTPS_PROXY: 'squid:3128' | ||||||
|     volumes: |     volumes: | ||||||
|       - 'cypress_logs:/results' |       - 'cypress_logs:/test/results' | ||||||
|       - './dev/resolv.conf:/etc/resolv.conf:ro' |       - './dev/resolv.conf:/etc/resolv.conf:ro' | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     command: cypress run --browser chrome --config-file=cypress/config/ci.js |     command: cypress run --browser chrome --config-file=cypress/config/ci.js | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ | |||||||
| services: | services: | ||||||
|  |  | ||||||
|   fullstack: |   fullstack: | ||||||
|     image: nginxproxymanager:dev |     image: npm2dev:core | ||||||
|     container_name: npm_core |     container_name: npm2dev.core | ||||||
|     build: |     build: | ||||||
|       context: ./ |       context: ./ | ||||||
|       dockerfile: ./dev/Dockerfile |       dockerfile: ./dev/Dockerfile | ||||||
| @@ -26,11 +26,17 @@ 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: | ||||||
| @@ -49,11 +55,15 @@ 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: npm_db |     container_name: npm2dev.db | ||||||
|     ports: |     ports: | ||||||
|       - 33306:3306 |       - 33306:3306 | ||||||
|     networks: |     networks: | ||||||
| @@ -66,8 +76,22 @@ 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' | ||||||
| @@ -78,6 +102,7 @@ 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: | ||||||
| @@ -85,7 +110,7 @@ services: | |||||||
|  |  | ||||||
|   swagger: |   swagger: | ||||||
|     image: swaggerapi/swagger-ui:latest |     image: swaggerapi/swagger-ui:latest | ||||||
|     container_name: npm_swagger |     container_name: npm2dev.swagger | ||||||
|     ports: |     ports: | ||||||
|       - 3082:80 |       - 3082:80 | ||||||
|     environment: |     environment: | ||||||
| @@ -96,7 +121,7 @@ services: | |||||||
|  |  | ||||||
|   squid: |   squid: | ||||||
|     image: ubuntu/squid |     image: ubuntu/squid | ||||||
|     container_name: npm_squid |     container_name: npm2dev.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' | ||||||
| @@ -107,7 +132,8 @@ services: | |||||||
|       - 8128:3128 |       - 8128:3128 | ||||||
|  |  | ||||||
|   pdns: |   pdns: | ||||||
|     image: pschiffe/pdns-mysql |     image: pschiffe/pdns-mysql:4.8 | ||||||
|  |     container_name: npm2dev.pdns | ||||||
|     volumes: |     volumes: | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     environment: |     environment: | ||||||
| @@ -136,6 +162,7 @@ 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' | ||||||
| @@ -149,7 +176,8 @@ services: | |||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|  |  | ||||||
|   cypress: |   cypress: | ||||||
|     image: "npm_dev_cypress" |     image: npm2dev:cypress | ||||||
|  |     container_name: npm2dev.cypress | ||||||
|     build: |     build: | ||||||
|       context: ../ |       context: ../ | ||||||
|       dockerfile: test/cypress/Dockerfile |       dockerfile: test/cypress/Dockerfile | ||||||
| @@ -164,16 +192,77 @@ 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: npm_core_data |     name: npm2dev_core_data | ||||||
|   le_data: |   le_data: | ||||||
|     name: npm_le_data |     name: npm2dev_le_data | ||||||
|   db_data: |   db_data: | ||||||
|     name: npm_db_data |     name: npm2dev_db_data | ||||||
|   pdns_mysql: |   pdns_mysql: | ||||||
|     name: npm_pdns_mysql |     name: npnpm2dev_pdns_mysql | ||||||
|  |   psql_data: | ||||||
|  |     name: npm2dev_psql_data | ||||||
|  |   redis_data: | ||||||
|  |     name: npm2dev_redis_data | ||||||
|  |  | ||||||
| networks: | networks: | ||||||
|   nginx_proxy_manager: |   nginx_proxy_manager: | ||||||
|     name: npm_network |     name: npm2dev_network | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ { | location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ { | ||||||
| 	if_modified_since off; | 	if_modified_since off; | ||||||
|  |  | ||||||
| 	# use the public cache | 	# use the public cache | ||||||
|   | |||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | ssl_session_timeout 5m; | ||||||
|  | ssl_session_cache shared:SSL_stream:50m; | ||||||
							
								
								
									
										2
									
								
								docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ssl_session_timeout 5m; | ||||||
|  | ssl_session_cache shared:SSL:50m; | ||||||
| @@ -1,6 +1,3 @@ | |||||||
| ssl_session_timeout 5m; |  | ||||||
| ssl_session_cache shared:SSL:50m; |  | ||||||
|  |  | ||||||
| # intermediate configuration. tweak to your needs. | # intermediate configuration. tweak to your needs. | ||||||
| ssl_protocols TLSv1.2 TLSv1.3; | ssl_protocols TLSv1.2 TLSv1.3; | ||||||
| ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; | ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; | ||||||
|   | |||||||
| @@ -8,21 +8,53 @@ log_info 'Setting ownership ...' | |||||||
| # root | # root | ||||||
| chown root /tmp/nginx | chown root /tmp/nginx | ||||||
|  |  | ||||||
| # npm user and group | locations=( | ||||||
| chown -R "$PUID:$PGID" /data | 	"/data" | ||||||
| chown -R "$PUID:$PGID" /etc/letsencrypt | 	"/etc/letsencrypt" | ||||||
| chown -R "$PUID:$PGID" /run/nginx | 	"/run/nginx" | ||||||
| chown -R "$PUID:$PGID" /tmp/nginx | 	"/tmp/nginx" | ||||||
| chown -R "$PUID:$PGID" /var/cache/nginx | 	"/var/cache/nginx" | ||||||
| chown -R "$PUID:$PGID" /var/lib/logrotate | 	"/var/lib/logrotate" | ||||||
| chown -R "$PUID:$PGID" /var/lib/nginx | 	"/var/lib/nginx" | ||||||
| chown -R "$PUID:$PGID" /var/log/nginx | 	"/var/log/nginx" | ||||||
|  | 	"/etc/nginx/nginx" | ||||||
|  | 	"/etc/nginx/nginx.conf" | ||||||
|  | 	"/etc/nginx/conf.d" | ||||||
|  | ) | ||||||
|  |  | ||||||
| # Don't chown entire /etc/nginx folder as this causes crashes on some systems | chownit() { | ||||||
| chown -R "$PUID:$PGID" /etc/nginx/nginx | 	local dir="$1" | ||||||
| chown -R "$PUID:$PGID" /etc/nginx/nginx.conf | 	local recursive="${2:-true}" | ||||||
| chown -R "$PUID:$PGID" /etc/nginx/conf.d |  | ||||||
|  |  | ||||||
| # Prevents errors when installing python certbot plugins when non-root | 	local have | ||||||
| chown "$PUID:$PGID" /opt/certbot /opt/certbot/bin | 	have="$(stat -c '%u:%g' "$dir")" | ||||||
| find /opt/certbot/lib/python*/site-packages -not -user "$PUID" -execdir chown "$PUID:$PGID" {} \+ | 	echo "- $dir ... " | ||||||
|  |  | ||||||
|  | 	if [ "$have" != "$PUID:$PGID" ]; then | ||||||
|  | 		if [ "$recursive" = 'true' ] && [ -d "$dir" ]; then | ||||||
|  | 			chown -R "$PUID:$PGID" "$dir" | ||||||
|  | 		else | ||||||
|  | 			chown "$PUID:$PGID" "$dir" | ||||||
|  | 		fi | ||||||
|  | 		echo "    DONE" | ||||||
|  | 	else | ||||||
|  | 		echo "    SKIPPED" | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  |  | ||||||
|  | for loc in "${locations[@]}"; do | ||||||
|  | 	chownit "$loc" | ||||||
|  | done | ||||||
|  |  | ||||||
|  | if [ "$(is_true "${SKIP_CERTBOT_OWNERSHIP:-}")" = '1' ]; then | ||||||
|  | 	log_info 'Skipping ownership change of certbot directories' | ||||||
|  | else | ||||||
|  | 	log_info 'Changing ownership of certbot directories, this may take some time ...' | ||||||
|  | 	chownit "/opt/certbot" false | ||||||
|  | 	chownit "/opt/certbot/bin" false | ||||||
|  |  | ||||||
|  | 	# Handle all site-packages directories efficiently | ||||||
|  | 	find /opt/certbot/lib -type d -name "site-packages" | while read -r SITE_PACKAGES_DIR; do | ||||||
|  | 		chownit "$SITE_PACKAGES_DIR" | ||||||
|  | 	done | ||||||
|  | fi | ||||||
|   | |||||||
| @@ -5,12 +5,9 @@ set -e | |||||||
|  |  | ||||||
| log_info 'Dynamic resolvers ...' | log_info 'Dynamic resolvers ...' | ||||||
|  |  | ||||||
| DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') |  | ||||||
|  |  | ||||||
| # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` | # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` | ||||||
| # thanks @tfmm | # thanks @tfmm | ||||||
| if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; | if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then | ||||||
| then |  | ||||||
| 	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf | 	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf | ||||||
| else | else | ||||||
| 	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf | 	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf | ||||||
|   | |||||||
| @@ -8,14 +8,11 @@ set -e | |||||||
|  |  | ||||||
| log_info 'IPv6 ...' | log_info 'IPv6 ...' | ||||||
|  |  | ||||||
| # Lowercase |  | ||||||
| DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') |  | ||||||
|  |  | ||||||
| process_folder () { | process_folder () { | ||||||
| 	FILES=$(find "$1" -type f -name "*.conf") | 	FILES=$(find "$1" -type f -name "*.conf") | ||||||
| 	SED_REGEX= | 	SED_REGEX= | ||||||
|  |  | ||||||
| 	if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then | 	if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then | ||||||
| 		# IPV6 is disabled | 		# IPV6 is disabled | ||||||
| 		echo "Disabling IPV6 in hosts in: $1" | 		echo "Disabling IPV6 in hosts in: $1" | ||||||
| 		SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g' | 		SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g' | ||||||
|   | |||||||
| @@ -56,3 +56,13 @@ get_group_id () { | |||||||
| 		getent group "$1" | cut -d: -f3 | 		getent group "$1" | cut -d: -f3 | ||||||
| 	fi | 	fi | ||||||
| } | } | ||||||
|  |  | ||||||
|  | # param $1: value | ||||||
|  | is_true () { | ||||||
|  | 	VAL=$(echo "${1:-}" | tr '[:upper:]' '[:lower:]') | ||||||
|  | 	if [ "$VAL" == 'true' ] || [ "$VAL" == 'on' ] || [ "$VAL" == '1' ] || [ "$VAL" == 'yes' ]; then | ||||||
|  | 		echo '1' | ||||||
|  | 	else | ||||||
|  | 		echo '0' | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ BLUE='\E[1;34m' | |||||||
| GREEN='\E[1;32m' | GREEN='\E[1;32m' | ||||||
| RESET='\E[0m' | RESET='\E[0m' | ||||||
|  |  | ||||||
| S6_OVERLAY_VERSION=3.1.5.0 | S6_OVERLAY_VERSION=3.2.1.0 | ||||||
| TARGETPLATFORM=${1:-linux/amd64} | TARGETPLATFORM=${1:-linux/amd64} | ||||||
|  |  | ||||||
| # Determine the correct binary file for the architecture given | # Determine the correct binary file for the architecture given | ||||||
|   | |||||||
| @@ -50,7 +50,6 @@ networks: | |||||||
| Let's look at a Portainer example: | Let's look at a Portainer example: | ||||||
|  |  | ||||||
| ```yml | ```yml | ||||||
| version: '3.8' |  | ||||||
| services: | services: | ||||||
|  |  | ||||||
|   portainer: |   portainer: | ||||||
| @@ -92,8 +91,6 @@ 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" | ||||||
| @@ -164,6 +161,14 @@ The easy fix is to add a Docker environment variable to the Nginx Proxy Manager | |||||||
|       DISABLE_IPV6: 'true' |       DISABLE_IPV6: 'true' | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | ## Disabling IP Ranges Fetch | ||||||
|  |  | ||||||
|  | By default, NPM fetches IP ranges from CloudFront and Cloudflare during application startup. In environments with limited internet access or to speed up container startup, this fetch can be disabled: | ||||||
|  |  | ||||||
|  | ```yml | ||||||
|  |     environment: | ||||||
|  |       IP_RANGES_FETCH_ENABLED: 'false' | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ## Custom Nginx Configurations | ## Custom Nginx Configurations | ||||||
|  |  | ||||||
| @@ -184,6 +189,7 @@ 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,7 +9,6 @@ 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' | ||||||
| @@ -22,8 +21,7 @@ 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 | ||||||
|  |  | ||||||
|     # Uncomment the next line if you uncomment anything in the section |     #environment: | ||||||
|     # 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" | ||||||
| @@ -55,7 +53,6 @@ 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' | ||||||
| @@ -101,6 +98,53 @@ 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,6 +12,7 @@ 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.7" |   version "3.3.8" | ||||||
|   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" |   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" | ||||||
|   integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== |   integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== | ||||||
|  |  | ||||||
| oniguruma-to-js@0.4.3: | oniguruma-to-js@0.4.3: | ||||||
|   version "0.4.3" |   version "0.4.3" | ||||||
| @@ -1065,9 +1065,9 @@ vfile@^6.0.0: | |||||||
|     vfile-message "^4.0.0" |     vfile-message "^4.0.0" | ||||||
|  |  | ||||||
| vite@^5.4.8: | vite@^5.4.8: | ||||||
|   version "5.4.8" |   version "5.4.21" | ||||||
|   resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8" |   resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" | ||||||
|   integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ== |   integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== | ||||||
|   dependencies: |   dependencies: | ||||||
|     esbuild "^0.21.3" |     esbuild "^0.21.3" | ||||||
|     postcss "^8.4.43" |     postcss "^8.4.43" | ||||||
|   | |||||||
| @@ -4,444 +4,438 @@ const Tokens   = require('./tokens'); | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * @param {String} route | 	 * @param {String} route | ||||||
|      * @param {Object} [options] | 	 * @param {Object} [options] | ||||||
|      * @returns {Boolean} | 	 * @returns {Boolean} | ||||||
|      */ | 	 */ | ||||||
|     navigate: function (route, options) { | 	navigate: function (route, options) { | ||||||
|         options = options || {}; | 		options = options || {}; | ||||||
|         Backbone.history.navigate(route.toString(), options); | 		Backbone.history.navigate(route.toString(), options); | ||||||
|         return true; | 		return true; | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Login | 	 * Login | ||||||
|      */ | 	 */ | ||||||
|     showLogin: function () { | 	showLogin: function () { | ||||||
|         window.location = '/login'; | 		window.location = '/login'; | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Users | 	 * Users | ||||||
|      */ | 	 */ | ||||||
|     showUsers: function () { | 	showUsers: function () { | ||||||
|         let controller = this; | 		const controller = this; | ||||||
|         if (Cache.User.isAdmin()) { | 		if (Cache.User.isAdmin()) { | ||||||
|             require(['./main', './users/main'], (App, View) => { | 			require(['./main', './users/main'], (App, View) => { | ||||||
|                 controller.navigate('/users'); | 				controller.navigate('/users'); | ||||||
|                 App.UI.showAppContent(new View()); | 				App.UI.showAppContent(new View()); | ||||||
|             }); | 			}); | ||||||
|         } else { | 		} else { | ||||||
|             this.showDashboard(); | 			this.showDashboard(); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * User Form | 	 * User Form | ||||||
|      * | 	 * | ||||||
|      * @param [model] | 	 * @param [model] | ||||||
|      */ | 	 */ | ||||||
|     showUserForm: function (model) { | 	showUserForm: function (model) { | ||||||
|         if (Cache.User.isAdmin()) { | 		if (Cache.User.isAdmin()) { | ||||||
|             require(['./main', './user/form'], function (App, View) { | 			require(['./main', './user/form'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * User Permissions Form | 	 * User Permissions Form | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showUserPermissions: function (model) { | 	showUserPermissions: function (model) { | ||||||
|         if (Cache.User.isAdmin()) { | 		if (Cache.User.isAdmin()) { | ||||||
|             require(['./main', './user/permissions'], function (App, View) { | 			require(['./main', './user/permissions'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * User Password Form | 	 * User Password Form | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showUserPasswordForm: function (model) { | 	showUserPasswordForm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { | 		if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { | ||||||
|             require(['./main', './user/password'], function (App, View) { | 			require(['./main', './user/password'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * User Delete Confirm | 	 * User Delete Confirm | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showUserDeleteConfirm: function (model) { | 	showUserDeleteConfirm: function (model) { | ||||||
|         if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { | 		if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { | ||||||
|             require(['./main', './user/delete'], function (App, View) { | 			require(['./main', './user/delete'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Dashboard | 	 * Dashboard | ||||||
|      */ | 	 */ | ||||||
|     showDashboard: function () { | 	showDashboard: function () { | ||||||
|         let controller = this; | 		const controller = this; | ||||||
|  | 		require(['./main', './dashboard/main'], (App, View) => { | ||||||
|  | 			controller.navigate('/'); | ||||||
|  | 			App.UI.showAppContent(new View()); | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|         require(['./main', './dashboard/main'], (App, View) => { | 	/** | ||||||
|             controller.navigate('/'); | 	 * Nginx Proxy Hosts | ||||||
|             App.UI.showAppContent(new View()); | 	 */ | ||||||
|         }); | 	showNginxProxy: function () { | ||||||
|     }, | 		if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { | ||||||
|  | 			const controller = this; | ||||||
|  |  | ||||||
|     /** | 			require(['./main', './nginx/proxy/main'], (App, View) => { | ||||||
|      * Nginx Proxy Hosts | 				controller.navigate('/nginx/proxy'); | ||||||
|      */ | 				App.UI.showAppContent(new View()); | ||||||
|     showNginxProxy: function () { | 			}); | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { | 		} | ||||||
|             let controller = this; | 	}, | ||||||
|  |  | ||||||
|             require(['./main', './nginx/proxy/main'], (App, View) => { | 	/** | ||||||
|                 controller.navigate('/nginx/proxy'); | 	 * Nginx Proxy Host Form | ||||||
|                 App.UI.showAppContent(new View()); | 	 * | ||||||
|             }); | 	 * @param [model] | ||||||
|         } | 	 */ | ||||||
|     }, | 	showNginxProxyForm: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||||
|  | 			require(['./main', './nginx/proxy/form'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Proxy Host Form | 	 * Proxy Host Delete Confirm | ||||||
|      * | 	 * | ||||||
|      * @param [model] | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showNginxProxyForm: function (model) { | 	showNginxProxyDeleteConfirm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||||
|             require(['./main', './nginx/proxy/form'], function (App, View) { | 			require(['./main', './nginx/proxy/delete'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Proxy Host Delete Confirm | 	 * Nginx Redirection Hosts | ||||||
|      * | 	 */ | ||||||
|      * @param model | 	showNginxRedirection: function () { | ||||||
|      */ | 		if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { | ||||||
|     showNginxProxyDeleteConfirm: function (model) { | 			const controller = this; | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | 			require(['./main', './nginx/redirection/main'], (App, View) => { | ||||||
|             require(['./main', './nginx/proxy/delete'], function (App, View) { | 				controller.navigate('/nginx/redirection'); | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showAppContent(new View()); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Redirection Hosts | 	 * Nginx Redirection Host Form | ||||||
|      */ | 	 * | ||||||
|     showNginxRedirection: function () { | 	 * @param [model] | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { | 	 */ | ||||||
|             let controller = this; | 	showNginxRedirectionForm: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||||
|  | 			require(['./main', './nginx/redirection/form'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|             require(['./main', './nginx/redirection/main'], (App, View) => { | 	/** | ||||||
|                 controller.navigate('/nginx/redirection'); | 	 * Proxy Redirection Delete Confirm | ||||||
|                 App.UI.showAppContent(new View()); | 	 * | ||||||
|             }); | 	 * @param model | ||||||
|         } | 	 */ | ||||||
|     }, | 	showNginxRedirectionDeleteConfirm: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||||
|  | 			require(['./main', './nginx/redirection/delete'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Redirection Host Form | 	 * Nginx Stream Hosts | ||||||
|      * | 	 */ | ||||||
|      * @param [model] | 	showNginxStream: function () { | ||||||
|      */ | 		if (Cache.User.isAdmin() || Cache.User.canView('streams')) { | ||||||
|     showNginxRedirectionForm: function (model) { | 			const controller = this; | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | 			require(['./main', './nginx/stream/main'], (App, View) => { | ||||||
|             require(['./main', './nginx/redirection/form'], function (App, View) { | 				controller.navigate('/nginx/stream'); | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showAppContent(new View()); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Proxy Redirection Delete Confirm | 	 * Stream Form | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param [model] | ||||||
|      */ | 	 */ | ||||||
|     showNginxRedirectionDeleteConfirm: function (model) { | 	showNginxStreamForm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||||
|             require(['./main', './nginx/redirection/delete'], function (App, View) { | 			require(['./main', './nginx/stream/form'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Stream Hosts | 	 * Stream Delete Confirm | ||||||
|      */ | 	 * | ||||||
|     showNginxStream: function () { | 	 * @param model | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canView('streams')) { | 	 */ | ||||||
|             let controller = this; | 	showNginxStreamDeleteConfirm: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||||
|  | 			require(['./main', './nginx/stream/delete'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|             require(['./main', './nginx/stream/main'], (App, View) => { | 	/** | ||||||
|                 controller.navigate('/nginx/stream'); | 	 * Nginx Dead Hosts | ||||||
|                 App.UI.showAppContent(new View()); | 	 */ | ||||||
|             }); | 	showNginxDead: function () { | ||||||
|         } | 		if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { | ||||||
|     }, | 			const controller = this; | ||||||
|  | 			require(['./main', './nginx/dead/main'], (App, View) => { | ||||||
|  | 				controller.navigate('/nginx/404'); | ||||||
|  | 				App.UI.showAppContent(new View()); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Stream Form | 	 * Dead Host Form | ||||||
|      * | 	 * | ||||||
|      * @param [model] | 	 * @param [model] | ||||||
|      */ | 	 */ | ||||||
|     showNginxStreamForm: function (model) { | 	showNginxDeadForm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||||
|             require(['./main', './nginx/stream/form'], function (App, View) { | 			require(['./main', './nginx/dead/form'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Stream Delete Confirm | 	 * Dead Host Delete Confirm | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showNginxStreamDeleteConfirm: function (model) { | 	showNginxDeadDeleteConfirm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||||
|             require(['./main', './nginx/stream/delete'], function (App, View) { | 			require(['./main', './nginx/dead/delete'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Dead Hosts | 	 * Help Dialog | ||||||
|      */ | 	 * | ||||||
|     showNginxDead: function () { | 	 * @param {String}  title | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { | 	 * @param {String}  content | ||||||
|             let controller = this; | 	 */ | ||||||
|  | 	showHelp: function (title, content) { | ||||||
|  | 		require(['./main', './help/main'], function (App, View) { | ||||||
|  | 			App.UI.showModalDialog(new View({title: title, content: content})); | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|             require(['./main', './nginx/dead/main'], (App, View) => { | 	/** | ||||||
|                 controller.navigate('/nginx/404'); | 	 * Nginx Access | ||||||
|                 App.UI.showAppContent(new View()); | 	 */ | ||||||
|             }); | 	showNginxAccess: function () { | ||||||
|         } | 		if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { | ||||||
|     }, | 			const controller = this; | ||||||
|  | 			require(['./main', './nginx/access/main'], (App, View) => { | ||||||
|  | 				controller.navigate('/nginx/access'); | ||||||
|  | 				App.UI.showAppContent(new View()); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Dead Host Form | 	 * Nginx Access List Form | ||||||
|      * | 	 * | ||||||
|      * @param [model] | 	 * @param [model] | ||||||
|      */ | 	 */ | ||||||
|     showNginxDeadForm: function (model) { | 	showNginxAccessListForm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||||
|             require(['./main', './nginx/dead/form'], function (App, View) { | 			require(['./main', './nginx/access/form'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Dead Host Delete Confirm | 	 * Access List Delete Confirm | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showNginxDeadDeleteConfirm: function (model) { | 	showNginxAccessListDeleteConfirm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||||
|             require(['./main', './nginx/dead/delete'], function (App, View) { | 			require(['./main', './nginx/access/delete'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Help Dialog | 	 * Nginx Certificates | ||||||
|      * | 	 */ | ||||||
|      * @param {String}  title | 	showNginxCertificates: function () { | ||||||
|      * @param {String}  content | 		if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { | ||||||
|      */ | 			const controller = this; | ||||||
|     showHelp: function (title, content) { | 			require(['./main', './nginx/certificates/main'], (App, View) => { | ||||||
|         require(['./main', './help/main'], function (App, View) { | 				controller.navigate('/nginx/certificates'); | ||||||
|             App.UI.showModalDialog(new View({title: title, content: content})); | 				App.UI.showAppContent(new View()); | ||||||
|         }); | 			}); | ||||||
|     }, | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Access | 	 * Nginx Certificate Form | ||||||
|      */ | 	 * | ||||||
|     showNginxAccess: function () { | 	 * @param [model] | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { | 	 */ | ||||||
|             let controller = this; | 	showNginxCertificateForm: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
|  | 			require(['./main', './nginx/certificates/form'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|             require(['./main', './nginx/access/main'], (App, View) => { | 	/** | ||||||
|                 controller.navigate('/nginx/access'); | 	 * Certificate Renew | ||||||
|                 App.UI.showAppContent(new View()); | 	 * | ||||||
|             }); | 	 * @param model | ||||||
|         } | 	 */ | ||||||
|     }, | 	showNginxCertificateRenew: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
|  | 			require(['./main', './nginx/certificates/renew'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Access List Form | 	 * Certificate Delete Confirm | ||||||
|      * | 	 * | ||||||
|      * @param [model] | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showNginxAccessListForm: function (model) { | 	showNginxCertificateDeleteConfirm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
|             require(['./main', './nginx/access/form'], function (App, View) { | 			require(['./main', './nginx/certificates/delete'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 			}); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Access List Delete Confirm | 	 * Certificate Test Reachability | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showNginxAccessListDeleteConfirm: function (model) { | 	showNginxCertificateTestReachability: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | 	  if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
|             require(['./main', './nginx/access/delete'], function (App, View) { | 		require(['./main', './nginx/certificates/test'], function (App, View) { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 		  App.UI.showModalDialog(new View({model: model})); | ||||||
|             }); | 		}); | ||||||
|         } | 	  } | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Certificates | 	 * Audit Log | ||||||
|      */ | 	 */ | ||||||
|     showNginxCertificates: function () { | 	showAuditLog: function () { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { | 		const controller = this; | ||||||
|             let controller = this; | 		if (Cache.User.isAdmin()) { | ||||||
|  | 			require(['./main', './audit-log/main'], (App, View) => { | ||||||
|  | 				controller.navigate('/audit-log'); | ||||||
|  | 				App.UI.showAppContent(new View()); | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			this.showDashboard(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|             require(['./main', './nginx/certificates/main'], (App, View) => { | 	/** | ||||||
|                 controller.navigate('/nginx/certificates'); | 	 * Audit Log Metadata | ||||||
|                 App.UI.showAppContent(new View()); | 	 * | ||||||
|             }); | 	 * @param model | ||||||
|         } | 	 */ | ||||||
|     }, | 	showAuditMeta: function (model) { | ||||||
|  | 		if (Cache.User.isAdmin()) { | ||||||
|  | 			require(['./main', './audit-log/meta'], function (App, View) { | ||||||
|  | 				App.UI.showModalDialog(new View({model: model})); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Nginx Certificate Form | 	 * Settings | ||||||
|      * | 	 */ | ||||||
|      * @param [model] | 	showSettings: function () { | ||||||
|      */ | 		const controller = this; | ||||||
|     showNginxCertificateForm: function (model) { | 		if (Cache.User.isAdmin()) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | 			require(['./main', './settings/main'], (App, View) => { | ||||||
|             require(['./main', './nginx/certificates/form'], function (App, View) { | 				controller.navigate('/settings'); | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				App.UI.showAppContent(new View()); | ||||||
|             }); | 			}); | ||||||
|         } | 		} else { | ||||||
|     }, | 			this.showDashboard(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Certificate Renew | 	 * Settings Item Form | ||||||
|      * | 	 * | ||||||
|      * @param model | 	 * @param model | ||||||
|      */ | 	 */ | ||||||
|     showNginxCertificateRenew: function (model) { | 	showSettingForm: function (model) { | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | 		if (Cache.User.isAdmin()) { | ||||||
|             require(['./main', './nginx/certificates/renew'], function (App, View) { | 			if (model.get('id') === 'default-site') { | ||||||
|                 App.UI.showModalDialog(new View({model: model})); | 				require(['./main', './settings/default-site/main'], function (App, View) { | ||||||
|             }); | 					App.UI.showModalDialog(new View({model: model})); | ||||||
|         } | 				}); | ||||||
|     }, | 			} | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|     /** | 	/** | ||||||
|      * Certificate Delete Confirm | 	 * Logout | ||||||
|      * | 	 */ | ||||||
|      * @param model | 	logout: function () { | ||||||
|      */ | 		Tokens.dropTopToken(); | ||||||
|     showNginxCertificateDeleteConfirm: function (model) { | 		this.showLogin(); | ||||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | 	} | ||||||
|             require(['./main', './nginx/certificates/delete'], function (App, View) { |  | ||||||
|                 App.UI.showModalDialog(new View({model: model})); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Certificate Test Reachability |  | ||||||
|      * |  | ||||||
|      * @param model |  | ||||||
|      */ |  | ||||||
|     showNginxCertificateTestReachability: function (model) { |  | ||||||
|       if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { |  | ||||||
|         require(['./main', './nginx/certificates/test'], function (App, View) { |  | ||||||
|           App.UI.showModalDialog(new View({model: model})); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Audit Log |  | ||||||
|      */ |  | ||||||
|     showAuditLog: function () { |  | ||||||
|         let controller = this; |  | ||||||
|         if (Cache.User.isAdmin()) { |  | ||||||
|             require(['./main', './audit-log/main'], (App, View) => { |  | ||||||
|                 controller.navigate('/audit-log'); |  | ||||||
|                 App.UI.showAppContent(new View()); |  | ||||||
|             }); |  | ||||||
|         } else { |  | ||||||
|             this.showDashboard(); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Audit Log Metadata |  | ||||||
|      * |  | ||||||
|      * @param model |  | ||||||
|      */ |  | ||||||
|     showAuditMeta: function (model) { |  | ||||||
|         if (Cache.User.isAdmin()) { |  | ||||||
|             require(['./main', './audit-log/meta'], function (App, View) { |  | ||||||
|                 App.UI.showModalDialog(new View({model: model})); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Settings |  | ||||||
|      */ |  | ||||||
|     showSettings: function () { |  | ||||||
|         let controller = this; |  | ||||||
|         if (Cache.User.isAdmin()) { |  | ||||||
|             require(['./main', './settings/main'], (App, View) => { |  | ||||||
|                 controller.navigate('/settings'); |  | ||||||
|                 App.UI.showAppContent(new View()); |  | ||||||
|             }); |  | ||||||
|         } else { |  | ||||||
|             this.showDashboard(); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Settings Item Form |  | ||||||
|      * |  | ||||||
|      * @param model |  | ||||||
|      */ |  | ||||||
|     showSettingForm: function (model) { |  | ||||||
|         if (Cache.User.isAdmin()) { |  | ||||||
|             if (model.get('id') === 'default-site') { |  | ||||||
|                 require(['./main', './settings/default-site/main'], function (App, View) { |  | ||||||
|                     App.UI.showModalDialog(new View({model: model})); |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Logout |  | ||||||
|      */ |  | ||||||
|     logout: function () { |  | ||||||
|         Tokens.dropTopToken(); |  | ||||||
|         this.showLogin(); |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -6,87 +6,85 @@ const Helpers    = require('../../lib/helpers'); | |||||||
| const template   = require('./main.ejs'); | const template   = require('./main.ejs'); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template: template, | 	template: template, | ||||||
|     id:       'dashboard', | 	id:       'dashboard', | ||||||
|     columns:  0, | 	columns:  0, | ||||||
|  |  | ||||||
|     stats: {}, | 	stats: {}, | ||||||
|  |  | ||||||
|     ui: { | 	ui: { | ||||||
|         links: 'a' | 		links: 'a' | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     events: { | 	events: { | ||||||
|         'click @ui.links': function (e) { | 		'click @ui.links': function (e) { | ||||||
|             e.preventDefault(); | 			e.preventDefault(); | ||||||
|             Controller.navigate($(e.currentTarget).attr('href'), true); | 			Controller.navigate($(e.currentTarget).attr('href'), true); | ||||||
|         } | 		} | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     templateContext: function () { | 	templateContext: function () { | ||||||
|         let view = this; | 		const view = this; | ||||||
|  |  | ||||||
|         return { | 		return { | ||||||
|             getUserName: function () { | 			getUserName: function () { | ||||||
|                 return Cache.User.get('nickname') || Cache.User.get('name'); | 				return Cache.User.get('nickname') || Cache.User.get('name'); | ||||||
|             }, | 			}, | ||||||
|  |  | ||||||
|             getHostStat: function (type) { | 			getHostStat: function (type) { | ||||||
|                 if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { | 				if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { | ||||||
|                     return Helpers.niceNumber(view.stats.hosts[type]); | 					return Helpers.niceNumber(view.stats.hosts[type]); | ||||||
|                 } | 				} | ||||||
|  |  | ||||||
|                 return '-'; | 				return '-'; | ||||||
|             }, | 			}, | ||||||
|  |  | ||||||
|             canShow: function (perm) { | 			canShow: function (perm) { | ||||||
|                 return Cache.User.isAdmin() || Cache.User.canView(perm); | 				return Cache.User.isAdmin() || Cache.User.canView(perm); | ||||||
|             }, | 			}, | ||||||
|  |  | ||||||
|             columns: view.columns | 			columns: view.columns | ||||||
|         }; | 		}; | ||||||
|     }, | 	}, | ||||||
|  |  | ||||||
|     onRender: function () { | 	onRender: function () { | ||||||
|         let view = this; | 		const view = this; | ||||||
|  | 		if (typeof view.stats.hosts === 'undefined') { | ||||||
|  | 			Api.Reports.getHostStats() | ||||||
|  | 				.then(response => { | ||||||
|  | 					if (!view.isDestroyed()) { | ||||||
|  | 						view.stats.hosts = response; | ||||||
|  | 						view.render(); | ||||||
|  | 					} | ||||||
|  | 				}) | ||||||
|  | 				.catch(err => { | ||||||
|  | 					console.log(err); | ||||||
|  | 				}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|         if (typeof view.stats.hosts === 'undefined') { | 	/** | ||||||
|             Api.Reports.getHostStats() | 	 * @param {Object}  [model] | ||||||
|                 .then(response => { | 	 */ | ||||||
|                     if (!view.isDestroyed()) { | 	preRender: function (model) { | ||||||
|                         view.stats.hosts = response; | 		this.columns = 0; | ||||||
|                         view.render(); |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|                 .catch(err => { |  | ||||||
|                     console.log(err); |  | ||||||
|                 }); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     /** | 		// calculate the available columns based on permissions for the objects | ||||||
|      * @param {Object}  [model] | 		// and store as a variable | ||||||
|      */ | 		const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; | ||||||
|     preRender: function (model) { |  | ||||||
|         this.columns = 0; |  | ||||||
|  |  | ||||||
|         // calculate the available columns based on permissions for the objects | 		perms.map(perm => { | ||||||
|         // and store as a variable | 			this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; | ||||||
|         //let view = this; | 		}); | ||||||
|         let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; |  | ||||||
|  |  | ||||||
|         perms.map(perm => { | 		// Prevent double rendering on initial calls | ||||||
|             this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; | 		if (typeof model !== 'undefined') { | ||||||
|         }); | 			this.render(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|         // Prevent double rendering on initial calls | 	initialize: function () { | ||||||
|         if (typeof model !== 'undefined') { | 		this.preRender(); | ||||||
|             this.render(); | 		this.listenTo(Cache.User, 'change', this.preRender); | ||||||
|         } | 	} | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     initialize: function () { |  | ||||||
|         this.preRender(); |  | ||||||
|         this.listenTo(Cache.User, 'change', this.preRender); |  | ||||||
|     } |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> |     <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' %>"> | ||||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> |         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></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.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> |     <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' %>"> | ||||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> |         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
| @@ -33,6 +33,13 @@ | |||||||
| <td class="<%- isExpired() ? 'text-danger' : '' %>"> | <td class="<%- isExpired() ? 'text-danger' : '' %>"> | ||||||
|     <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> |     <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> | ||||||
| </td> | </td> | ||||||
|  | <td> | ||||||
|  |     <% if (active_domain_names().length > 0) { %> | ||||||
|  |         <span class="status-icon bg-success"></span> <%- i18n('certificates', 'in-use') %> | ||||||
|  |     <% } else { %> | ||||||
|  |         <span class="status-icon bg-danger"></span> <%- i18n('certificates', 'inactive') %> | ||||||
|  |     <% } %> | ||||||
|  | </td> | ||||||
| <% if (canManage) { %> | <% if (canManage) { %> | ||||||
| <td class="text-right"> | <td class="text-right"> | ||||||
|     <div class="item-action dropdown"> |     <div class="item-action dropdown"> | ||||||
| @@ -48,7 +55,14 @@ | |||||||
|                 <div class="dropdown-divider"></div> |                 <div class="dropdown-divider"></div> | ||||||
|             <% } %> |             <% } %> | ||||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> |             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||||
|  |             <% if (active_domain_names().length > 0) { %> | ||||||
|  |                 <div class="dropdown-divider"></div> | ||||||
|  |                 <span class="dropdown-header"><%- i18n('certificates', 'active-domain_names') %></span> | ||||||
|  |                 <% active_domain_names().forEach(function(host) { %> | ||||||
|  |                     <a href="https://<%- host %>" class="dropdown-item" target="_blank"><%- host %></a> | ||||||
|  |                 <% }); %> | ||||||
|  |             <% } %> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <% } %> | <% } %> | ||||||
| @@ -44,14 +44,24 @@ module.exports = Mn.View.extend({ | |||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     templateContext: function () { | ||||||
|         canManage: App.Cache.User.canManage('certificates'), |         return { | ||||||
|         isExpired: function () { |             canManage: App.Cache.User.canManage('certificates'), | ||||||
|             return moment(this.expires_on).isBefore(moment()); |             isExpired: function () { | ||||||
|         }, |                 return moment(this.expires_on).isBefore(moment()); | ||||||
|         dns_providers: dns_providers |             }, | ||||||
|  |             dns_providers: dns_providers, | ||||||
|  |             active_domain_names: function () { | ||||||
|  |                 const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this; | ||||||
|  |                 return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => { | ||||||
|  |                     acc.push(...(host.domain_names || [])); | ||||||
|  |                     return acc; | ||||||
|  |                 }, []); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |  | ||||||
|     initialize: function () { |     initialize: function () { | ||||||
|         this.listenTo(this.model, 'change', this.render); |         this.listenTo(this.model, 'change', this.render); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <th><%- i18n('str', 'name') %></th> |     <th><%- i18n('str', 'name') %></th> | ||||||
|     <th><%- i18n('all-hosts', 'cert-provider') %></th> |     <th><%- i18n('all-hosts', 'cert-provider') %></th> | ||||||
|     <th><%- i18n('str', 'expires') %></th> |     <th><%- i18n('str', 'expires') %></th> | ||||||
|  |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|     <% } %> |     <% } %> | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ module.exports = Mn.View.extend({ | |||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             let query = this.ui.query.val(); |             let query = this.ui.query.val(); | ||||||
|  |  | ||||||
|             this.fetch(['owner'], query) |             this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query) | ||||||
|                 .then(response => this.showData(response)) |                 .then(response => this.showData(response)) | ||||||
|                 .catch(err => { |                 .catch(err => { | ||||||
|                     this.showError(err); |                     this.showError(err); | ||||||
| @@ -89,7 +89,7 @@ module.exports = Mn.View.extend({ | |||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         let view = this; |         let view = this; | ||||||
|  |  | ||||||
|         view.fetch(['owner']) |         view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts']) | ||||||
|             .then(response => { |             .then(response => { | ||||||
|                 if (!view.isDestroyed()) { |                 if (!view.isDestroyed()) { | ||||||
|                     if (response && response.length) { |                     if (response && response.length) { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> |     <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' %>"> | ||||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> |         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></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.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> |     <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' %>"> | ||||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> |         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></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.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> |     <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' %>"> | ||||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> |         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   | |||||||
| @@ -3,48 +3,187 @@ | |||||||
|         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5> |         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5> | ||||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-body"> |     <div class="modal-body has-tabs"> | ||||||
|  |         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> | ||||||
|         <form> |         <form> | ||||||
|             <div class="row"> |             <ul class="nav nav-tabs" role="tablist"> | ||||||
|                 <div class="col-sm-12 col-md-12"> |                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||||
|                     <div class="form-group"> |                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||||
|                         <label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label> |             </ul> | ||||||
|                         <input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required> |             <div class="tab-content"> | ||||||
|  |                 <!-- Details --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||||
|  |                     <div class="row"> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-8 col-md-8"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-4 col-md-4"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div> | ||||||
|  |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="col-sm-8 col-md-8"> |  | ||||||
|                     <div class="form-group"> |                 <!-- SSL --> | ||||||
|                         <label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label> |                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||||
|                         <input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required> |                     <div class="row"> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('streams', 'ssl-certificate') %></label> | ||||||
|  |                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||||
|  |                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||||
|  |                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||||
|  |                                 </select> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- DNS challenge --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>"> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input | ||||||
|  |                                             type="checkbox" | ||||||
|  |                                             class="custom-switch-input" | ||||||
|  |                                             name="meta[dns_challenge]" | ||||||
|  |                                             value="1" | ||||||
|  |                                             checked | ||||||
|  |                                             disabled | ||||||
|  |                                     > | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <fieldset class="form-fieldset dns-challenge"> | ||||||
|  |                                 <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> | ||||||
|  |  | ||||||
|  |                                 <!-- Certbot DNS plugin selection --> | ||||||
|  |                                 <div class="row"> | ||||||
|  |                                     <div class="col-sm-12 col-md-12"> | ||||||
|  |                                         <div class="form-group"> | ||||||
|  |                                             <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> | ||||||
|  |                                             <select | ||||||
|  |                                                     name="meta[dns_provider]" | ||||||
|  |                                                     id="dns_provider" | ||||||
|  |                                                     class="form-control custom-select" | ||||||
|  |                                             > | ||||||
|  |                                                 <option | ||||||
|  |                                                         value="" | ||||||
|  |                                                         disabled | ||||||
|  |                                                         hidden | ||||||
|  |                                                         <%- getDnsProvider() === null ? 'selected' : '' %> | ||||||
|  |                                                 >Please Choose...</option> | ||||||
|  |                                                 <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> | ||||||
|  |                                                     <option | ||||||
|  |                                                             value="<%- plugin_name %>" | ||||||
|  |                                                             <%- getDnsProvider() === plugin_name ? 'selected' : '' %> | ||||||
|  |                                                     ><%- plugin_info.name %></option> | ||||||
|  |                                                 <% }); %> | ||||||
|  |                                             </select> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <!-- Certbot credentials file content --> | ||||||
|  |                                 <div class="row credentials-file-content"> | ||||||
|  |                                     <div class="col-sm-12 col-md-12"> | ||||||
|  |                                         <div class="form-group"> | ||||||
|  |                                             <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> | ||||||
|  |                                             <textarea | ||||||
|  |                                                     name="meta[dns_provider_credentials]" | ||||||
|  |                                                     class="form-control text-monospace" | ||||||
|  |                                                     id="dns_provider_credentials" | ||||||
|  |                                             ><%- getDnsProviderCredentials() %></textarea> | ||||||
|  |                                             <div class="text-secondary small"> | ||||||
|  |                                                 <i class="fe fe-info"></i> | ||||||
|  |                                                 <%= i18n('ssl', 'credentials-file-content-info') %> | ||||||
|  |                                             </div> | ||||||
|  |                                             <div class="text-red small"> | ||||||
|  |                                                 <i class="fe fe-alert-triangle"></i> | ||||||
|  |                                                 <%= i18n('ssl', 'stored-as-plaintext-info') %> | ||||||
|  |                                             </div> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <!-- DNS propagation delay --> | ||||||
|  |                                 <div class="row"> | ||||||
|  |                                     <div class="col-sm-12 col-md-12"> | ||||||
|  |                                         <div class="form-group mb-0"> | ||||||
|  |                                             <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> | ||||||
|  |                                             <input | ||||||
|  |                                                     type="number" | ||||||
|  |                                                     min="0" | ||||||
|  |                                                     name="meta[propagation_seconds]" | ||||||
|  |                                                     class="form-control" | ||||||
|  |                                                     id="propagation_seconds" | ||||||
|  |                                                     value="<%- getPropagationSeconds() %>" | ||||||
|  |                                             > | ||||||
|  |                                             <div class="text-secondary small"> | ||||||
|  |                                                 <i class="fe fe-info"></i> | ||||||
|  |                                                 <%= i18n('ssl', 'propagation-seconds-info') %> | ||||||
|  |                                             </div> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |                             </fieldset> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- Lets encrypt --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="col-sm-4 col-md-4"> |  | ||||||
|                     <div class="form-group"> |  | ||||||
|                         <label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label> |  | ||||||
|                         <input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="col-sm-6 col-md-6"> |  | ||||||
|                     <div class="form-group"> |  | ||||||
|                         <label class="custom-switch"> |  | ||||||
|                             <input type="checkbox" class="custom-switch-input" name="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>> |  | ||||||
|                             <span class="custom-switch-indicator"></span> |  | ||||||
|                             <span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span> |  | ||||||
|                         </label> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="col-sm-6 col-md-6"> |  | ||||||
|                     <div class="form-group"> |  | ||||||
|                         <label class="custom-switch"> |  | ||||||
|                             <input type="checkbox" class="custom-switch-input" name="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>> |  | ||||||
|                             <span class="custom-switch-indicator"></span> |  | ||||||
|                             <span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span> |  | ||||||
|                         </label> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="col-sm-12 col-md-12"> |  | ||||||
|                     <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |             </div> | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -1,24 +1,38 @@ | |||||||
| const Mn          = require('backbone.marionette'); | const Mn            = require('backbone.marionette'); | ||||||
| const App         = require('../../main'); | const App           = require('../../main'); | ||||||
| const StreamModel = require('../../../models/stream'); | const StreamModel   = require('../../../models/stream'); | ||||||
| const template    = require('./form.ejs'); | const template      = require('./form.ejs'); | ||||||
|  | const dns_providers = require('../../../../../global/certbot-dns-plugins'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); | require('jquery-serializejson'); | ||||||
| require('jquery-mask-plugin'); | require('jquery-mask-plugin'); | ||||||
| require('selectize'); | require('selectize'); | ||||||
|  | const Helpers = require("../../../lib/helpers"); | ||||||
|  | const certListItemTemplate = require("../certificates-list-item.ejs"); | ||||||
|  | const i18n = require("../../i18n"); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
|  |  | ||||||
|     ui: { |     ui: { | ||||||
|         form:       'form', |         form:                     'form', | ||||||
|         forwarding_host: 'input[name="forwarding_host"]', |         forwarding_host:          'input[name="forwarding_host"]', | ||||||
|         type_error: '.forward-type-error', |         type_error:               '.forward-type-error', | ||||||
|         buttons:    '.modal-footer button', |         buttons:                  '.modal-footer button', | ||||||
|         switches:   '.custom-switch-input', |         switches:                 '.custom-switch-input', | ||||||
|         cancel:     'button.cancel', |         cancel:                   'button.cancel', | ||||||
|         save:       'button.save' |         save:                     'button.save', | ||||||
|  |         le_error_info:            '#le-error-info', | ||||||
|  |         certificate_select:       'select[name="certificate_id"]', | ||||||
|  |         domain_names:             'input[name="domain_names"]', | ||||||
|  |         dns_challenge_switch:     'input[name="meta[dns_challenge]"]', | ||||||
|  |         dns_challenge_content:    '.dns-challenge', | ||||||
|  |         dns_provider:             'select[name="meta[dns_provider]"]', | ||||||
|  |         credentials_file_content: '.credentials-file-content', | ||||||
|  |         dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', | ||||||
|  |         propagation_seconds:      'input[name="meta[propagation_seconds]"]', | ||||||
|  |         letsencrypt:              '.letsencrypt' | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     events: { |     events: { | ||||||
| @@ -48,6 +62,35 @@ module.exports = Mn.View.extend({ | |||||||
|             data.tcp_forwarding  = !!data.tcp_forwarding; |             data.tcp_forwarding  = !!data.tcp_forwarding; | ||||||
|             data.udp_forwarding  = !!data.udp_forwarding; |             data.udp_forwarding  = !!data.udp_forwarding; | ||||||
|  |  | ||||||
|  |             if (typeof data.meta === 'undefined') data.meta = {}; | ||||||
|  |             data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; | ||||||
|  |             data.meta.dns_challenge = true; | ||||||
|  |  | ||||||
|  |             if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; | ||||||
|  |  | ||||||
|  |             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||||
|  |                 data.domain_names = data.domain_names.split(','); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||||
|  |             if (data.certificate_id === 'new') { | ||||||
|  |                 let domain_err = false; | ||||||
|  |                 if (!data.meta.dns_challenge) { | ||||||
|  |                     data.domain_names.map(function (name) { | ||||||
|  |                         if (name.match(/\*/im)) { | ||||||
|  |                             domain_err = true; | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (domain_err) { | ||||||
|  |                     alert(i18n('ssl', 'no-wildcard-without-dns')); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             let method = App.Api.Nginx.Streams.create; |             let method = App.Api.Nginx.Streams.create; | ||||||
|             let is_new = true; |             let is_new = true; | ||||||
|  |  | ||||||
| @@ -70,10 +113,108 @@ module.exports = Mn.View.extend({ | |||||||
|                     }); |                     }); | ||||||
|                 }) |                 }) | ||||||
|                 .catch(err => { |                 .catch(err => { | ||||||
|                     alert(err.message); |                     let more_info = ''; | ||||||
|  |                     if (err.code === 500 && err.debug) { | ||||||
|  |                         try { | ||||||
|  |                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); | ||||||
|  |                         } catch (e) { | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>` : ''}`; | ||||||
|  |                     this.ui.le_error_info.show(); | ||||||
|  |                     this.ui.le_error_info[0].scrollIntoView(); | ||||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                     this.ui.save.removeClass('btn-loading'); | ||||||
|                 }); |                 }); | ||||||
|         } |         }, | ||||||
|  |  | ||||||
|  |         'change @ui.certificate_select': function () { | ||||||
|  |             let id = this.ui.certificate_select.val(); | ||||||
|  |             if (id === 'new') { | ||||||
|  |                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||||
|  |                 this.ui.domain_names.prop('required', 'required'); | ||||||
|  |  | ||||||
|  |                 this.ui.dns_challenge_switch | ||||||
|  |                     .prop('disabled', true) | ||||||
|  |                     .parents('.form-group') | ||||||
|  |                     .css('opacity', 0.5); | ||||||
|  |  | ||||||
|  |                 this.ui.dns_provider.prop('required', 'required'); | ||||||
|  |                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||||
|  |                 if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||||
|  |                     this.ui.dns_provider_credentials.prop('required', 'required'); | ||||||
|  |                 } | ||||||
|  |                 this.ui.dns_challenge_content.show(); | ||||||
|  |             } else { | ||||||
|  |                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'change @ui.dns_provider': function () { | ||||||
|  |             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||||
|  |             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||||
|  |                 this.ui.dns_provider_credentials.prop('required', 'required'); | ||||||
|  |                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; | ||||||
|  |                 this.ui.credentials_file_content.show(); | ||||||
|  |             } else { | ||||||
|  |                 this.ui.dns_provider_credentials.prop('required', false); | ||||||
|  |                 this.ui.credentials_file_content.hide(); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         getLetsencryptEmail: function () { | ||||||
|  |             return App.Cache.User.get('email'); | ||||||
|  |         }, | ||||||
|  |         getDnsProvider: function () { | ||||||
|  |             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; | ||||||
|  |         }, | ||||||
|  |         getDnsProviderCredentials: function () { | ||||||
|  |             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; | ||||||
|  |         }, | ||||||
|  |         getPropagationSeconds: function () { | ||||||
|  |             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; | ||||||
|  |         }, | ||||||
|  |         dns_plugins: dns_providers, | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         let view = this; | ||||||
|  |  | ||||||
|  |         // Certificates | ||||||
|  |         this.ui.le_error_info.hide(); | ||||||
|  |         this.ui.dns_challenge_content.hide(); | ||||||
|  |         this.ui.credentials_file_content.hide(); | ||||||
|  |         this.ui.letsencrypt.hide(); | ||||||
|  |         this.ui.certificate_select.selectize({ | ||||||
|  |             valueField:       'id', | ||||||
|  |             labelField:       'nice_name', | ||||||
|  |             searchField:      ['nice_name', 'domain_names'], | ||||||
|  |             create:           false, | ||||||
|  |             preload:          true, | ||||||
|  |             allowEmptyOption: true, | ||||||
|  |             render:           { | ||||||
|  |                 option: function (item) { | ||||||
|  |                     item.i18n         = App.i18n; | ||||||
|  |                     item.formatDbDate = Helpers.formatDbDate; | ||||||
|  |                     return certListItemTemplate(item); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             load:             function (query, callback) { | ||||||
|  |                 App.Api.Nginx.Certificates.getAll() | ||||||
|  |                     .then(rows => { | ||||||
|  |                         callback(rows); | ||||||
|  |                     }) | ||||||
|  |                     .catch(err => { | ||||||
|  |                         console.error(err); | ||||||
|  |                         callback(); | ||||||
|  |                     }); | ||||||
|  |             }, | ||||||
|  |             onLoad:           function () { | ||||||
|  |                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     initialize: function (options) { |     initialize: function (options) { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> |     <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' %>"> | ||||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> |         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
| @@ -16,7 +16,10 @@ | |||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|     <div> |     <div> | ||||||
|         <% if (tcp_forwarding) { %> |         <% if (certificate) { %> | ||||||
|  |             <span class="tag"><%- i18n('streams', 'tcp+ssl') %></span> | ||||||
|  |         <% } | ||||||
|  |         else if (tcp_forwarding) { %> | ||||||
|             <span class="tag"><%- i18n('streams', 'tcp') %></span> |             <span class="tag"><%- i18n('streams', 'tcp') %></span> | ||||||
|         <% } |         <% } | ||||||
|         if (udp_forwarding) { %> |         if (udp_forwarding) { %> | ||||||
| @@ -24,6 +27,9 @@ | |||||||
|         <% } %> |         <% } %> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %></div> | ||||||
|  | </td> | ||||||
| <td> | <td> | ||||||
|     <% |     <% | ||||||
|     var o = isOnline(); |     var o = isOnline(); | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <th><%- i18n('streams', 'incoming-port') %></th> |     <th><%- i18n('streams', 'incoming-port') %></th> | ||||||
|     <th><%- i18n('str', 'destination') %></th> |     <th><%- i18n('str', 'destination') %></th> | ||||||
|     <th><%- i18n('streams', 'protocol') %></th> |     <th><%- i18n('streams', 'protocol') %></th> | ||||||
|  |     <th><%- i18n('str', 'ssl') %></th> | ||||||
|     <th><%- i18n('str', 'status') %></th> |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ module.exports = Mn.View.extend({ | |||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         let view = this; |         let view = this; | ||||||
|  |  | ||||||
|         view.fetch(['owner']) |         view.fetch(['owner', 'certificate']) | ||||||
|             .then(response => { |             .then(response => { | ||||||
|                 if (!view.isDestroyed()) { |                 if (!view.isDestroyed()) { | ||||||
|                     if (response && response.length) { |                     if (response && response.length) { | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <div class="modal-content"> | <div class="modal-content"> | ||||||
|     <div class="modal-header"> |     <form> | ||||||
|         <h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5> |         <div class="modal-header"> | ||||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> |             <h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5> | ||||||
|     </div> |             <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|     <div class="modal-body"> |         </div> | ||||||
|         <form> |         <div class="modal-body"> | ||||||
|             <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: { | ||||||
|  |  | ||||||
|         'click @ui.save': function (e) { |         'submit @ui.form': function (e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             this.ui.error.hide(); |             this.ui.error.hide(); | ||||||
|             let view = this; |             let view = this; | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ | |||||||
|     }, |     }, | ||||||
|     "footer": { |     "footer": { | ||||||
|       "fork-me": "Fork me on Github", |       "fork-me": "Fork me on Github", | ||||||
|       "copy": "© 2024 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.", |       "copy": "© 2025 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.", | ||||||
|       "theme": "Theme by <a href=\"{url}\" target=\"_blank\">Tabler</a>" |       "theme": "Theme by <a href=\"{url}\" target=\"_blank\">Tabler</a>" | ||||||
|     }, |     }, | ||||||
|     "dashboard": { |     "dashboard": { | ||||||
| @@ -179,7 +179,9 @@ | |||||||
|       "delete-confirm": "Are you sure you want to delete this Stream?", |       "delete-confirm": "Are you sure you want to delete this Stream?", | ||||||
|       "help-title": "What is a Stream?", |       "help-title": "What is a Stream?", | ||||||
|       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", |       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", | ||||||
|       "search": "Search Incoming Port…" |       "search": "Search Incoming Port…", | ||||||
|  |       "ssl-certificate": "SSL Certificate for TCP Forwarding", | ||||||
|  |       "tcp+ssl": "TCP+SSL" | ||||||
|     }, |     }, | ||||||
|     "certificates": { |     "certificates": { | ||||||
|       "title": "SSL Certificates", |       "title": "SSL Certificates", | ||||||
| @@ -206,7 +208,10 @@ | |||||||
|       "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", |       "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", | ||||||
|       "download": "Download", |       "download": "Download", | ||||||
|       "renew-title": "Renew Let's Encrypt Certificate", |       "renew-title": "Renew Let's Encrypt Certificate", | ||||||
|       "search": "Search Certificate…" |       "search": "Search Certificate…", | ||||||
|  |       "in-use"  : "In use", | ||||||
|  |       "inactive": "Inactive", | ||||||
|  |       "active-domain_names": "Active domain names" | ||||||
|     }, |     }, | ||||||
|     "access-lists": { |     "access-lists": { | ||||||
|       "title": "Access Lists", |       "title": "Access Lists", | ||||||
|   | |||||||
| @@ -15,8 +15,11 @@ const model = Backbone.Model.extend({ | |||||||
|             udp_forwarding:  false, |             udp_forwarding:  false, | ||||||
|             enabled:         true, |             enabled:         true, | ||||||
|             meta:            {}, |             meta:            {}, | ||||||
|  |             certificate_id:  0, | ||||||
|  |             domain_names:    [], | ||||||
|             // The following are expansions: |             // The following are expansions: | ||||||
|             owner:           null |             owner:           null, | ||||||
|  |             certificate:     null | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -26,8 +26,8 @@ | |||||||
|     "messageformat": "^2.3.0", |     "messageformat": "^2.3.0", | ||||||
|     "messageformat-loader": "^0.8.1", |     "messageformat-loader": "^0.8.1", | ||||||
|     "mini-css-extract-plugin": "^0.9.0", |     "mini-css-extract-plugin": "^0.9.0", | ||||||
|     "moment": "^2.29.4", |     "moment": "^2.30.1", | ||||||
|     "node-sass": "^9.0.0", |     "sass": "^1.92.1", | ||||||
|     "nodemon": "^2.0.2", |     "nodemon": "^2.0.2", | ||||||
|     "numeral": "^2.0.6", |     "numeral": "^2.0.6", | ||||||
|     "sass-loader": "^10.0.0", |     "sass-loader": "^10.0.0", | ||||||
|   | |||||||
| @@ -167,4 +167,5 @@ $pink: #f66d9b; | |||||||
|  |  | ||||||
| textarea.form-control.text-monospace { | textarea.form-control.text-monospace { | ||||||
|     font-size: 12px; |     font-size: 12px; | ||||||
|  |     font-family: monospace; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1491
									
								
								frontend/yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										1491
									
								
								frontend/yarn.lock
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -7,18 +7,18 @@ | |||||||
| 		"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json", | 		"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json", | ||||||
| 		"full_plugin_name": "dns-acmedns" | 		"full_plugin_name": "dns-acmedns" | ||||||
| 	}, | 	}, | ||||||
| 	"active24":{ | 	"active24": { | ||||||
| 		"name": "Active24", | 		"name": "Active24", | ||||||
| 		"package_name": "certbot-dns-active24", | 		"package_name": "certbot-dns-active24", | ||||||
| 		"version": "~=1.5.1", | 		"version": "~=2.0.0", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_active24_token=\"TOKEN\"", | 		"credentials": "dns_active24_api_key = <identifier>\ndns_active24_secret = <secret>", | ||||||
| 		"full_plugin_name": "dns-active24" | 		"full_plugin_name": "dns-active24" | ||||||
| 	}, | 	}, | ||||||
| 	"aliyun": { | 	"aliyun": { | ||||||
| 		"name": "Aliyun", | 		"name": "Aliyun", | ||||||
| 		"package_name": "certbot-dns-aliyun", | 		"package_name": "certbot-dns-aliyun", | ||||||
| 		"version": "~=0.38.1", | 		"version": "~=2.0.0", | ||||||
| 		"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,6 +31,22 @@ | |||||||
| 		"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" | ||||||
| 	}, | 	}, | ||||||
|  | 	"baidu": { | ||||||
|  | 		"name": "baidu", | ||||||
|  | 		"package_name": "certbot-dns-baidu", | ||||||
|  | 		"version": "~=0.1.1", | ||||||
|  | 		"dependencies": "", | ||||||
|  | 		"credentials": "dns_baidu_access_key = 12345678\ndns_baidu_secret_key = 1234567890abcdef1234567890abcdef", | ||||||
|  | 		"full_plugin_name": "dns-baidu" | ||||||
|  | 	}, | ||||||
|  | 	"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", | ||||||
| @@ -39,11 +55,19 @@ | |||||||
| 		"credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", | 		"credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", | ||||||
| 		"full_plugin_name": "dns-bunny" | 		"full_plugin_name": "dns-bunny" | ||||||
| 	}, | 	}, | ||||||
|  | 	"cdmon": { | ||||||
|  | 		"name": "cdmon", | ||||||
|  | 		"package_name": "certbot-dns-cdmon", | ||||||
|  | 		"version": "~=0.4.1", | ||||||
|  | 		"dependencies": "", | ||||||
|  | 		"credentials": "dns_cdmon_api_key=your-cdmon-api-token\ndns_cdmon_domain=your_domain_is_optional", | ||||||
|  | 		"full_plugin_name": "dns-cdmon" | ||||||
|  | 	}, | ||||||
| 	"cloudflare": { | 	"cloudflare": { | ||||||
| 		"name": "Cloudflare", | 		"name": "Cloudflare", | ||||||
| 		"package_name": "certbot-dns-cloudflare", | 		"package_name": "certbot-dns-cloudflare", | ||||||
| 		"version": "=={{certbot-version}}", | 		"version": "=={{certbot-version}}", | ||||||
| 		"dependencies": "cloudflare==2.19.* acme=={{certbot-version}}", | 		"dependencies": "acme=={{certbot-version}}", | ||||||
| 		"credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567", | 		"credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567", | ||||||
| 		"full_plugin_name": "dns-cloudflare" | 		"full_plugin_name": "dns-cloudflare" | ||||||
| 	}, | 	}, | ||||||
| @@ -82,11 +106,19 @@ | |||||||
| 	"cpanel": { | 	"cpanel": { | ||||||
| 		"name": "cPanel", | 		"name": "cPanel", | ||||||
| 		"package_name": "certbot-dns-cpanel", | 		"package_name": "certbot-dns-cpanel", | ||||||
| 		"version": "~=0.2.2", | 		"version": "~=0.4.0", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = user\ncpanel_password = hunter2", | 		"credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = your_username\ncpanel_password = your_password\ncpanel_token = your_api_token", | ||||||
| 		"full_plugin_name": "cpanel" | 		"full_plugin_name": "cpanel" | ||||||
| 	}, | 	}, | ||||||
|  | 	"ddnss": { | ||||||
|  | 		"name": "DDNSS", | ||||||
|  | 		"package_name": "certbot-dns-ddnss", | ||||||
|  | 		"version": "~=1.1.0", | ||||||
|  | 		"dependencies": "", | ||||||
|  | 		"credentials": "dns_ddnss_token = YOUR_DDNSS_API_TOKEN", | ||||||
|  | 		"full_plugin_name": "dns-ddnss" | ||||||
|  | 	}, | ||||||
| 	"desec": { | 	"desec": { | ||||||
| 		"name": "deSEC", | 		"name": "deSEC", | ||||||
| 		"package_name": "certbot-dns-desec", | 		"package_name": "certbot-dns-desec", | ||||||
| @@ -153,11 +185,11 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"domainoffensive": { | 	"domainoffensive": { | ||||||
| 		"name": "DomainOffensive (do.de)", | 		"name": "DomainOffensive (do.de)", | ||||||
| 		"package_name": "certbot-dns-do", | 		"package_name": "certbot-dns-domainoffensive", | ||||||
| 		"version": "~=0.31.0", | 		"version": "~=2.0.0", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN", | 		"credentials": "dns_domainoffensive_api_token = YOUR_DO_DE_AUTH_TOKEN", | ||||||
| 		"full_plugin_name": "dns-do" | 		"full_plugin_name": "dns-domainoffensive" | ||||||
| 	}, | 	}, | ||||||
| 	"domeneshop": { | 	"domeneshop": { | ||||||
| 		"name": "Domeneshop", | 		"name": "Domeneshop", | ||||||
| @@ -191,6 +223,14 @@ | |||||||
| 		"credentials": "dns_eurodns_applicationId = myuser\ndns_eurodns_apiKey = mysecretpassword\ndns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy", | 		"credentials": "dns_eurodns_applicationId = myuser\ndns_eurodns_apiKey = mysecretpassword\ndns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy", | ||||||
| 		"full_plugin_name": "dns-eurodns" | 		"full_plugin_name": "dns-eurodns" | ||||||
| 	}, | 	}, | ||||||
|  | 	"firstdomains": { | ||||||
|  |                 "name": "First Domains", | ||||||
|  |                 "package_name": "certbot-dns-firstdomains", | ||||||
|  |                 "version": ">=1.0", | ||||||
|  |                 "dependencies": "", | ||||||
|  |                 "credentials": "dns_firstdomains_username = myremoteuser\ndns_firstdomains_password = verysecureremoteuserpassword", | ||||||
|  |                 "full_plugin_name": "dns-firstdomains" | ||||||
|  |         }, | ||||||
| 	"freedns": { | 	"freedns": { | ||||||
| 		"name": "FreeDNS", | 		"name": "FreeDNS", | ||||||
| 		"package_name": "certbot-dns-freedns", | 		"package_name": "certbot-dns-freedns", | ||||||
| @@ -201,12 +241,20 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"gandi": { | 	"gandi": { | ||||||
| 		"name": "Gandi Live DNS", | 		"name": "Gandi Live DNS", | ||||||
| 		"package_name": "certbot_plugin_gandi", | 		"package_name": "certbot-dns-gandi", | ||||||
| 		"version": "~=1.5.0", | 		"version": "~=1.6.1", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN", | 		"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN", | ||||||
| 		"full_plugin_name": "dns-gandi" | 		"full_plugin_name": "dns-gandi" | ||||||
| 	}, | 	}, | ||||||
|  | 	"gcore": { | ||||||
|  | 		"name": "Gcore DNS", | ||||||
|  | 		"package_name": "certbot-dns-gcore", | ||||||
|  | 		"version": "~=0.1.8", | ||||||
|  | 		"dependencies": "", | ||||||
|  | 		"credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567", | ||||||
|  | 		"full_plugin_name": "dns-gcore" | ||||||
|  | 	}, | ||||||
| 	"godaddy": { | 	"godaddy": { | ||||||
| 		"name": "GoDaddy", | 		"name": "GoDaddy", | ||||||
| 		"package_name": "certbot-dns-godaddy", | 		"package_name": "certbot-dns-godaddy", | ||||||
| @@ -247,6 +295,14 @@ | |||||||
| 		"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", | ||||||
| @@ -340,7 +396,7 @@ | |||||||
| 		"package_name": "certbot-dns-mijn-host", | 		"package_name": "certbot-dns-mijn-host", | ||||||
| 		"version": "~=0.0.4", | 		"version": "~=0.0.4", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns-mijn-host-credentials = /etc/letsencrypt/mijnhost-credentials.ini", | 		"credentials": "dns_mijn_host_api_key=0123456789abcdef0123456789abcdef", | ||||||
| 		"full_plugin_name": "dns-mijn-host" | 		"full_plugin_name": "dns-mijn-host" | ||||||
| 	}, | 	}, | ||||||
| 	"namecheap": { | 	"namecheap": { | ||||||
| @@ -359,6 +415,14 @@ | |||||||
| 		"credentials": "dns_netcup_customer_id  = 123456\ndns_netcup_api_key      = 0123456789abcdef0123456789abcdef01234567\ndns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123", | 		"credentials": "dns_netcup_customer_id  = 123456\ndns_netcup_api_key      = 0123456789abcdef0123456789abcdef01234567\ndns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123", | ||||||
| 		"full_plugin_name": "dns-netcup" | 		"full_plugin_name": "dns-netcup" | ||||||
| 	}, | 	}, | ||||||
|  | 	"nicru": { | ||||||
|  | 		"name": "nic.ru", | ||||||
|  | 		"package_name": "certbot-dns-nicru", | ||||||
|  | 		"version": "~=1.0.3", | ||||||
|  | 		"dependencies": "", | ||||||
|  | 		"credentials": "dns_nicru_client_id = application-id\ndns_nicru_client_secret = application-token\ndns_nicru_username = 0001110/NIC-D\ndns_nicru_password = password\ndns_nicru_scope = .+:.+/zones/example.com(/.+)?\ndns_nicru_service = DNS_SERVICE_NAME\ndns_nicru_zone = example.com", | ||||||
|  | 		"full_plugin_name": "dns-nicru" | ||||||
|  | 	}, | ||||||
| 	"njalla": { | 	"njalla": { | ||||||
| 		"name": "Njalla", | 		"name": "Njalla", | ||||||
| 		"package_name": "certbot-dns-njalla", | 		"package_name": "certbot-dns-njalla", | ||||||
| @@ -402,7 +466,7 @@ | |||||||
| 	"porkbun": { | 	"porkbun": { | ||||||
| 		"name": "Porkbun", | 		"name": "Porkbun", | ||||||
| 		"package_name": "certbot-dns-porkbun", | 		"package_name": "certbot-dns-porkbun", | ||||||
| 		"version": "~=0.2", | 		"version": "~=0.9", | ||||||
| 		"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" | ||||||
| @@ -447,14 +511,30 @@ | |||||||
| 		"credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", | 		"credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", | ||||||
| 		"full_plugin_name": "dns-route53" | 		"full_plugin_name": "dns-route53" | ||||||
| 	}, | 	}, | ||||||
|  | 	"spaceship": { | ||||||
|  | 		"name": "Spaceship", | ||||||
|  | 		"package_name": "certbot-dns-spaceship", | ||||||
|  | 		"version": "~=1.0.4", | ||||||
|  | 		"dependencies": "", | ||||||
|  | 		"credentials": "[spaceship]\napi_key=your_api_key\napi_secret=your_api_secret", | ||||||
|  | 		"full_plugin_name": "dns-spaceship" | ||||||
|  | 	}, | ||||||
| 	"strato": { | 	"strato": { | ||||||
| 		"name": "Strato", | 		"name": "Strato", | ||||||
| 		"package_name": "certbot-dns-strato", | 		"package_name": "certbot-dns-strato", | ||||||
| 		"version": "~=0.2.1", | 		"version": "~=0.2.2", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_strato_username = user\ndns_strato_password = pass\n# uncomment if youre using two factor authentication:\n# dns_strato_totp_devicename = 2fa_device\n# dns_strato_totp_secret = 2fa_secret\n#\n# uncomment if domain name contains special characters\n# insert domain display name as seen on your account page here\n# dns_strato_domain_display_name = my-punicode-url.de\n#\n# if youre not using strato.de or another special endpoint you can customise it below\n# you will probably only need to adjust the host, but you can also change the complete endpoint url\n# dns_strato_custom_api_scheme = https\n# dns_strato_custom_api_host = www.strato.de\n# dns_strato_custom_api_port = 443\n# dns_strato_custom_api_path = \"/apps/CustomerService\"", | 		"credentials": "dns_strato_username = user\ndns_strato_password = pass\n# uncomment if youre using two factor authentication:\n# dns_strato_totp_devicename = 2fa_device\n# dns_strato_totp_secret = 2fa_secret\n#\n# uncomment if domain name contains special characters\n# insert domain display name as seen on your account page here\n# dns_strato_domain_display_name = my-punicode-url.de\n#\n# if youre not using strato.de or another special endpoint you can customise it below\n# you will probably only need to adjust the host, but you can also change the complete endpoint url\n# dns_strato_custom_api_scheme = https\n# dns_strato_custom_api_host = www.strato.de\n# dns_strato_custom_api_port = 443\n# dns_strato_custom_api_path = \"/apps/CustomerService\"", | ||||||
| 		"full_plugin_name": "dns-strato" | 		"full_plugin_name": "dns-strato" | ||||||
| 	}, | 	}, | ||||||
|  | 	        "selectelv2": { | ||||||
|  |                 "name": "Selectel api v2", | ||||||
|  |                 "package_name": "certbot-dns-selectel-api-v2", | ||||||
|  |                 "version": "~=0.3.0", | ||||||
|  |                 "dependencies": "", | ||||||
|  |                 "credentials": "dns_selectel_api_v2_account_id = your_account_id\ndns_selectel_api_v2_project_name = your_project\ndns_selectel_api_v2_username = your_username\ndns_selectel_api_v2_password = your_password", | ||||||
|  |                 "full_plugin_name": "dns-selectel-api-v2" | ||||||
|  |         }, | ||||||
| 	"timeweb": { | 	"timeweb": { | ||||||
| 		"name": "Timeweb Cloud", | 		"name": "Timeweb Cloud", | ||||||
| 		"package_name": "certbot-dns-timeweb", | 		"package_name": "certbot-dns-timeweb", | ||||||
| @@ -495,7 +575,7 @@ | |||||||
| 		"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>", | 		"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>", | ||||||
| 		"full_plugin_name": "dns-websupport" | 		"full_plugin_name": "dns-websupport" | ||||||
| 	}, | 	}, | ||||||
| 	"wedos":{ | 	"wedos": { | ||||||
| 		"name": "Wedos", | 		"name": "Wedos", | ||||||
| 		"package_name": "certbot-dns-wedos", | 		"package_name": "certbot-dns-wedos", | ||||||
| 		"version": "~=2.2", | 		"version": "~=2.2", | ||||||
| @@ -510,5 +590,13 @@ | |||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf", | 		"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf", | ||||||
| 		"full_plugin_name": "edgedns" | 		"full_plugin_name": "edgedns" | ||||||
| 	} | 	}, | ||||||
|  | 	"zoneedit": { | ||||||
|  | 		"name": "ZoneEdit", | ||||||
|  | 		"package_name": "certbot-dns-zoneedit", | ||||||
|  | 		"version": "~=0.3.2", | ||||||
|  | 		"dependencies": "--no-deps dnspython", | ||||||
|  | 		"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>", | ||||||
|  | 		"full_plugin_name": "dns-zoneedit" | ||||||
|  |  	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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="npmdev" | COMPOSE_PROJECT_NAME="npm2dev" | ||||||
| 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,6 +67,8 @@ 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,12 +36,11 @@ 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 | 	docker-compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap | ||||||
| 	docker-compose up -d --remove-orphans --pull=never fullstack | 	docker-compose build --pull --parallel 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 | ||||||
|  |  | ||||||
| @@ -53,10 +52,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 npm_core | 		docker logs -f npm2dev.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 npm_core" | 		echo "  docker logs -f npm2dev.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,11 +1,22 @@ | |||||||
| FROM cypress/included:13.9.0 | FROM cypress/included:14.0.1 | ||||||
|  |  | ||||||
| COPY --chown=1000 ./test /test |  | ||||||
|  |  | ||||||
| # Disable Cypress CLI colors | # Disable Cypress CLI colors | ||||||
| ENV FORCE_COLOR=0 | ENV FORCE_COLOR=0 | ||||||
| ENV NO_COLOR=1 | ENV NO_COLOR=1 | ||||||
|  |  | ||||||
|  | # testssl.sh and mkcert | ||||||
|  | RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \ | ||||||
|  | 	&& tar -xzf /tmp/testssl.tgz -C /tmp \ | ||||||
|  | 	&& mv /tmp/testssl.sh-3.2rc4 /testssl \ | ||||||
|  | 	&& rm /tmp/testssl.tgz \ | ||||||
|  | 	&& apt-get update \ | ||||||
|  | 	&& apt-get install -y bsdmainutils curl dnsutils \ | ||||||
|  | 	&& apt-get clean \ | ||||||
|  | 	&& rm -rf /var/lib/apt/lists/* \ | ||||||
|  | 	&& wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \ | ||||||
|  | 	&& chmod +x /bin/mkcert | ||||||
|  |  | ||||||
|  | COPY --chown=1000 ./test /test | ||||||
| WORKDIR /test | WORKDIR /test | ||||||
| RUN yarn install && yarn cache clean | RUN yarn install && yarn cache clean | ||||||
| ENTRYPOINT [] | ENTRYPOINT [] | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ describe('Certificates endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Validate custom certificate', function() { | 	it('Validate custom certificate', () => { | ||||||
| 		cy.task('backendApiPostFiles', { | 		cy.task('backendApiPostFiles', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates/validate', | 			path:  '/api/nginx/certificates/validate', | ||||||
| @@ -25,7 +25,7 @@ describe('Certificates endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Custom certificate lifecycle', function() { | 	it('Custom certificate lifecycle', () => { | ||||||
| 		// Create custom cert | 		// Create custom cert | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| @@ -73,7 +73,7 @@ describe('Certificates endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Request Certificate - CVE-2024-46256/CVE-2024-46257', function() { | 	it('Request Certificate - CVE-2024-46256/CVE-2024-46257', () => { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates', | 			path:  '/api/nginx/certificates', | ||||||
| @@ -96,4 +96,28 @@ describe('Certificates endpoints', () => { | |||||||
| 			expect(data.error.message).to.contain('data/domain_names/0 must match pattern'); | 			expect(data.error.message).to.contain('data/domain_names/0 must match pattern'); | ||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	it('Request Certificate - LE Email Escaped', () => { | ||||||
|  | 		cy.task('backendApiPost', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/nginx/certificates', | ||||||
|  | 			data:  { | ||||||
|  | 				domain_names: ['test.com"||echo hello-world||\\\\n test.com"'], | ||||||
|  | 				meta:         { | ||||||
|  | 					dns_challenge:     false, | ||||||
|  | 					letsencrypt_agree: true, | ||||||
|  | 					letsencrypt_email: "admin@example.com' --version;echo hello-world", | ||||||
|  | 				}, | ||||||
|  | 				provider: 'letsencrypt', | ||||||
|  | 			}, | ||||||
|  | 			returnOnError: true, | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('post', 400, '/nginx/certificates', data); | ||||||
|  | 			expect(data).to.have.property('error'); | ||||||
|  | 			expect(data.error).to.have.property('message'); | ||||||
|  | 			expect(data.error).to.have.property('code'); | ||||||
|  | 			expect(data.error.code).to.equal(400); | ||||||
|  | 			expect(data.error.message).to.contain('data/meta/letsencrypt_email must match pattern'); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								test/cypress/e2e/api/Dashboard.cy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								test/cypress/e2e/api/Dashboard.cy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | /// <reference types="cypress" /> | ||||||
|  |  | ||||||
|  | describe('Dashboard endpoints', () => { | ||||||
|  | 	let token; | ||||||
|  |  | ||||||
|  | 	before(() => { | ||||||
|  | 		cy.getToken().then((tok) => { | ||||||
|  | 			token = tok; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('Should be able to get host counts', () => { | ||||||
|  | 		cy.task('backendApiGet', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/reports/hosts' | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('get', 200, '/reports/hosts', data); | ||||||
|  | 			expect(data).to.have.property('dead'); | ||||||
|  | 			expect(data).to.have.property('proxy'); | ||||||
|  | 			expect(data).to.have.property('redirection'); | ||||||
|  | 			expect(data).to.have.property('stream'); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | }); | ||||||
| @@ -9,7 +9,7 @@ describe('Full Certificate Provisions', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should be able to create new http certificate', function() { | 	it('Should be able to create new http certificate', () => { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates', | 			path:  '/api/nginx/certificates', | ||||||
| @@ -32,7 +32,7 @@ describe('Full Certificate Provisions', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should be able to create new DNS certificate with Powerdns', function() { | 	it('Should be able to create new DNS certificate with Powerdns', () => { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates', | 			path:  '/api/nginx/certificates', | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| /// <reference types="cypress" /> | /// <reference types="cypress" /> | ||||||
|  |  | ||||||
| describe('Basic API checks', () => { | describe('Basic API checks', () => { | ||||||
| 	it('Should return a valid health payload', function () { | 	it('Should return a valid health payload', () => { | ||||||
| 		cy.task('backendApiGet', { | 		cy.task('backendApiGet', { | ||||||
| 			path: '/api/', | 			path: '/api/', | ||||||
| 		}).then((data) => { | 		}).then((data) => { | ||||||
| @@ -10,9 +10,9 @@ describe('Basic API checks', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should return a valid schema payload', function () { | 	it('Should return a valid schema payload', () => { | ||||||
| 		cy.task('backendApiGet', { | 		cy.task('backendApiGet', { | ||||||
| 			path: '/api/schema?ts=' + Date.now(), | 			path: `/api/schema?ts=${Date.now()}`, | ||||||
| 		}).then((data) => { | 		}).then((data) => { | ||||||
| 			expect(data.openapi).to.be.equal('3.1.0'); | 			expect(data.openapi).to.be.equal('3.1.0'); | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								test/cypress/e2e/api/Ldap.cy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								test/cypress/e2e/api/Ldap.cy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | /// <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', () => { | ||||||
|  | 			// 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'); | ||||||
|  | 			// }); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
							
								
								
									
										97
									
								
								test/cypress/e2e/api/OAuth.cy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								test/cypress/e2e/api/OAuth.cy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | /// <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', () => { | ||||||
|  | 			// 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(); | ||||||
|  | 			// }); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
| @@ -9,7 +9,7 @@ describe('Proxy Hosts endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should be able to create a http host', function() { | 	it('Should be able to create a http host', () => { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/proxy-hosts', | 			path:  '/api/nginx/proxy-hosts', | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Get all settings', function() { | 	it('Get all settings', () => { | ||||||
| 		cy.task('backendApiGet', { | 		cy.task('backendApiGet', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings', | 			path:  '/api/settings', | ||||||
| @@ -19,7 +19,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Get default-site setting', function() { | 	it('Get default-site setting', () => { | ||||||
| 		cy.task('backendApiGet', { | 		cy.task('backendApiGet', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings/default-site', | 			path:  '/api/settings/default-site', | ||||||
| @@ -30,7 +30,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Default Site congratulations', function() { | 	it('Default Site congratulations', () => { | ||||||
| 		cy.task('backendApiPut', { | 		cy.task('backendApiPut', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings/default-site', | 			path:  '/api/settings/default-site', | ||||||
| @@ -46,7 +46,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Default Site 404', function() { | 	it('Default Site 404', () => { | ||||||
| 		cy.task('backendApiPut', { | 		cy.task('backendApiPut', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings/default-site', | 			path:  '/api/settings/default-site', | ||||||
| @@ -62,7 +62,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Default Site 444', function() { | 	it('Default Site 444', () => { | ||||||
| 		cy.task('backendApiPut', { | 		cy.task('backendApiPut', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings/default-site', | 			path:  '/api/settings/default-site', | ||||||
| @@ -78,7 +78,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Default Site redirect', function() { | 	it('Default Site redirect', () => { | ||||||
| 		cy.task('backendApiPut', { | 		cy.task('backendApiPut', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings/default-site', | 			path:  '/api/settings/default-site', | ||||||
| @@ -100,7 +100,7 @@ describe('Settings endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Default Site html', function() { | 	it('Default Site html', () => { | ||||||
| 		cy.task('backendApiPut', { | 		cy.task('backendApiPut', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/settings/default-site', | 			path:  '/api/settings/default-site', | ||||||
|   | |||||||
							
								
								
									
										225
									
								
								test/cypress/e2e/api/Streams.cy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								test/cypress/e2e/api/Streams.cy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | |||||||
|  | /// <reference types="cypress" /> | ||||||
|  |  | ||||||
|  | describe('Streams', () => { | ||||||
|  | 	let token; | ||||||
|  |  | ||||||
|  | 	before(() => { | ||||||
|  | 		cy.getToken().then((tok) => { | ||||||
|  | 			token = tok; | ||||||
|  | 			// Set default site content | ||||||
|  | 			cy.task('backendApiPut', { | ||||||
|  | 				token: token, | ||||||
|  | 				path:  '/api/settings/default-site', | ||||||
|  | 				data: { | ||||||
|  | 					value: 'html', | ||||||
|  | 					meta: { | ||||||
|  | 						html: '<p>yay it works</p>' | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}).then((data) => { | ||||||
|  | 				cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		// Create a custom cert pair | ||||||
|  | 		cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => { | ||||||
|  | 			expect(result.code).to.eq(0); | ||||||
|  | 			// Install CA | ||||||
|  | 			cy.exec('mkcert -install').then((result) => { | ||||||
|  | 				expect(result.code).to.eq(0); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		cy.exec('rm -f /test/results/testssl.json'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('Should be able to create TCP Stream', () => { | ||||||
|  | 		cy.task('backendApiPost', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/nginx/streams', | ||||||
|  | 			data:  { | ||||||
|  | 				incoming_port: 1500, | ||||||
|  | 				forwarding_host: '127.0.0.1', | ||||||
|  | 				forwarding_port: 80, | ||||||
|  | 				certificate_id: 0, | ||||||
|  | 				meta: { | ||||||
|  | 					dns_provider_credentials: "", | ||||||
|  | 					letsencrypt_agree: false, | ||||||
|  | 					dns_challenge: true | ||||||
|  | 				}, | ||||||
|  | 				tcp_forwarding: true, | ||||||
|  | 				udp_forwarding: false | ||||||
|  | 			} | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); | ||||||
|  | 			expect(data).to.have.property('id'); | ||||||
|  | 			expect(data.id).to.be.greaterThan(0); | ||||||
|  | 			expect(data).to.have.property('enabled', true); | ||||||
|  | 			expect(data).to.have.property('tcp_forwarding', true); | ||||||
|  | 			expect(data).to.have.property('udp_forwarding', false); | ||||||
|  |  | ||||||
|  | 			cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => { | ||||||
|  | 				expect(result.code).to.eq(0); | ||||||
|  | 				expect(result.stdout).to.contain('yay it works'); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('Should be able to create UDP Stream', () => { | ||||||
|  | 		cy.task('backendApiPost', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/nginx/streams', | ||||||
|  | 			data:  { | ||||||
|  | 				incoming_port: 1501, | ||||||
|  | 				forwarding_host: '127.0.0.1', | ||||||
|  | 				forwarding_port: 80, | ||||||
|  | 				certificate_id: 0, | ||||||
|  | 				meta: { | ||||||
|  | 					dns_provider_credentials: "", | ||||||
|  | 					letsencrypt_agree: false, | ||||||
|  | 					dns_challenge: true | ||||||
|  | 				}, | ||||||
|  | 				tcp_forwarding: false, | ||||||
|  | 				udp_forwarding: true | ||||||
|  | 			} | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); | ||||||
|  | 			expect(data).to.have.property('id'); | ||||||
|  | 			expect(data.id).to.be.greaterThan(0); | ||||||
|  | 			expect(data).to.have.property('enabled', true); | ||||||
|  | 			expect(data).to.have.property('tcp_forwarding', false); | ||||||
|  | 			expect(data).to.have.property('udp_forwarding', true); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('Should be able to create TCP/UDP Stream', () => { | ||||||
|  | 		cy.task('backendApiPost', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/nginx/streams', | ||||||
|  | 			data:  { | ||||||
|  | 				incoming_port: 1502, | ||||||
|  | 				forwarding_host: '127.0.0.1', | ||||||
|  | 				forwarding_port: 80, | ||||||
|  | 				certificate_id: 0, | ||||||
|  | 				meta: { | ||||||
|  | 					dns_provider_credentials: "", | ||||||
|  | 					letsencrypt_agree: false, | ||||||
|  | 					dns_challenge: true | ||||||
|  | 				}, | ||||||
|  | 				tcp_forwarding: true, | ||||||
|  | 				udp_forwarding: true | ||||||
|  | 			} | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); | ||||||
|  | 			expect(data).to.have.property('id'); | ||||||
|  | 			expect(data.id).to.be.greaterThan(0); | ||||||
|  | 			expect(data).to.have.property('enabled', true); | ||||||
|  | 			expect(data).to.have.property('tcp_forwarding', true); | ||||||
|  | 			expect(data).to.have.property('udp_forwarding', true); | ||||||
|  |  | ||||||
|  | 			cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => { | ||||||
|  | 				expect(result.code).to.eq(0); | ||||||
|  | 				expect(result.stdout).to.contain('yay it works'); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('Should be able to create SSL TCP Stream', () => { | ||||||
|  | 		let certID = 0; | ||||||
|  |  | ||||||
|  | 		// Create custom cert | ||||||
|  | 		cy.task('backendApiPost', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/nginx/certificates', | ||||||
|  | 			data:  { | ||||||
|  | 				provider: "other", | ||||||
|  | 				nice_name: "Custom Certificate for SSL Stream", | ||||||
|  | 			}, | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data); | ||||||
|  | 			expect(data).to.have.property('id'); | ||||||
|  | 			certID = data.id; | ||||||
|  |  | ||||||
|  | 			// Upload files | ||||||
|  | 			cy.task('backendApiPostFiles', { | ||||||
|  | 				token: token, | ||||||
|  | 				path:  `/api/nginx/certificates/${certID}/upload`, | ||||||
|  | 				files:  { | ||||||
|  | 					certificate: 'website1.pem', | ||||||
|  | 					certificate_key: 'website1.key.pem', | ||||||
|  | 				}, | ||||||
|  | 			}).then((data) => { | ||||||
|  | 				cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data); | ||||||
|  | 				expect(data).to.have.property('certificate'); | ||||||
|  | 				expect(data).to.have.property('certificate_key'); | ||||||
|  |  | ||||||
|  | 				// Create the stream | ||||||
|  | 				cy.task('backendApiPost', { | ||||||
|  | 					token: token, | ||||||
|  | 					path:  '/api/nginx/streams', | ||||||
|  | 					data:  { | ||||||
|  | 						incoming_port: 1503, | ||||||
|  | 						forwarding_host: '127.0.0.1', | ||||||
|  | 						forwarding_port: 80, | ||||||
|  | 						certificate_id: certID, | ||||||
|  | 						meta: { | ||||||
|  | 							dns_provider_credentials: "", | ||||||
|  | 							letsencrypt_agree: false, | ||||||
|  | 							dns_challenge: true | ||||||
|  | 						}, | ||||||
|  | 						tcp_forwarding: true, | ||||||
|  | 						udp_forwarding: false | ||||||
|  | 					} | ||||||
|  | 				}).then((data) => { | ||||||
|  | 					cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); | ||||||
|  | 					expect(data).to.have.property('id'); | ||||||
|  | 					expect(data.id).to.be.greaterThan(0); | ||||||
|  | 					expect(data).to.have.property("enabled", true); | ||||||
|  | 					expect(data).to.have.property('tcp_forwarding', true); | ||||||
|  | 					expect(data).to.have.property('udp_forwarding', false); | ||||||
|  | 					expect(data).to.have.property('certificate_id', certID); | ||||||
|  |  | ||||||
|  | 					// Check the ssl termination | ||||||
|  | 					cy.task('log', '[testssl.sh] Running ...'); | ||||||
|  | 					cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', { | ||||||
|  | 						timeout: 120000, // 2 minutes | ||||||
|  | 					}).then((result) => { | ||||||
|  | 						cy.task('log', `[testssl.sh] ${result.stdout}`); | ||||||
|  |  | ||||||
|  | 						const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"]; | ||||||
|  | 						const ignoredIDs = [ | ||||||
|  | 							'cert_chain_of_trust', | ||||||
|  | 							'cert_extlifeSpan', | ||||||
|  | 							'cert_revocation', | ||||||
|  | 							'overall_grade', | ||||||
|  | 						]; | ||||||
|  |  | ||||||
|  | 						cy.readFile('/test/results/testssl.json').then((data) => { | ||||||
|  | 							// Parse each array item | ||||||
|  | 							for (let i = 0; i < data.length; i++) { | ||||||
|  | 								const item = data[i]; | ||||||
|  | 								if (ignoredIDs.includes(item.id)) { | ||||||
|  | 									continue; | ||||||
|  | 								} | ||||||
|  | 								expect(item.severity).to.be.oneOf(allowedSeverities); | ||||||
|  | 							} | ||||||
|  | 						}); | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('Should be able to List Streams', () => { | ||||||
|  | 		cy.task('backendApiGet', { | ||||||
|  | 			token: token, | ||||||
|  | 			path:  '/api/nginx/streams?expand=owner,certificate', | ||||||
|  | 		}).then((data) => { | ||||||
|  | 			cy.validateSwaggerSchema('get', 200, '/nginx/streams', data); | ||||||
|  | 			expect(data.length).to.be.greaterThan(0); | ||||||
|  | 			expect(data[0]).to.have.property('id'); | ||||||
|  | 			expect(data[0]).to.have.property('enabled'); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | }); | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user