Compare commits

..

56 Commits

Author SHA1 Message Date
28f72086ec Merge pull request #592 from jc21/develop
v2.5.0
2020-09-04 09:07:47 +10:00
a6b9bd7b01 Version bump and contributors 2020-09-03 14:11:44 +10:00
2c5eac9dad Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2020-09-03 14:03:43 +10:00
87f61b8527 Merge pull request #572 from jipjan/features/dns-cloudflare
Add DNS CloudFlare with wildcard support
2020-09-03 14:01:05 +10:00
74bfe490c6 Merge pull request #587 from duhruh/bug/custom-ssl-inputs
Allow inputs to update
2020-09-03 13:53:17 +10:00
015167f34d Allow inputs to update 2020-08-29 20:24:51 -07:00
4bafc7ff1a Merge pull request #546 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.21.0
Bump prismjs from 1.20.0 to 1.21.0 in /docs
2020-08-25 10:51:11 +10:00
bf8beb50b4 Merge pull request #559 from jlesage/remove-webroot-certbot-arg
Removed the hardcoded `--webroot` certbot argument to better support DNS challenge
2020-08-25 08:44:00 +10:00
e5034a34f5 Merge pull request #570 from jc21/dependabot/npm_and_yarn/backend/bcrypt-5.0.0
Bump bcrypt from 4.0.1 to 5.0.0 in /backend
2020-08-25 08:31:48 +10:00
a561605653 show in ssl certificates list that CloudFlare is used 2020-08-24 09:09:52 +00:00
e8596c1554 cloudflare DNS also possible while adding proxy, redirection and 404 2020-08-24 09:00:00 +00:00
ab67481e99 fix eslint errors 2020-08-23 18:56:25 +00:00
1b611e67c8 Merge commit 'c5aa2b9f771cbd4c78c239ed0791aeb8d9e4d2e4' into features/dns-cloudflare 2020-08-23 18:30:07 +00:00
c5aa2b9f77 add cloudflare renew and make revoke working for both by deleting unnecessary config command 2020-08-23 18:29:16 +00:00
cff6c4d1f5 - prevent wildcard generation when not using Cloudflare dns
- fix cloudflare token required logic
2020-08-23 16:48:14 +00:00
077cf75ef2 wildcard support 2020-08-23 13:24:20 +00:00
ff1770204c request via cloudflare dns working 2020-08-23 12:50:41 +00:00
b9a95840e0 add cloudflare dns option to letsencrypt via manual certificate 2020-08-23 11:40:41 +00:00
2d7576c57e add cloudflare dns also to dev docker file 2020-08-23 10:54:36 +00:00
251aac716a Add CloudFlare DNS plugin to certbot 2020-08-21 09:49:43 +02:00
6694a42270 Merge pull request #560 from jlesage/remove-from-unixtime
Removed usage of `FROM_UNIXTIME` mysql-specific function.
2020-08-21 14:21:40 +10:00
f78a4c6ad1 Bump bcrypt from 4.0.1 to 5.0.0 in /backend
Bumps [bcrypt](https://github.com/kelektiv/node.bcrypt.js) from 4.0.1 to 5.0.0.
- [Release notes](https://github.com/kelektiv/node.bcrypt.js/releases)
- [Changelog](https://github.com/kelektiv/node.bcrypt.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kelektiv/node.bcrypt.js/compare/v4.0.1...v5.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-20 17:01:00 +00:00
83fad8bcda Removed usage of FROM_UNIXTIME mysql-specific function.
This provide better interoperability with different databases (e.g. sqlite).
Fixes #557
2020-08-14 19:31:05 -04:00
f539e813aa Removed the hardcoded --webroot certbot argument to better support DNS challenge. Also, this option is already set in the default letsencrypt.ini. 2020-08-14 14:28:03 -04:00
5d65166777 Ignore local subnets for real IP determination 2020-08-12 09:32:40 +10:00
70346138a7 Bump prismjs from 1.20.0 to 1.21.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.20.0 to 1.21.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.20.0...v1.21.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-08 00:02:04 +00:00
d68656559c Merge pull request #544 from jlesage/sqlite-now-helper-fix
Fixed now_helper for sqlite (time is missing)
2020-08-07 08:37:00 +10:00
01660b5b80 Fixed now_helper for sqlite: it should also returns the time. 2020-08-06 17:16:22 -04:00
74010acd85 Merge pull request #543 from jc21/develop
v2.4.0
2020-08-06 16:00:10 +10:00
7c7d255172 Added another contributor 2020-08-06 14:46:19 +10:00
058f1e9835 Merge pull request #464 from vrenjith/patch-1
Update location-item.ejs - forward_host size increase to 200
2020-08-06 14:45:09 +10:00
b4fc629ec0 Bumped version 2020-08-06 14:43:34 +10:00
ae06b2da75 Updated deps and added contributor 2020-08-06 14:40:54 +10:00
54d423a11f Updated doc for sqlite 2020-08-06 14:27:29 +10:00
5da6c97a00 Pull cypress tests from correct location 2020-08-06 13:57:33 +10:00
bf2f13443f Cypress fixes 2020-08-06 12:47:24 +10:00
9ce4c3fe2f CI fix 2020-08-06 12:02:47 +10:00
4a07bf666d Added users cypress tests 2020-08-06 11:57:31 +10:00
5be46b4b20 Cypress fixes 2020-08-06 11:26:37 +10:00
7fd825b76b Use development config file in CI 2020-08-06 10:59:25 +10:00
b23d59dec7 Updated cypress to 4.12.1 2020-08-06 09:00:52 +10:00
492d450d26 Sqlite Tweaks
- Added cypress testing in CI for sqlite
- Cleaned up promises in setup
- Ensure check for settings is strict
2020-08-06 08:58:20 +10:00
04412f3624 Merge pull request #510 from tg44/multidb-re
Multidb - sqlite support
2020-08-06 08:33:00 +10:00
c41057b28a Revert builx push experiment 2020-07-31 09:28:45 +10:00
8312bc0100 Use same tags for experiment 2020-07-30 14:00:59 +10:00
85ac43bc5e Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2020-07-30 08:31:18 +10:00
d1a0780c7a Attempt to circumvent docker login token timeouts 2020-07-30 08:30:26 +10:00
f9b8d76527 Merge pull request #513 from jc21/dependabot/npm_and_yarn/frontend/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /frontend
2020-07-20 12:39:10 +10:00
26f00eeae4 Merge branch 'master' into dependabot/npm_and_yarn/frontend/lodash-4.17.19 2020-07-20 10:59:15 +10:00
1bc2df2178 Merge pull request #514 from jc21/dependabot/npm_and_yarn/docs/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /docs
2020-07-20 10:58:36 +10:00
8dfbcef198 Bump lodash from 4.17.15 to 4.17.19 in /docs
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 20:33:49 +00:00
6690b7735d sqlite3 and psql support 2020-07-19 20:04:29 +02:00
a9e7222e5e introduced now_helper for multidb capabilities 2020-07-19 20:03:53 +02:00
f8edeb2775 fixed migration and setup
more info: https://github.com/knex/knex/issues/2820
2020-07-19 20:02:20 +02:00
d1786fe159 Bump lodash from 4.17.15 to 4.17.19 in /frontend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 15:20:28 +00:00
157a12fb7c Update location-item.ejs 2020-06-19 01:56:16 +05:30
55 changed files with 5222 additions and 4458 deletions

View File

@ -0,0 +1,11 @@
{
"database": {
"engine": "knex-native",
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/data/database.sqlite"
}
}
}
}

View File

@ -1 +1 @@
2.3.1 2.5.0

41
Jenkinsfile vendored
View File

@ -83,23 +83,49 @@ pipeline {
''' '''
} }
} }
stage('Test') { stage('Integration Tests Sqlite') {
steps { steps {
// Bring up a stack // Bring up a stack
sh 'docker-compose up -d fullstack' sh 'docker-compose up -d fullstack-sqlite'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack) 120' sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
// Run tests // Run tests
sh 'rm -rf test/results' sh 'rm -rf test/results'
sh 'docker-compose up cypress' sh 'docker-compose up cypress-sqlite'
// Get results // Get results
sh 'docker cp -L "$(docker-compose ps -q cypress):/results" test/' sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
} }
post { post {
always { always {
// Dumps to analyze later // Dumps to analyze later
sh 'mkdir -p debug' sh 'mkdir -p debug'
sh 'docker-compose logs fullstack | gzip > debug/docker_fullstack.log.gz' sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results/junit/*'
}
}
}
stage('Integration Tests Mysql') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-mysql'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress-mysql'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz' sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
// Cypress videos and screenshot artifacts // Cypress videos and screenshot artifacts
dir(path: 'test/results') { dir(path: 'test/results') {
@ -136,8 +162,9 @@ pipeline {
} }
steps { steps {
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
// Docker Login
sh "docker login -u '${duser}' -p '${dpass}'" sh "docker login -u '${duser}' -p '${dpass}'"
// Buildx with push // Buildx with push from cache
sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}" sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
} }
} }

View File

@ -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.3.1-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.5.0-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>
@ -155,14 +155,36 @@ Special thanks to the following contributors:
<br /><sub><b>OhHeyAlan</b></sub> <br /><sub><b>OhHeyAlan</b></sub>
</a> </a>
</td> </td>
</tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/dogmatic69"> <a href="https://github.com/dogmatic69">
<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/> <img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/>
<br /><sub><b>Carl Sutton</b></sub> <br /><sub><b>Carl Sutton</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/tg44">
<img src="https://avatars0.githubusercontent.com/u/31839?s=460&u=ad32f4cadfef5e5fb09cdfa4b7b7b36a99ba6811&v=4" width="80px;" alt=""/>
<br /><sub><b>Gergő Törcsvári</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vrenjith">
<img src="https://avatars3.githubusercontent.com/u/2093241?s=460&u=96ce93a9bebabdd0a60a2dc96cd093a41d5edaba&v=4" width="80px;" alt=""/>
<br /><sub><b>vrenjith</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/duhruh">
<img src="https://avatars2.githubusercontent.com/u/1133969?s=460&u=c0691e6131ec6d516416c1c6fcedb5034f877bbe&v=4" width="80px;" alt=""/>
<br /><sub><b>David Rivera</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jipjan">
<img src="https://avatars2.githubusercontent.com/u/1384618?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Jaap-Jan de Wit</b></sub>
</a>
</td>
</tr> </tr>
</table> </table>
<!-- markdownlint-enable --> <!-- markdownlint-enable -->

View File

@ -0,0 +1,26 @@
{
"database": {
"engine": "knex-native",
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/app/backend/config/mydb.sqlite"
},
"pool": {
"min": 0,
"max": 1,
"createTimeoutMillis": 3000,
"acquireTimeoutMillis": 30000,
"idleTimeoutMillis": 30000,
"reapIntervalMillis": 1000,
"createRetryIntervalMillis": 100,
"propagateCreateError": false
},
"migrations": {
"tableName": "migrations",
"stub": "src/backend/lib/migrate_template.js",
"directory": "src/backend/migrations"
}
}
}
}

View File

@ -4,7 +4,11 @@ if (!config.has('database')) {
throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md'); throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
} }
let data = { function generateDbConfig() {
if (config.database.engine === 'knex-native') {
return config.database.knex;
} else
return {
client: config.database.engine, client: config.database.engine,
connection: { connection: {
host: config.database.host, host: config.database.host,
@ -16,7 +20,11 @@ let data = {
migrations: { migrations: {
tableName: 'migrations' tableName: 'migrations'
} }
}; };
}
let data = generateDbConfig();
if (typeof config.database.version !== 'undefined') { if (typeof config.database.version !== 'undefined') {
data.version = config.database.version; data.version = config.database.version;

View File

@ -77,7 +77,7 @@ const internalCertificate = {
.where('id', certificate.id) .where('id', certificate.id)
.andWhere('provider', 'letsencrypt') .andWhere('provider', 'letsencrypt')
.patch({ .patch({
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')') expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
}); });
}) })
.catch((err) => { .catch((err) => {
@ -141,6 +141,29 @@ const internalCertificate = {
}); });
}) })
.then((in_use_result) => { .then((in_use_result) => {
// Is CloudFlare, no config needed, so skip 3 and 5.
if (data.meta.cloudflare_use) {
return internalNginx.reload().then(() => {
// 4. Request cert
return internalCertificate.requestLetsEncryptCloudFlareDnsSsl(certificate, data.meta.cloudflare_token);
})
.then(internalNginx.reload)
.then(() => {
// 6. Re-instate previously disabled hosts
return internalCertificate.enableInUseHosts(in_use_result);
})
.then(() => {
return certificate;
})
.catch((err) => {
// In the event of failure, revert things and throw err back
return internalCertificate.enableInUseHosts(in_use_result)
.then(internalNginx.reload)
.then(() => {
throw err;
});
});
} else {
// 3. Generate the LE config // 3. Generate the LE config
return internalNginx.generateLetsEncryptRequestConfig(certificate) return internalNginx.generateLetsEncryptRequestConfig(certificate)
.then(internalNginx.reload) .then(internalNginx.reload)
@ -171,6 +194,7 @@ const internalCertificate = {
throw err; throw err;
}); });
}); });
}
}) })
.then(() => { .then(() => {
// At this point, the letsencrypt cert should exist on disk. // At this point, the letsencrypt cert should exist on disk.
@ -180,7 +204,7 @@ const internalCertificate = {
return certificateModel return certificateModel
.query() .query()
.patchAndFetchById(certificate.id, { .patchAndFetchById(certificate.id, {
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')') expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
}) })
.then((saved_row) => { .then((saved_row) => {
// Add cert data for audit log // Add cert data for audit log
@ -558,7 +582,7 @@ const internalCertificate = {
// TODO: This uses a mysql only raw function that won't translate to postgres // TODO: This uses a mysql only raw function that won't translate to postgres
return internalCertificate.update(access, { return internalCertificate.update(access, {
id: data.id, id: data.id,
expires_on: certificateModel.raw('FROM_UNIXTIME(' + validations.certificate.dates.to + ')'), expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
domain_names: [validations.certificate.cn], domain_names: [validations.certificate.cn],
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
}) })
@ -733,7 +757,6 @@ const internalCertificate = {
'--agree-tos ' + '--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' + '--email "' + certificate.meta.letsencrypt_email + '" ' +
'--preferred-challenges "dns,http" ' + '--preferred-challenges "dns,http" ' +
'--webroot ' +
'--domains "' + certificate.domain_names.join(',') + '" ' + '--domains "' + certificate.domain_names.join(',') + '" ' +
(le_staging ? '--staging' : ''); (le_staging ? '--staging' : '');
@ -748,6 +771,39 @@ const internalCertificate = {
}); });
}, },
/**
* @param {Object} certificate the certificate row
* @param {String} apiToken the cloudflare api token
* @returns {Promise}
*/
requestLetsEncryptCloudFlareDnsSsl: (certificate, apiToken) => {
logger.info('Requesting Let\'sEncrypt certificates via Cloudflare DNS for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let tokenLoc = '~/cloudflare-token';
let storeKey = 'echo "dns_cloudflare_api_token = ' + apiToken + '" > ' + tokenLoc;
let cmd =
storeKey + ' && ' +
certbot_command + ' certonly --non-interactive ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
'--dns-cloudflare --dns-cloudflare-credentials ' + tokenLoc +
(le_staging ? ' --staging' : '')
+ ' && rm ' + tokenLoc;
if (debug_mode) {
logger.info('Command:', cmd);
}
return utils.exec(cmd).then((result) => {
logger.info(result);
return result;
});
},
/** /**
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
@ -761,7 +817,9 @@ const internalCertificate = {
}) })
.then((certificate) => { .then((certificate) => {
if (certificate.provider === 'letsencrypt') { if (certificate.provider === 'letsencrypt') {
return internalCertificate.renewLetsEncryptSsl(certificate) let renewMethod = certificate.meta.cloudflare_use ? internalCertificate.renewLetsEncryptCloudFlareSsl : internalCertificate.renewLetsEncryptSsl;
return renewMethod(certificate)
.then(() => { .then(() => {
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem'); return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem');
}) })
@ -769,7 +827,7 @@ const internalCertificate = {
return certificateModel return certificateModel
.query() .query()
.patchAndFetchById(certificate.id, { .patchAndFetchById(certificate.id, {
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')') expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
}); });
}) })
.then((updated_certificate) => { .then((updated_certificate) => {
@ -815,6 +873,29 @@ const internalCertificate = {
}); });
}, },
/**
* @param {Object} certificate the certificate row
* @returns {Promise}
*/
renewLetsEncryptCloudFlareSsl: (certificate) => {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbot_command + ' renew --non-interactive ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--disable-hook-validation ' +
(le_staging ? '--staging' : '');
if (debug_mode) {
logger.info('Command:', cmd);
}
return utils.exec(cmd)
.then((result) => {
logger.info(result);
return result;
});
},
/** /**
* @param {Object} certificate the certificate row * @param {Object} certificate the certificate row
* @param {Boolean} [throw_errors] * @param {Boolean} [throw_errors]
@ -824,7 +905,6 @@ const internalCertificate = {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbot_command + ' revoke --non-interactive ' + let cmd = certbot_command + ' revoke --non-interactive ' +
'--config "' + le_config + '" ' +
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + '--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
'--delete-after-revoke ' + '--delete-after-revoke ' +
(le_staging ? '--staging' : ''); (le_staging ? '--staging' : '');

View File

@ -22,22 +22,6 @@ exports.up = function (knex/*, Promise*/) {
}) })
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] setting Table created'); logger.info('[' + migrate_name + '] setting Table created');
// TODO: add settings
let settingModel = require('../models/setting');
return settingModel
.query()
.insert({
id: 'default-site',
name: 'Default Site',
description: 'What to show when Nginx is hit with an unknown Host',
value: 'congratulations',
meta: {}
});
})
.then(() => {
logger.info('[' + migrate_name + '] Default settings added');
}); });
}; };

View File

@ -6,13 +6,14 @@ const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const AccessListAuth = require('./access_list_auth'); const AccessListAuth = require('./access_list_auth');
const AccessListClient = require('./access_list_client'); const AccessListClient = require('./access_list_client');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AccessList extends Model { class AccessList extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
@ -21,7 +22,7 @@ class AccessList extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -3,13 +3,14 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AccessListAuth extends Model { class AccessListAuth extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
@ -18,7 +19,7 @@ class AccessListAuth extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -3,13 +3,14 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AccessListClient extends Model { class AccessListClient extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
@ -18,7 +19,7 @@ class AccessListClient extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -4,13 +4,14 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AuditLog extends Model { class AuditLog extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
@ -19,7 +20,7 @@ class AuditLog extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -5,6 +5,7 @@ const bcrypt = require('bcrypt');
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
@ -24,8 +25,8 @@ function encryptPassword () {
class Auth extends Model { class Auth extends Model {
$beforeInsert (queryContext) { $beforeInsert (queryContext) {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
@ -36,7 +37,7 @@ class Auth extends Model {
} }
$beforeUpdate (queryContext) { $beforeUpdate (queryContext) {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
return encryptPassword.apply(this, queryContext); return encryptPassword.apply(this, queryContext);
} }

View File

@ -4,17 +4,18 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class Certificate extends Model { class Certificate extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for expires_on // Default for expires_on
if (typeof this.expires_on === 'undefined') { if (typeof this.expires_on === 'undefined') {
this.expires_on = Model.raw('NOW()'); this.expires_on = now();
} }
// Default for domain_names // Default for domain_names
@ -31,7 +32,7 @@ class Certificate extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== 'undefined') { if (typeof this.domain_names !== 'undefined') {

View File

@ -5,13 +5,14 @@ const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const Certificate = require('./certificate'); const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class DeadHost extends Model { class DeadHost extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === 'undefined') { if (typeof this.domain_names === 'undefined') {
@ -27,7 +28,7 @@ class DeadHost extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== 'undefined') { if (typeof this.domain_names !== 'undefined') {

View File

@ -0,0 +1,13 @@
const db = require('../db');
const config = require('config');
const Model = require('objection').Model;
Model.knex(db);
module.exports = function () {
if (config.database.knex && config.database.knex.client === 'sqlite3') {
return Model.raw('datetime(\'now\',\'localtime\')');
} else {
return Model.raw('NOW()');
}
};

View File

@ -6,13 +6,14 @@ const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const AccessList = require('./access_list'); const AccessList = require('./access_list');
const Certificate = require('./certificate'); const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class ProxyHost extends Model { class ProxyHost extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === 'undefined') { if (typeof this.domain_names === 'undefined') {
@ -28,7 +29,7 @@ class ProxyHost extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== 'undefined') { if (typeof this.domain_names !== 'undefined') {

View File

@ -5,13 +5,14 @@ const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const Certificate = require('./certificate'); const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class RedirectionHost extends Model { class RedirectionHost extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === 'undefined') { if (typeof this.domain_names === 'undefined') {
@ -27,7 +28,7 @@ class RedirectionHost extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== 'undefined') { if (typeof this.domain_names !== 'undefined') {

View File

@ -4,13 +4,14 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class Stream extends Model { class Stream extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
@ -19,7 +20,7 @@ class Stream extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -4,13 +4,14 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const UserPermission = require('./user_permission'); const UserPermission = require('./user_permission');
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class User extends Model { class User extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
// Default for roles // Default for roles
if (typeof this.roles === 'undefined') { if (typeof this.roles === 'undefined') {
@ -19,7 +20,7 @@ class User extends Model {
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -3,17 +3,18 @@
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class UserPermission extends Model { class UserPermission extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = Model.raw('NOW()'); this.created_on = now();
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
$beforeUpdate () { $beforeUpdate () {
this.modified_on = Model.raw('NOW()'); this.modified_on = now();
} }
static get name () { static get name () {

View File

@ -6,28 +6,30 @@
"dependencies": { "dependencies": {
"ajv": "^6.12.0", "ajv": "^6.12.0",
"batchflow": "^0.4.0", "batchflow": "^0.4.0",
"bcrypt": "^4.0.1", "bcrypt": "^5.0.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"config": "^3.3.1", "config": "^3.3.1",
"diskdb": "^0.1.17", "diskdb": "^0.1.17",
"express": "^4.17.1", "express": "^4.17.1",
"express-fileupload": "^1.1.6", "express-fileupload": "^1.1.9",
"gravatar": "^1.8.0", "gravatar": "^1.8.0",
"html-entities": "^1.2.1", "html-entities": "^1.2.1",
"json-schema-ref-parser": "^8.0.0", "json-schema-ref-parser": "^8.0.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"knex": "^0.20.13", "knex": "^0.20.13",
"liquidjs": "^9.11.10", "liquidjs": "^9.11.10",
"lodash": "^4.17.15", "lodash": "^4.17.19",
"moment": "^2.24.0", "moment": "^2.24.0",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"node-rsa": "^1.0.8", "node-rsa": "^1.0.8",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",
"objection": "^2.1.3", "objection": "^2.1.3",
"path": "^0.12.7", "path": "^0.12.7",
"pg": "^7.12.1",
"restler": "^3.4.0", "restler": "^3.4.0",
"signale": "^1.4.0", "signale": "^1.4.0",
"sqlite3": "^4.1.1",
"temp-write": "^4.0.0", "temp-write": "^4.0.0",
"unix-timestamp": "^0.2.0" "unix-timestamp": "^0.2.0"
}, },

View File

@ -41,6 +41,12 @@
}, },
"letsencrypt_agree": { "letsencrypt_agree": {
"type": "boolean" "type": "boolean"
},
"cloudflare_use": {
"type": "boolean"
},
"cloudflare_token": {
"type": "string"
} }
} }
} }

View File

@ -5,9 +5,15 @@ const logger = require('./logger').setup;
const userModel = require('./models/user'); const userModel = require('./models/user');
const userPermissionModel = require('./models/user_permission'); const userPermissionModel = require('./models/user_permission');
const authModel = require('./models/auth'); const authModel = require('./models/auth');
const settingModel = require('./models/setting');
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG; const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
module.exports = function () { /**
* Creates a new JWT RSA Keypair if not alread set on the config
*
* @returns {Promise}
*/
const setupJwt = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Now go and check if the jwt gpg keys have been created and if not, create them // Now go and check if the jwt gpg keys have been created and if not, create them
if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) { if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) {
@ -27,12 +33,12 @@ module.exports = function () {
} }
// Now create the keys and save them in the config. // Now create the keys and save them in the config.
let key = new NodeRSA({b: 2048}); let key = new NodeRSA({ b: 2048 });
key.generateKeyPair(); key.generateKeyPair();
config_data.jwt = { config_data.jwt = {
key: key.exportKey('private').toString(), key: key.exportKey('private').toString(),
pub: key.exportKey('public').toString() pub: key.exportKey('public').toString(),
}; };
// Write config // Write config
@ -47,7 +53,6 @@ module.exports = function () {
process.exit(0); process.exit(0);
} }
}); });
} else { } else {
// JWT key pair exists // JWT key pair exists
if (debug_mode) { if (debug_mode) {
@ -56,14 +61,20 @@ module.exports = function () {
resolve(); resolve();
} }
}) });
.then(() => { };
/**
* Creates a default admin users if one doesn't already exist in the database
*
* @returns {Promise}
*/
const setupDefaultUser = () => {
return userModel return userModel
.query() .query()
.select(userModel.raw('COUNT(`id`) as `count`')) .select(userModel.raw('COUNT(`id`) as `count`'))
.where('is_deleted', 0) .where('is_deleted', 0)
.first(); .first()
})
.then((row) => { .then((row) => {
if (!row.count) { if (!row.count) {
// Create a new user and set password // Create a new user and set password
@ -75,7 +86,7 @@ module.exports = function () {
name: 'Administrator', name: 'Administrator',
nickname: 'Admin', nickname: 'Admin',
avatar: '', avatar: '',
roles: ['admin'] roles: ['admin'],
}; };
return userModel return userModel
@ -88,12 +99,10 @@ module.exports = function () {
user_id: user.id, user_id: user.id,
type: 'password', type: 'password',
secret: 'changeme', secret: 'changeme',
meta: {} meta: {},
}) })
.then(() => { .then(() => {
return userPermissionModel return userPermissionModel.query().insert({
.query()
.insert({
user_id: user.id, user_id: user.id,
visibility: 'all', visibility: 'all',
proxy_hosts: 'manage', proxy_hosts: 'manage',
@ -101,15 +110,53 @@ module.exports = function () {
dead_hosts: 'manage', dead_hosts: 'manage',
streams: 'manage', streams: 'manage',
access_lists: 'manage', access_lists: 'manage',
certificates: 'manage' certificates: 'manage',
}); });
}); });
}) })
.then(() => { .then(() => {
logger.info('Initial setup completed'); logger.info('Initial admin setup completed');
}); });
} else if (debug_mode) { } else if (debug_mode) {
logger.debug('Admin user setup not required'); logger.debug('Admin user setup not required');
} }
}); });
}; };
/**
* Creates default settings if they don't already exist in the database
*
* @returns {Promise}
*/
const setupDefaultSettings = () => {
return settingModel
.query()
.select(settingModel.raw('COUNT(`id`) as `count`'))
.where({id: 'default-site'})
.first()
.then((row) => {
if (!row.count) {
settingModel
.query()
.insert({
id: 'default-site',
name: 'Default Site',
description: 'What to show when Nginx is hit with an unknown Host',
value: 'congratulations',
meta: {},
})
.then(() => {
logger.info('Default settings added');
});
}
if (debug_mode) {
logger.debug('Default setting setup not required');
}
});
};
module.exports = function () {
return setupJwt()
.then(setupDefaultUser)
.then(setupDefaultSettings);
};

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,8 @@ ENV NODE_ENV=production
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
&& apk update \ && apk update \
&& apk add python2 certbot jq \ && apk add python2 py-pip certbot jq \
&& pip install certbot-dns-cloudflare \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
ENV NPM_BUILD_VERSION="${BUILD_VERSION}" NPM_BUILD_COMMIT="${BUILD_COMMIT}" NPM_BUILD_DATE="${BUILD_DATE}" ENV NPM_BUILD_VERSION="${BUILD_VERSION}" NPM_BUILD_COMMIT="${BUILD_COMMIT}" NPM_BUILD_DATE="${BUILD_DATE}"

View File

@ -7,7 +7,8 @@ ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
&& apk update \ && apk update \
&& apk add python2 certbot jq \ && apk add python2 py-pip certbot jq \
&& pip install certbot-dns-cloudflare \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# Task # Task

View File

@ -2,14 +2,14 @@
version: "3" version: "3"
services: services:
fullstack: fullstack-mysql:
image: ${IMAGE}:ci-${BUILD_NUMBER} image: ${IMAGE}:ci-${BUILD_NUMBER}
environment: environment:
- NODE_ENV=development - NODE_ENV=development
- FORCE_COLOR=1 - FORCE_COLOR=1
volumes: volumes:
- npm_data:/data - npm_data:/data
- ../.jenkins/config.json:/app/config/production.json - ../.jenkins/config-mysql.json:/app/config/development.json
expose: expose:
- 81 - 81
- 80 - 80
@ -17,6 +17,19 @@ services:
depends_on: depends_on:
- db - db
fullstack-sqlite:
image: ${IMAGE}:ci-${BUILD_NUMBER}
environment:
- NODE_ENV=development
- FORCE_COLOR=1
volumes:
- npm_data:/data
- ../.jenkins/config-sqlite.json:/app/config/development.json
expose:
- 81
- 80
- 443
db: db:
image: jc21/mariadb-aria image: jc21/mariadb-aria
environment: environment:
@ -27,13 +40,24 @@ services:
volumes: volumes:
- db_data:/var/lib/mysql - db_data:/var/lib/mysql
cypress: cypress-mysql:
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER} image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
build: build:
context: ../ context: ../
dockerfile: test/cypress/Dockerfile dockerfile: test/cypress/Dockerfile
environment: environment:
CYPRESS_baseUrl: "http://fullstack:81" CYPRESS_baseUrl: "http://fullstack-mysql:81"
volumes:
- cypress-logs:/results
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
cypress-sqlite:
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
build:
context: ../
dockerfile: test/cypress/Dockerfile
environment:
CYPRESS_baseUrl: "http://fullstack-sqlite:81"
volumes: volumes:
- cypress-logs:/results - cypress-logs:/results
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json} command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}

View File

@ -60,6 +60,9 @@ http {
# Real IP Determination # Real IP Determination
# Docker subnet: # Docker subnet:
set_real_ip_from 172.0.0.0/8; set_real_ip_from 172.0.0.0/8;
# Local subnets:
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.0.0.0/8;
# NPM generated CDN ip ranges: # NPM generated CDN ip ranges:
include conf.d/include/ip_ranges.conf; include conf.d/include/ip_ranges.conf;
# always put the following 2 lines after ip subnets: # always put the following 2 lines after ip subnets:

View File

@ -4,15 +4,15 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@vuepress/plugin-google-analytics": "^1.4.0", "@vuepress/plugin-google-analytics": "^1.5.3",
"abbrev": "^1.1.1", "abbrev": "^1.1.1",
"accepts": "^1.3.7", "accepts": "^1.3.7",
"acorn": "^7.1.1", "acorn": "^7.4.0",
"agentkeepalive": "^4.1.0", "agentkeepalive": "^4.1.3",
"ajv": "^6.12.0", "ajv": "^6.12.3",
"ajv-errors": "^1.0.1", "ajv-errors": "^1.0.1",
"ajv-keywords": "^3.4.1", "ajv-keywords": "^3.5.2",
"algoliasearch": "^4.1.0", "algoliasearch": "^4.3.1",
"alphanum-sort": "^1.0.2", "alphanum-sort": "^1.0.2",
"ansi-colors": "^4.1.1", "ansi-colors": "^4.1.1",
"ansi-escapes": "^4.3.1", "ansi-escapes": "^4.3.1",
@ -30,7 +30,7 @@
"array-uniq": "^2.1.0", "array-uniq": "^2.1.0",
"array-unique": "^0.3.2", "array-unique": "^0.3.2",
"asn1": "^0.2.4", "asn1": "^0.2.4",
"asn1.js": "^5.3.0", "asn1.js": "^5.4.1",
"assert": "^2.0.0", "assert": "^2.0.0",
"assert-plus": "^1.0.0", "assert-plus": "^1.0.0",
"assign-symbols": "^2.0.2", "assign-symbols": "^2.0.2",
@ -40,11 +40,11 @@
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"atob": "^2.1.2", "atob": "^2.1.2",
"autocomplete.js": "^0.37.1", "autocomplete.js": "^0.37.1",
"autoprefixer": "^9.7.6", "autoprefixer": "^9.8.6",
"aws-sign2": "^0.7.0", "aws-sign2": "^0.7.0",
"aws4": "^1.9.1", "aws4": "^1.10.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"babel-plugin-dynamic-import-node": "^2.3.0", "babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-module-resolver": "^4.0.0", "babel-plugin-module-resolver": "^4.0.0",
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"base": "^3.0.0", "base": "^3.0.0",
@ -52,9 +52,9 @@
"batch": "^0.6.1", "batch": "^0.6.1",
"bcrypt-pbkdf": "^1.0.2", "bcrypt-pbkdf": "^1.0.2",
"big.js": "^5.2.2", "big.js": "^5.2.2",
"binary-extensions": "^2.0.0", "binary-extensions": "^2.1.0",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"bn.js": "^5.1.1", "bn.js": "^5.1.2",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"bonjour": "^3.5.0", "bonjour": "^3.5.0",
"boolbase": "^1.0.0", "boolbase": "^1.0.0",
@ -65,18 +65,18 @@
"browserify-cipher": "^1.0.1", "browserify-cipher": "^1.0.1",
"browserify-des": "^1.0.2", "browserify-des": "^1.0.2",
"browserify-rsa": "^4.0.1", "browserify-rsa": "^4.0.1",
"browserify-sign": "^4.0.4", "browserify-sign": "^4.2.1",
"browserify-zlib": "^0.2.0", "browserify-zlib": "^0.2.0",
"browserslist": "^4.11.1", "browserslist": "^4.13.0",
"buffer": "^5.5.0", "buffer": "^5.6.0",
"buffer-from": "^1.1.1", "buffer-from": "^1.1.1",
"buffer-indexof": "^1.1.1", "buffer-indexof": "^1.1.1",
"buffer-json": "^2.0.0", "buffer-json": "^2.0.0",
"buffer-xor": "^2.0.2", "buffer-xor": "^2.0.2",
"builtin-status-codes": "^3.0.0", "builtin-status-codes": "^3.0.0",
"bytes": "^3.1.0", "bytes": "^3.1.0",
"cac": "^6.5.8", "cac": "^6.6.1",
"cacache": "^15.0.0", "cacache": "^15.0.5",
"cache-base": "^4.0.0", "cache-base": "^4.0.0",
"cache-loader": "^4.1.0", "cache-loader": "^4.1.0",
"call-me-maybe": "^1.0.1", "call-me-maybe": "^1.0.1",
@ -84,12 +84,12 @@
"caller-path": "^3.0.0", "caller-path": "^3.0.0",
"callsites": "^3.1.0", "callsites": "^3.1.0",
"camel-case": "^4.1.1", "camel-case": "^4.1.1",
"camelcase": "^5.3.1", "camelcase": "^6.0.0",
"caniuse-api": "^3.0.0", "caniuse-api": "^3.0.0",
"caniuse-lite": "^1.0.30001039", "caniuse-lite": "^1.0.30001111",
"caseless": "^0.12.0", "caseless": "^0.12.0",
"chalk": "^4.0.0", "chalk": "^4.1.0",
"chokidar": "^3.3.1", "chokidar": "^3.4.1",
"chownr": "^2.0.0", "chownr": "^2.0.0",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"ci-info": "^2.0.0", "ci-info": "^2.0.0",
@ -106,7 +106,7 @@
"color-name": "^1.1.4", "color-name": "^1.1.4",
"color-string": "^1.5.3", "color-string": "^1.5.3",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"commander": "^5.0.0", "commander": "^6.0.0",
"commondir": "^1.0.1", "commondir": "^1.0.1",
"component-emitter": "^1.3.0", "component-emitter": "^1.3.0",
"compressible": "^2.0.18", "compressible": "^2.0.18",
@ -114,36 +114,36 @@
"concat-map": "^0.0.1", "concat-map": "^0.0.1",
"concat-stream": "^2.0.0", "concat-stream": "^2.0.0",
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"consola": "^2.11.3", "consola": "^2.15.0",
"console-browserify": "^1.2.0", "console-browserify": "^1.2.0",
"consolidate": "^0.15.1", "consolidate": "^0.15.1",
"constants-browserify": "^1.0.0", "constants-browserify": "^1.0.0",
"content-disposition": "^0.5.3", "content-disposition": "^0.5.3",
"content-type": "^1.0.4", "content-type": "^1.0.4",
"convert-source-map": "^1.7.0", "convert-source-map": "^1.7.0",
"cookie": "^0.4.0", "cookie": "^0.4.1",
"cookie-signature": "^1.1.0", "cookie-signature": "^1.1.0",
"copy-concurrently": "^1.0.5", "copy-concurrently": "^1.0.5",
"copy-descriptor": "^0.1.1", "copy-descriptor": "^0.1.1",
"copy-webpack-plugin": "^5.1.1", "copy-webpack-plugin": "^6.0.3",
"core-js": "^3.6.4", "core-js": "^3.6.5",
"core-util-is": "^1.0.2", "core-util-is": "^1.0.2",
"cosmiconfig": "^6.0.0", "cosmiconfig": "^7.0.0",
"create-ecdh": "^4.0.3", "create-ecdh": "^4.0.4",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"create-hmac": "^1.1.7", "create-hmac": "^1.1.7",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.3",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"css": "^2.2.4", "css": "^3.0.0",
"css-color-names": "^1.0.1", "css-color-names": "^1.0.1",
"css-declaration-sorter": "^5.1.2", "css-declaration-sorter": "^5.1.2",
"css-loader": "^3.5.0", "css-loader": "^4.2.0",
"css-parse": "^2.0.0", "css-parse": "^2.0.0",
"css-select": "^2.1.0", "css-select": "^2.1.0",
"css-select-base-adapter": "^0.1.1", "css-select-base-adapter": "^0.1.1",
"css-tree": "^1.0.0-alpha.39", "css-tree": "^1.0.0-alpha.39",
"css-unit-converter": "^1.1.1", "css-unit-converter": "^1.1.2",
"css-what": "^3.2.1", "css-what": "^3.3.0",
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"cssnano-preset-default": "^4.0.7", "cssnano-preset-default": "^4.0.7",
@ -158,9 +158,9 @@
"debug": "^4.1.1", "debug": "^4.1.1",
"decamelize": "^4.0.0", "decamelize": "^4.0.0",
"decode-uri-component": "^0.2.0", "decode-uri-component": "^0.2.0",
"deep-equal": "^2.0.2", "deep-equal": "^2.0.3",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"default-gateway": "^6.0.0", "default-gateway": "^6.0.1",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"define-property": "^2.0.2", "define-property": "^2.0.2",
"del": "^5.1.0", "del": "^5.1.0",
@ -178,52 +178,52 @@
"dns-txt": "^2.0.2", "dns-txt": "^2.0.2",
"docsearch.js": "^2.6.3", "docsearch.js": "^2.6.3",
"dom-converter": "^0.2.0", "dom-converter": "^0.2.0",
"dom-serializer": "^0.2.2", "dom-serializer": "^1.0.1",
"dom-walk": "^0.1.2", "dom-walk": "^0.1.2",
"domain-browser": "^4.0.0", "domain-browser": "^4.16.0",
"domelementtype": "^2.0.1", "domelementtype": "^2.0.1",
"domhandler": "^3.0.0", "domhandler": "^3.0.0",
"domutils": "^2.0.0", "domutils": "^2.1.0",
"dot-prop": "^5.2.0", "dot-prop": "^5.2.0",
"duplexify": "^4.1.1", "duplexify": "^4.1.1",
"ecc-jsbn": "^0.2.0", "ecc-jsbn": "^0.2.0",
"ee-first": "^1.1.1", "ee-first": "^1.1.1",
"electron-to-chromium": "^1.3.397", "electron-to-chromium": "^1.3.522",
"elliptic": "^6.5.2", "elliptic": "^6.5.3",
"emoji-regex": "^8.0.0", "emoji-regex": "^9.0.0",
"emojis-list": "^3.0.0", "emojis-list": "^3.0.0",
"encodeurl": "^1.0.2", "encodeurl": "^1.0.2",
"end-of-stream": "^1.4.4", "end-of-stream": "^1.4.4",
"enhanced-resolve": "^4.1.1", "enhanced-resolve": "^4.3.0",
"entities": "^2.0.0", "entities": "^2.0.3",
"envify": "^4.1.0", "envify": "^4.1.0",
"envinfo": "^7.5.0", "envinfo": "^7.7.2",
"errno": "^0.1.7", "errno": "^0.1.7",
"error-ex": "^1.3.2", "error-ex": "^1.3.2",
"es-abstract": "^1.17.5", "es-abstract": "^1.17.6",
"es-to-primitive": "^1.2.1", "es-to-primitive": "^1.2.1",
"es6-promise": "^4.2.8", "es6-promise": "^4.2.8",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.1.0",
"esprima": "^4.0.1", "esprima": "^4.0.1",
"esrecurse": "^4.2.1", "esrecurse": "^4.2.1",
"estraverse": "^5.0.0", "estraverse": "^5.2.0",
"esutils": "^2.0.3", "esutils": "^2.0.3",
"etag": "^1.8.1", "etag": "^1.8.1",
"eventemitter3": "^4.0.0", "eventemitter3": "^4.0.4",
"events": "^3.1.0", "events": "^3.2.0",
"eventsource": "^1.0.7", "eventsource": "^1.0.7",
"evp_bytestokey": "^1.0.3", "evp_bytestokey": "^1.0.3",
"execa": "^4.0.0", "execa": "^4.0.3",
"expand-brackets": "^4.0.0", "expand-brackets": "^4.0.0",
"express": "^4.17.1", "express": "^4.17.1",
"extend": "^3.0.2", "extend": "^3.0.2",
"extend-shallow": "^3.0.2", "extend-shallow": "^3.0.2",
"extglob": "^3.0.0", "extglob": "^3.0.0",
"extsprintf": "^1.4.0", "extsprintf": "^1.4.0",
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.3",
"fast-glob": "^3.2.2", "fast-glob": "^3.2.4",
"fast-json-stable-stringify": "^2.1.0", "fast-json-stable-stringify": "^2.1.0",
"faye-websocket": "^0.11.3", "faye-websocket": "^0.11.3",
"figgy-pudding": "^3.5.2", "figgy-pudding": "^3.5.2",
@ -235,7 +235,7 @@
"find-cache-dir": "^3.3.1", "find-cache-dir": "^3.3.1",
"find-up": "^4.1.0", "find-up": "^4.1.0",
"flush-write-stream": "^2.0.0", "flush-write-stream": "^2.0.0",
"follow-redirects": "^1.11.0", "follow-redirects": "^1.12.1",
"for-in": "^1.0.2", "for-in": "^1.0.2",
"foreach": "^2.0.5", "foreach": "^2.0.5",
"forever-agent": "^0.6.1", "forever-agent": "^0.6.1",
@ -244,7 +244,7 @@
"fragment-cache": "^0.2.1", "fragment-cache": "^0.2.1",
"fresh": "^0.5.2", "fresh": "^0.5.2",
"from2": "^2.3.0", "from2": "^2.3.0",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.1",
"fs-write-stream-atomic": "^1.0.10", "fs-write-stream-atomic": "^1.0.10",
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
"function-bind": "^1.1.1", "function-bind": "^1.1.1",
@ -257,21 +257,21 @@
"glob-parent": "^5.1.1", "glob-parent": "^5.1.1",
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"global": "^4.4.0", "global": "^4.4.0",
"globals": "^12.4.0", "globals": "^13.1.0",
"globby": "^11.0.0", "globby": "^11.0.1",
"good-listener": "^1.2.2", "good-listener": "^1.2.2",
"graceful-fs": "^4.2.3", "graceful-fs": "^4.2.4",
"gray-matter": "^4.0.2", "gray-matter": "^4.0.2",
"handle-thing": "^2.0.1", "handle-thing": "^2.0.1",
"har-schema": "^2.0.0", "har-schema": "^2.0.0",
"har-validator": "^5.1.3", "har-validator": "^5.1.5",
"has": "^1.0.3", "has": "^1.0.3",
"has-ansi": "^4.0.0", "has-ansi": "^4.0.0",
"has-flag": "^4.0.0", "has-flag": "^4.0.0",
"has-symbols": "^1.0.1", "has-symbols": "^1.0.1",
"has-value": "^2.0.2", "has-value": "^2.0.2",
"has-values": "^2.0.1", "has-values": "^2.0.1",
"hash-base": "^3.0.4", "hash-base": "^3.1.0",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"hash.js": "^1.1.7", "hash.js": "^1.1.7",
"he": "^1.2.0", "he": "^1.2.0",
@ -282,24 +282,24 @@
"hsl-regex": "^1.0.0", "hsl-regex": "^1.0.0",
"hsla-regex": "^1.0.0", "hsla-regex": "^1.0.0",
"html-comment-regex": "^1.1.2", "html-comment-regex": "^1.1.2",
"html-entities": "^1.2.1", "html-entities": "^1.3.1",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"html-tags": "^3.1.0", "html-tags": "^3.1.0",
"htmlparser2": "^4.1.0", "htmlparser2": "^4.1.0",
"http-deceiver": "^1.2.7", "http-deceiver": "^1.2.7",
"http-errors": "^1.7.3", "http-errors": "^1.8.0",
"http-parser-js": "^0.5.2", "http-parser-js": "^0.5.2",
"http-proxy": "^1.18.0", "http-proxy": "^1.18.1",
"http-proxy-middleware": "^1.0.3", "http-proxy-middleware": "^1.0.5",
"http-signature": "^1.3.4", "http-signature": "^1.3.4",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"iconv-lite": "^0.5.1", "iconv-lite": "^0.6.2",
"icss-replace-symbols": "^1.1.0", "icss-replace-symbols": "^1.1.0",
"icss-utils": "^4.1.1", "icss-utils": "^4.1.1",
"ieee754": "^1.1.13", "ieee754": "^1.1.13",
"iferr": "^1.0.2", "iferr": "^1.0.2",
"ignore": "^5.1.4", "ignore": "^5.1.8",
"immediate": "^3.2.3", "immediate": "^3.3.0",
"import-cwd": "^3.0.0", "import-cwd": "^3.0.0",
"import-fresh": "^3.2.1", "import-fresh": "^3.2.1",
"import-from": "^3.0.0", "import-from": "^3.0.0",
@ -309,9 +309,9 @@
"infer-owner": "^1.0.4", "infer-owner": "^1.0.4",
"inflight": "^1.0.6", "inflight": "^1.0.6",
"inherits": "^2.0.4", "inherits": "^2.0.4",
"internal-ip": "^6.0.0", "internal-ip": "^6.1.0",
"invariant": "^2.2.4", "invariant": "^2.2.4",
"invert-kv": "^3.0.0", "invert-kv": "^3.0.1",
"ip": "^1.1.5", "ip": "^1.1.5",
"ip-regex": "^4.1.0", "ip-regex": "^4.1.0",
"ipaddr.js": "^1.9.1", "ipaddr.js": "^1.9.1",
@ -321,7 +321,7 @@
"is-arrayish": "^0.3.2", "is-arrayish": "^0.3.2",
"is-binary-path": "^2.1.0", "is-binary-path": "^2.1.0",
"is-buffer": "^2.0.4", "is-buffer": "^2.0.4",
"is-callable": "^1.1.5", "is-callable": "^1.2.0",
"is-color-stop": "^1.1.0", "is-color-stop": "^1.1.0",
"is-data-descriptor": "^2.0.0", "is-data-descriptor": "^2.0.0",
"is-date-object": "^1.0.2", "is-date-object": "^1.0.2",
@ -337,25 +337,25 @@
"is-path-in-cwd": "^3.0.0", "is-path-in-cwd": "^3.0.0",
"is-path-inside": "^3.0.2", "is-path-inside": "^3.0.2",
"is-plain-obj": "^2.1.0", "is-plain-obj": "^2.1.0",
"is-plain-object": "^3.0.0", "is-plain-object": "^4.1.1",
"is-regex": "^1.0.5", "is-regex": "^1.1.1",
"is-resolvable": "^1.1.0", "is-resolvable": "^1.1.0",
"is-stream": "^2.0.0", "is-stream": "^2.0.0",
"is-svg": "^4.2.1", "is-svg": "^4.2.1",
"is-symbol": "^1.0.3", "is-symbol": "^1.0.3",
"is-typedarray": "^1.0.0", "is-typedarray": "^1.0.0",
"is-windows": "^1.0.2", "is-windows": "^1.0.2",
"is-wsl": "^2.1.1", "is-wsl": "^2.2.0",
"isarray": "^2.0.5", "isarray": "^2.0.5",
"isexe": "^2.0.0", "isexe": "^2.0.0",
"isobject": "^4.0.0", "isobject": "^4.0.0",
"isstream": "^0.1.2", "isstream": "^0.1.2",
"javascript-stringify": "^2.0.1", "javascript-stringify": "^2.0.1",
"js-levenshtein": "^1.1.6", "js-levenshtein": "^1.1.6",
"js-tokens": "^5.0.0", "js-tokens": "^6.0.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.14.0",
"jsbn": "^1.1.0", "jsbn": "^1.1.0",
"jsesc": "^2.5.2", "jsesc": "^3.0.1",
"json-parse-better-errors": "^1.0.2", "json-parse-better-errors": "^1.0.2",
"json-schema": "^0.2.5", "json-schema": "^0.2.5",
"json-schema-traverse": "^0.4.1", "json-schema-traverse": "^0.4.1",
@ -368,12 +368,12 @@
"kind-of": "^6.0.3", "kind-of": "^6.0.3",
"last-call-webpack-plugin": "^3.0.0", "last-call-webpack-plugin": "^3.0.0",
"lcid": "^3.1.1", "lcid": "^3.1.1",
"linkify-it": "^2.2.0", "linkify-it": "^3.0.2",
"load-script": "^1.0.0", "load-script": "^1.0.0",
"loader-runner": "^3.1.0", "loader-runner": "^4.0.0",
"loader-utils": "^2.0.0", "loader-utils": "^2.0.0",
"locate-path": "^5.0.0", "locate-path": "^5.0.0",
"lodash": "^4.17.15", "lodash": "^4.17.19",
"lodash._reinterpolate": "^3.0.0", "lodash._reinterpolate": "^3.0.0",
"lodash.chunk": "^4.2.0", "lodash.chunk": "^4.2.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
@ -385,37 +385,37 @@
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"lodash.templatesettings": "^4.2.0", "lodash.templatesettings": "^4.2.0",
"lodash.uniq": "^4.5.0", "lodash.uniq": "^4.5.0",
"loglevel": "^1.6.7", "loglevel": "^1.6.8",
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"lower-case": "^2.0.1", "lower-case": "^2.0.1",
"lru-cache": "^5.1.1", "lru-cache": "^6.0.0",
"make-dir": "^3.0.2", "make-dir": "^3.1.0",
"mamacro": "^0.0.7", "mamacro": "^0.0.7",
"map-age-cleaner": "^0.1.3", "map-age-cleaner": "^0.1.3",
"map-cache": "^0.2.2", "map-cache": "^0.2.2",
"map-visit": "^1.0.0", "map-visit": "^1.0.0",
"markdown-it": "^10.0.0", "markdown-it": "^11.0.0",
"markdown-it-anchor": "^5.2.7", "markdown-it-anchor": "^5.3.0",
"markdown-it-chain": "^1.3.0", "markdown-it-chain": "^1.3.0",
"markdown-it-container": "^2.0.0", "markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^1.4.0", "markdown-it-emoji": "^1.4.0",
"markdown-it-table-of-contents": "^0.4.4", "markdown-it-table-of-contents": "^0.4.4",
"md5.js": "^1.3.5", "md5.js": "^1.3.5",
"mdn-data": "^2.0.8", "mdn-data": "^2.0.11",
"mdurl": "^1.0.1", "mdurl": "^1.0.1",
"media-typer": "^1.1.0", "media-typer": "^1.1.0",
"mem": "^6.0.1", "mem": "^6.1.0",
"memory-fs": "^0.5.0", "memory-fs": "^0.5.0",
"merge-descriptors": "^1.0.1", "merge-descriptors": "^1.0.1",
"merge-source-map": "^1.1.0", "merge-source-map": "^1.1.0",
"merge2": "^1.3.0", "merge2": "^1.4.1",
"methods": "^1.1.2", "methods": "^1.1.2",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"miller-rabin": "^4.0.1", "miller-rabin": "^4.0.1",
"mime": "^2.4.4", "mime": "^2.4.6",
"mime-db": "^1.43.0", "mime-db": "^1.44.0",
"mime-types": "^2.1.26", "mime-types": "^2.1.27",
"mimic-fn": "^3.0.0", "mimic-fn": "^3.1.0",
"min-document": "^2.19.0", "min-document": "^2.19.0",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^0.9.0",
"minimalistic-assert": "^1.0.1", "minimalistic-assert": "^1.0.1",
@ -431,16 +431,16 @@
"multicast-dns-service-types": "^1.1.0", "multicast-dns-service-types": "^1.1.0",
"nanomatch": "^1.2.13", "nanomatch": "^1.2.13",
"negotiator": "^0.6.2", "negotiator": "^0.6.2",
"neo-async": "^2.6.1", "neo-async": "^2.6.2",
"nice-try": "^2.0.1", "nice-try": "^2.0.1",
"no-case": "^3.0.3", "no-case": "^3.0.3",
"node-forge": "^0.9.1", "node-forge": "^0.9.1",
"node-libs-browser": "^2.2.1", "node-libs-browser": "^2.2.1",
"node-releases": "^1.1.53", "node-releases": "^1.1.60",
"nopt": "^4.0.3", "nopt": "^4.0.3",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"normalize-url": "^5.0.0", "normalize-url": "^5.1.0",
"npm-run-path": "^4.0.1", "npm-run-path": "^4.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"nth-check": "^1.0.2", "nth-check": "^1.0.2",
@ -449,8 +449,8 @@
"oauth-sign": "^0.9.0", "oauth-sign": "^0.9.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"object-copy": "^1.0.0", "object-copy": "^1.0.0",
"object-inspect": "^1.7.0", "object-inspect": "^1.8.0",
"object-is": "^1.0.2", "object-is": "^1.1.2",
"object-keys": "^1.1.1", "object-keys": "^1.1.1",
"object-visit": "^1.0.1", "object-visit": "^1.0.1",
"object.assign": "^4.1.0", "object.assign": "^4.1.0",
@ -461,7 +461,7 @@
"on-finished": "^2.3.0", "on-finished": "^2.3.0",
"on-headers": "^1.0.2", "on-headers": "^1.0.2",
"once": "^1.4.0", "once": "^1.4.0",
"opencollective-postinstall": "^2.0.2", "opencollective-postinstall": "^2.0.3",
"opn": "^6.0.0", "opn": "^6.0.0",
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3",
"original": "^1.0.2", "original": "^1.0.2",
@ -470,7 +470,7 @@
"p-defer": "^3.0.0", "p-defer": "^3.0.0",
"p-finally": "^2.0.1", "p-finally": "^2.0.1",
"p-is-promise": "^3.0.0", "p-is-promise": "^3.0.0",
"p-limit": "^2.3.0", "p-limit": "^3.0.2",
"p-locate": "^4.1.0", "p-locate": "^4.1.0",
"p-map": "^4.0.0", "p-map": "^4.0.0",
"p-retry": "^4.2.0", "p-retry": "^4.2.0",
@ -479,7 +479,7 @@
"parallel-transform": "^1.2.0", "parallel-transform": "^1.2.0",
"param-case": "^3.0.3", "param-case": "^3.0.3",
"parse-asn1": "^5.1.5", "parse-asn1": "^5.1.5",
"parse-json": "^5.0.0", "parse-json": "^5.0.1",
"parseurl": "^1.3.3", "parseurl": "^1.3.3",
"pascalcase": "^1.0.0", "pascalcase": "^1.0.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
@ -491,16 +491,16 @@
"path-parse": "^1.0.6", "path-parse": "^1.0.6",
"path-to-regexp": "^6.1.0", "path-to-regexp": "^6.1.0",
"path-type": "^4.0.0", "path-type": "^4.0.0",
"pbkdf2": "^3.0.17", "pbkdf2": "^3.1.1",
"performance-now": "^2.1.0", "performance-now": "^2.1.0",
"pify": "^5.0.0", "pify": "^5.0.0",
"pinkie": "^2.0.4", "pinkie": "^2.0.4",
"pinkie-promise": "^2.0.1", "pinkie-promise": "^2.0.1",
"pkg-dir": "^4.2.0", "pkg-dir": "^4.2.0",
"pkg-up": "^3.1.0", "pkg-up": "^3.1.0",
"portfinder": "^1.0.25", "portfinder": "^1.0.28",
"posix-character-classes": "^1.0.0", "posix-character-classes": "^1.0.0",
"postcss": "^7.0.27", "postcss": "^7.0.32",
"postcss-calc": "^7.0.2", "postcss-calc": "^7.0.2",
"postcss-colormin": "^4.0.3", "postcss-colormin": "^4.0.3",
"postcss-convert-values": "^4.0.1", "postcss-convert-values": "^4.0.1",
@ -517,7 +517,7 @@
"postcss-minify-params": "^4.0.2", "postcss-minify-params": "^4.0.2",
"postcss-minify-selectors": "^4.0.2", "postcss-minify-selectors": "^4.0.2",
"postcss-modules-extract-imports": "^2.0.0", "postcss-modules-extract-imports": "^2.0.0",
"postcss-modules-local-by-default": "^3.0.2", "postcss-modules-local-by-default": "^3.0.3",
"postcss-modules-scope": "^2.2.0", "postcss-modules-scope": "^2.2.0",
"postcss-modules-values": "^3.0.0", "postcss-modules-values": "^3.0.0",
"postcss-normalize-charset": "^4.0.1", "postcss-normalize-charset": "^4.0.1",
@ -536,9 +536,9 @@
"postcss-selector-parser": "^6.0.2", "postcss-selector-parser": "^6.0.2",
"postcss-svgo": "^4.0.2", "postcss-svgo": "^4.0.2",
"postcss-unique-selectors": "^4.0.1", "postcss-unique-selectors": "^4.0.1",
"postcss-value-parser": "^4.0.3", "postcss-value-parser": "^4.1.0",
"prepend-http": "^3.0.1", "prepend-http": "^3.0.1",
"prettier": "^2.0.4", "prettier": "^2.0.5",
"pretty-error": "^2.1.1", "pretty-error": "^2.1.1",
"pretty-time": "^1.1.0", "pretty-time": "^1.1.0",
"prismjs": "^1.20.0", "prismjs": "^1.20.0",
@ -555,8 +555,8 @@
"pumpify": "^2.0.1", "pumpify": "^2.0.1",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"q": "^1.5.1", "q": "^1.5.1",
"qs": "^6.9.3", "qs": "^6.9.4",
"query-string": "^6.12.0", "query-string": "^6.13.1",
"querystring": "^0.2.0", "querystring": "^0.2.0",
"querystring-es3": "^0.2.1", "querystring-es3": "^0.2.1",
"querystringify": "^2.1.1", "querystringify": "^2.1.1",
@ -567,14 +567,14 @@
"readable-stream": "^3.6.0", "readable-stream": "^3.6.0",
"readdirp": "^3.4.0", "readdirp": "^3.4.0",
"reduce": "^1.0.2", "reduce": "^1.0.2",
"regenerate": "^1.4.0", "regenerate": "^1.4.1",
"regenerate-unicode-properties": "^8.2.0", "regenerate-unicode-properties": "^8.2.0",
"regenerator-runtime": "^0.13.5", "regenerator-runtime": "^0.13.7",
"regenerator-transform": "^0.14.4", "regenerator-transform": "^0.14.5",
"regex-not": "^1.0.2", "regex-not": "^1.0.2",
"regexp.prototype.flags": "^1.3.0", "regexp.prototype.flags": "^1.3.0",
"regexpu-core": "^4.7.0", "regexpu-core": "^4.7.0",
"regjsgen": "^0.5.1", "regjsgen": "^0.5.2",
"regjsparser": "^0.6.4", "regjsparser": "^0.6.4",
"relateurl": "^0.2.7", "relateurl": "^0.2.7",
"remove-trailing-separator": "^1.1.0", "remove-trailing-separator": "^1.1.0",
@ -586,7 +586,7 @@
"require-main-filename": "^2.0.0", "require-main-filename": "^2.0.0",
"requires-port": "^1.0.0", "requires-port": "^1.0.0",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"resolve": "^1.15.1", "resolve": "^1.17.0",
"resolve-cwd": "^3.0.0", "resolve-cwd": "^3.0.0",
"resolve-from": "^5.0.0", "resolve-from": "^5.0.0",
"resolve-url": "^0.2.1", "resolve-url": "^0.2.1",
@ -597,18 +597,18 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ripemd160": "^2.0.2", "ripemd160": "^2.0.2",
"run-queue": "^2.0.1", "run-queue": "^2.0.1",
"safe-buffer": "^5.2.0", "safe-buffer": "^5.2.1",
"safe-regex": "^2.1.1", "safe-regex": "^2.1.1",
"safer-buffer": "^2.1.2", "safer-buffer": "^2.1.2",
"sax": "^1.2.4", "sax": "^1.2.4",
"schema-utils": "^2.6.5", "schema-utils": "^2.7.0",
"section-matter": "^1.0.0", "section-matter": "^1.0.0",
"select": "^1.1.2", "select": "^1.1.2",
"select-hose": "^2.0.0", "select-hose": "^2.0.0",
"selfsigned": "^1.10.7", "selfsigned": "^1.10.7",
"semver": "^7.2.1", "semver": "^7.3.2",
"send": "^0.17.1", "send": "^0.17.1",
"serialize-javascript": "^3.0.0", "serialize-javascript": "^4.0.0",
"serve-index": "^1.9.1", "serve-index": "^1.9.1",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"set-blocking": "^2.0.0", "set-blocking": "^2.0.0",
@ -620,19 +620,19 @@
"shebang-regex": "^3.0.0", "shebang-regex": "^3.0.0",
"signal-exit": "^3.0.3", "signal-exit": "^3.0.3",
"simple-swizzle": "^0.2.2", "simple-swizzle": "^0.2.2",
"sitemap": "^6.1.0", "sitemap": "^6.2.0",
"slash": "^3.0.0", "slash": "^3.0.0",
"smoothscroll-polyfill": "^0.4.4", "smoothscroll-polyfill": "^0.4.4",
"snapdragon": "^0.12.0", "snapdragon": "^0.12.0",
"snapdragon-node": "^3.0.0", "snapdragon-node": "^3.0.0",
"snapdragon-util": "^5.0.1", "snapdragon-util": "^5.0.1",
"sockjs": "^0.3.20", "sockjs": "^0.3.21",
"sockjs-client": "^1.4.0", "sockjs-client": "^1.5.0",
"sort-keys": "^4.0.0", "sort-keys": "^4.0.0",
"source-list-map": "^2.0.1", "source-list-map": "^2.0.1",
"source-map": "^0.7.3", "source-map": "^0.7.3",
"source-map-resolve": "^0.6.0", "source-map-resolve": "^0.6.0",
"source-map-support": "^0.5.16", "source-map-support": "^0.5.19",
"source-map-url": "^0.4.0", "source-map-url": "^0.4.0",
"spdy": "^4.0.2", "spdy": "^4.0.2",
"spdy-transport": "^3.0.0", "spdy-transport": "^3.0.0",
@ -641,13 +641,13 @@
"sshpk": "^1.16.1", "sshpk": "^1.16.1",
"ssri": "^8.0.0", "ssri": "^8.0.0",
"stable": "^0.1.8", "stable": "^0.1.8",
"stack-utils": "^2.0.1", "stack-utils": "^2.0.2",
"static-extend": "^0.1.2", "static-extend": "^0.1.2",
"statuses": "^1.5.0", "statuses": "^2.0.0",
"std-env": "^2.2.1", "std-env": "^2.2.1",
"stream-browserify": "^2.0.2", "stream-browserify": "^3.0.0",
"stream-each": "^1.2.3", "stream-each": "^1.2.3",
"stream-http": "^3.1.0", "stream-http": "^3.1.1",
"stream-shift": "^1.0.1", "stream-shift": "^1.0.1",
"strict-uri-encode": "^2.0.0", "strict-uri-encode": "^2.0.0",
"string-width": "^4.2.0", "string-width": "^4.2.0",
@ -658,17 +658,17 @@
"strip-bom-string": "^1.0.0", "strip-bom-string": "^1.0.0",
"strip-eof": "^2.0.0", "strip-eof": "^2.0.0",
"stylehacks": "^4.0.3", "stylehacks": "^4.0.3",
"stylus": "^0.54.7", "stylus": "^0.54.8",
"stylus-loader": "^3.0.2", "stylus-loader": "^3.0.2",
"supports-color": "^7.1.0", "supports-color": "^7.1.0",
"svg-tags": "^1.0.0", "svg-tags": "^1.0.0",
"svgo": "^1.3.2", "svgo": "^1.3.2",
"tapable": "^1.1.3", "tapable": "^1.1.3",
"terser": "^4.6.10", "terser": "^5.0.0",
"terser-webpack-plugin": "^2.3.5", "terser-webpack-plugin": "^4.0.0",
"text-table": "^0.2.0", "text-table": "^0.2.0",
"through": "^2.3.8", "through": "^2.3.8",
"through2": "^3.0.1", "through2": "^4.0.2",
"thunky": "^1.1.0", "thunky": "^1.1.0",
"timers-browserify": "^2.0.11", "timers-browserify": "^2.0.11",
"timsort": "^0.3.0", "timsort": "^0.3.0",
@ -684,15 +684,15 @@
"toposort": "^2.0.2", "toposort": "^2.0.2",
"tough-cookie": "^4.0.0", "tough-cookie": "^4.0.0",
"tr46": "^2.0.2", "tr46": "^2.0.2",
"tslib": "^1.11.1", "tslib": "^2.0.0",
"tty-browserify": "^0.0.1", "tty-browserify": "^0.0.1",
"tunnel-agent": "^0.6.0", "tunnel-agent": "^0.6.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"type-fest": "^0.13.0", "type-fest": "^0.16.0",
"type-is": "^1.6.18", "type-is": "^1.6.18",
"typedarray": "^0.0.6", "typedarray": "^0.0.6",
"uc.micro": "^1.0.6", "uc.micro": "^1.0.6",
"uglify-js": "^3.8.1", "uglify-js": "^3.10.1",
"unicode-canonical-property-names-ecmascript": "^1.0.4", "unicode-canonical-property-names-ecmascript": "^1.0.4",
"unicode-match-property-ecmascript": "^1.0.4", "unicode-match-property-ecmascript": "^1.0.4",
"unicode-match-property-value-ecmascript": "^1.2.0", "unicode-match-property-value-ecmascript": "^1.2.0",
@ -702,7 +702,7 @@
"uniqs": "^2.0.0", "uniqs": "^2.0.0",
"unique-filename": "^1.1.1", "unique-filename": "^1.1.1",
"unique-slug": "^2.0.2", "unique-slug": "^2.0.2",
"universalify": "^1.0.0", "universalify": "^2.0.0",
"unpipe": "^1.0.0", "unpipe": "^1.0.0",
"unquote": "^1.1.1", "unquote": "^1.1.1",
"unset-value": "^1.0.0", "unset-value": "^1.0.0",
@ -711,60 +711,60 @@
"uri-js": "^4.2.2", "uri-js": "^4.2.2",
"urix": "^0.1.0", "urix": "^0.1.0",
"url": "^0.11.0", "url": "^0.11.0",
"url-loader": "^4.0.0", "url-loader": "^4.1.0",
"url-parse": "^1.4.7", "url-parse": "^1.4.7",
"use": "^3.1.1", "use": "^3.1.1",
"util": "^0.12.2", "util": "^0.12.3",
"util-deprecate": "^1.0.2", "util-deprecate": "^1.0.2",
"util.promisify": "^1.0.1", "util.promisify": "^1.0.1",
"utila": "^0.4.0", "utila": "^0.4.0",
"utils-merge": "^1.0.1", "utils-merge": "^1.0.1",
"uuid": "^7.0.3", "uuid": "^8.3.0",
"vary": "^1.1.2", "vary": "^1.1.2",
"vendors": "^1.0.4", "vendors": "^1.0.4",
"verror": "^1.10.0", "verror": "^1.10.0",
"vm-browserify": "^1.1.2", "vm-browserify": "^1.1.2",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-hot-reload-api": "^2.3.4", "vue-hot-reload-api": "^2.3.4",
"vue-loader": "^15.9.1", "vue-loader": "^15.9.3",
"vue-router": "^3.1.6", "vue-router": "^3.4.0",
"vue-server-renderer": "^2.6.11", "vue-server-renderer": "^2.6.11",
"vue-style-loader": "^4.1.2", "vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"vue-template-es2015-compiler": "^1.9.1", "vue-template-es2015-compiler": "^1.9.1",
"vuepress": "^1.4.0", "vuepress": "^1.5.3",
"vuepress-html-webpack-plugin": "^3.2.0", "vuepress-html-webpack-plugin": "^3.2.0",
"vuepress-plugin-container": "^2.1.2", "vuepress-plugin-container": "^2.1.4",
"vuepress-plugin-sitemap": "^2.3.1", "vuepress-plugin-sitemap": "^2.3.1",
"vuepress-plugin-smooth-scroll": "^0.0.9", "vuepress-plugin-smooth-scroll": "^0.0.9",
"vuepress-plugin-zooming": "^1.1.7", "vuepress-plugin-zooming": "^1.1.7",
"watchpack": "^1.6.1", "watchpack": "^1.7.4",
"wbuf": "^1.7.3", "wbuf": "^1.7.3",
"webidl-conversions": "^6.0.0", "webidl-conversions": "^6.1.0",
"webpack": "^4.42.1", "webpack": "^4.44.1",
"webpack-chain": "^6.4.0", "webpack-chain": "^6.5.1",
"webpack-dev-middleware": "^3.7.2", "webpack-dev-middleware": "^3.7.2",
"webpack-dev-server": "^3.10.3", "webpack-dev-server": "^3.11.0",
"webpack-log": "^3.0.1", "webpack-log": "^3.0.1",
"webpack-merge": "^4.2.2", "webpack-merge": "^5.1.1",
"webpack-sources": "^1.4.3", "webpack-sources": "^1.4.3",
"webpackbar": "^4.0.0", "webpackbar": "^4.0.0",
"websocket-driver": "^0.7.3", "websocket-driver": "^0.7.4",
"websocket-extensions": "^0.1.3", "websocket-extensions": "^0.1.4",
"whatwg-url": "^8.0.0", "whatwg-url": "^8.1.0",
"when": "^3.7.8", "when": "^3.7.8",
"which": "^2.0.2", "which": "^2.0.2",
"which-module": "^2.0.0", "which-module": "^2.0.0",
"worker-farm": "^1.7.0", "worker-farm": "^1.7.0",
"wrap-ansi": "^6.2.0", "wrap-ansi": "^7.0.0",
"wrappy": "^1.0.2", "wrappy": "^1.0.2",
"ws": "^7.2.3", "ws": "^7.3.1",
"xmlbuilder": "^15.1.0", "xmlbuilder": "^15.1.1",
"xtend": "^4.0.2", "xtend": "^4.0.2",
"y18n": "^4.0.0", "y18n": "^4.0.0",
"yallist": "^4.0.0", "yallist": "^4.0.0",
"yargs": "^15.3.1", "yargs": "^15.4.1",
"yargs-parser": "^18.1.2", "yargs-parser": "^18.1.3",
"zepto": "^1.2.0" "zepto": "^1.2.0"
}, },
"devDependencies": {}, "devDependencies": {},

View File

@ -23,15 +23,31 @@ Here's an example configuration for `mysql` (or mariadb) that is compatible with
} }
``` ```
Alternatively if you would like to use a Sqlite database file:
```json
{
"database": {
"engine": "knex-native",
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/data/database.sqlite"
}
}
}
}
```
Once you've created your configuration file it's easy to mount it in the docker container. Once you've created your configuration file it's easy to mount it in the docker container.
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys **Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys
affect the login and session management of the application. If these keys change for any reason, all users will be logged out. affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
### Database ### MySQL Database
This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported for the minimum versions: If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
- MySQL v5.7.8+ - MySQL v5.7.8+
- MariaDB v10.2.7+ - MariaDB v10.2.7+

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,24 @@
<input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required> <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required>
</div> </div>
</div> </div>
<!-- CloudFlare -->
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="meta[cloudflare_use]" value="1">
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 cloudflare">
<div class="form-group">
<label class="form-label">CloudFlare DNS API Token <span class="form-required">*</span></label>
<input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
</div>
</div>
<div class="col-sm-12 col-md-12"> <div class="col-sm-12 col-md-12">
<div class="form-group"> <div class="form-group">
<label class="custom-switch"> <label class="custom-switch">
@ -42,7 +60,7 @@
<div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div> <div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div>
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required> <input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required>
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> <label id="other_certificate_key_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
</div> </div>
</div> </div>
</div> </div>
@ -51,7 +69,7 @@
<div class="form-label"><%- i18n('certificates', 'other-certificate') %><span class="form-required">*</span></div> <div class="form-label"><%- i18n('certificates', 'other-certificate') %><span class="form-required">*</span></div>
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_certificate"> <input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_certificate">
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> <label id="other_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
</div> </div>
</div> </div>
</div> </div>
@ -60,7 +78,7 @@
<div class="form-label"><%- i18n('certificates', 'other-intermediate-certificate') %></div> <div class="form-label"><%- i18n('certificates', 'other-intermediate-certificate') %></div>
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate"> <input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate">
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> <label id="other_intermediate_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
</div> </div>
</div> </div>
</div> </div>

View File

@ -19,16 +19,33 @@ module.exports = Mn.View.extend({
cancel: 'button.cancel', cancel: 'button.cancel',
save: 'button.save', save: 'button.save',
other_certificate: '#other_certificate', other_certificate: '#other_certificate',
other_certificate_label: '#other_certificate_label',
other_certificate_key: '#other_certificate_key', other_certificate_key: '#other_certificate_key',
other_intermediate_certificate: '#other_intermediate_certificate' cloudflare_switch: 'input[name="meta[cloudflare_use]"]',
cloudflare_token: 'input[name="meta[cloudflare_token]"',
cloudflare: '.cloudflare',
other_certificate_key_label: '#other_certificate_key_label',
other_intermediate_certificate: '#other_intermediate_certificate',
other_intermediate_certificate_label: '#other_intermediate_certificate_label'
}, },
events: { events: {
'change @ui.cloudflare_switch': function() {
let checked = this.ui.cloudflare_switch.prop('checked');
if (checked) {
this.ui.cloudflare_token.prop('required', 'required');
this.ui.cloudflare.show();
} else {
this.ui.cloudflare_token.prop('required', false);
this.ui.cloudflare.hide();
}
},
'click @ui.save': function (e) { 'click @ui.save': function (e) {
e.preventDefault(); e.preventDefault();
if (!this.ui.form[0].checkValidity()) { if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
$(this).removeClass('btn-loading');
return; return;
} }
@ -36,10 +53,29 @@ module.exports = Mn.View.extend({
let data = this.ui.form.serializeJSON(); let data = this.ui.form.serializeJSON();
data.provider = this.model.get('provider'); data.provider = this.model.get('provider');
let domain_err = false;
if (!data.meta.cloudflare_use) {
data.domain_names.split(',').map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
}
if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains when not using CloudFlare DNS');
return;
}
// Manipulate // Manipulate
if (typeof data.meta !== 'undefined' && typeof data.meta.letsencrypt_agree !== 'undefined') { if (typeof data.meta !== 'undefined' && typeof data.meta.letsencrypt_agree !== 'undefined') {
data.meta.letsencrypt_agree = !!data.meta.letsencrypt_agree; data.meta.letsencrypt_agree = !!data.meta.letsencrypt_agree;
} }
if (typeof data.meta !== 'undefined' && typeof data.meta.cloudflare_use !== 'undefined') {
data.meta.cloudflare_use = !!data.meta.cloudflare_use;
}
if (typeof data.domain_names === 'string' && data.domain_names) { if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(','); data.domain_names = data.domain_names.split(',');
@ -81,6 +117,7 @@ module.exports = Mn.View.extend({
} }
this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
// compile file data // compile file data
let form_data = new FormData(); let form_data = new FormData();
@ -119,10 +156,22 @@ module.exports = Mn.View.extend({
.catch(err => { .catch(err => {
alert(err.message); alert(err.message);
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.other_certificate_key': function(e){
this.setFileName("other_certificate_key_label", e)
},
'change @ui.other_certificate': function(e){
this.setFileName("other_certificate_label", e)
},
'change @ui.other_intermediate_certificate': function(e){
this.setFileName("other_intermediate_certificate_label", e)
} }
}, },
setFileName(ui, e){
this.getUI(ui).text(e.target.files[0].name)
},
templateContext: { templateContext: {
getLetsencryptEmail: function () { getLetsencryptEmail: function () {
return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email');
@ -130,6 +179,10 @@ module.exports = Mn.View.extend({
getLetsencryptAgree: function () { getLetsencryptAgree: function () {
return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false;
},
getCloudflareUse: function () {
return typeof this.meta.cloudflare_use !== 'undefined' ? this.meta.cloudflare_use : false;
} }
}, },
@ -144,8 +197,9 @@ module.exports = Mn.View.extend({
text: input text: input
}; };
}, },
createFilter: /^(?:[^.*]+\.?)+[^.]$/ createFilter: /^(?:[^.]+\.?)+[^.]$/
}); });
this.ui.cloudflare.hide();
}, },
initialize: function (options) { initialize: function (options) {

View File

@ -28,7 +28,7 @@
</div> </div>
</td> </td>
<td> <td>
<%- i18n('ssl', provider) %> <%- i18n('ssl', provider) %><% if (meta.cloudflare_use) { %> - CloudFlare DNS<% } %>
</td> </td>
<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') %>

View File

@ -73,6 +73,23 @@
</div> </div>
</div> </div>
<!-- CloudFlare -->
<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[cloudflare_use]" value="1">
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 cloudflare letsencrypt">
<div class="form-group">
<label class="form-label">CloudFlare DNS API Token <span class="form-required">*</span></label>
<input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
</div>
</div>
<!-- Lets encrypt --> <!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt"> <div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group"> <div class="form-group">

View File

@ -23,6 +23,9 @@ module.exports = Mn.View.extend({
hsts_enabled: 'input[name="hsts_enabled"]', hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]', hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]', http2_support: 'input[name="http2_support"]',
cloudflare_switch: 'input[name="meta[cloudflare_use]"]',
cloudflare_token: 'input[name="meta[cloudflare_token]"',
cloudflare: '.cloudflare',
letsencrypt: '.letsencrypt' letsencrypt: '.letsencrypt'
}, },
@ -31,10 +34,12 @@ module.exports = Mn.View.extend({
let id = this.ui.certificate_select.val(); let id = this.ui.certificate_select.val();
if (id === 'new') { if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false); this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.cloudflare.hide();
} else { } else {
this.ui.letsencrypt.hide().find('input').prop('disabled', true); this.ui.letsencrypt.hide().find('input').prop('disabled', true);
} }
let enabled = id === 'new' || parseInt(id, 10) > 0; let enabled = id === 'new' || parseInt(id, 10) > 0;
let inputs = this.ui.ssl_forced.add(this.ui.http2_support); let inputs = this.ui.ssl_forced.add(this.ui.http2_support);
@ -76,6 +81,17 @@ module.exports = Mn.View.extend({
} }
}, },
'change @ui.cloudflare_switch': function() {
let checked = this.ui.cloudflare_switch.prop('checked');
if (checked) {
this.ui.cloudflare_token.prop('required', 'required');
this.ui.cloudflare.show();
} else {
this.ui.cloudflare_token.prop('required', false);
this.ui.cloudflare.hide();
}
},
'click @ui.save': function (e) { 'click @ui.save': function (e) {
e.preventDefault(); e.preventDefault();
@ -100,17 +116,20 @@ module.exports = Mn.View.extend({
// Check for any domain names containing wildcards, which are not allowed with letsencrypt // Check for any domain names containing wildcards, which are not allowed with letsencrypt
if (data.certificate_id === 'new') { if (data.certificate_id === 'new') {
let domain_err = false; let domain_err = false;
if (!data.meta.cloudflare_use) {
data.domain_names.map(function (name) { data.domain_names.map(function (name) {
if (name.match(/\*/im)) { if (name.match(/\*/im)) {
domain_err = true; domain_err = true;
} }
}); });
}
if (domain_err) { if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains'); alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.');
return; return;
} }
data.meta.cloudflare_use = data.meta.cloudflare_use === '1';
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
} else { } else {
data.certificate_id = parseInt(data.certificate_id, 10); data.certificate_id = parseInt(data.certificate_id, 10);
@ -127,6 +146,8 @@ module.exports = Mn.View.extend({
} }
this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
method(data) method(data)
.then(result => { .then(result => {
view.model.set(result); view.model.set(result);
@ -140,6 +161,7 @@ module.exports = Mn.View.extend({
.catch(err => { .catch(err => {
alert(err.message); alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
}); });
} }
}, },

View File

@ -141,6 +141,23 @@
</div> </div>
</div> </div>
<!-- CloudFlare -->
<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[cloudflare_use]" value="1">
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 cloudflare letsencrypt">
<div class="form-group">
<label class="form-label">CloudFlare DNS API Token <span class="form-required">*</span></label>
<input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
</div>
</div>
<!-- Lets encrypt --> <!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt"> <div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group"> <div class="form-group">

View File

@ -33,6 +33,9 @@ module.exports = Mn.View.extend({
hsts_enabled: 'input[name="hsts_enabled"]', hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]', hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]', http2_support: 'input[name="http2_support"]',
cloudflare_switch: 'input[name="meta[cloudflare_use]"]',
cloudflare_token: 'input[name="meta[cloudflare_token]"',
cloudflare: '.cloudflare',
forward_scheme: 'select[name="forward_scheme"]', forward_scheme: 'select[name="forward_scheme"]',
letsencrypt: '.letsencrypt' letsencrypt: '.letsencrypt'
}, },
@ -46,6 +49,7 @@ module.exports = Mn.View.extend({
let id = this.ui.certificate_select.val(); let id = this.ui.certificate_select.val();
if (id === 'new') { if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false); this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.cloudflare.hide();
} else { } else {
this.ui.letsencrypt.hide().find('input').prop('disabled', true); this.ui.letsencrypt.hide().find('input').prop('disabled', true);
} }
@ -91,6 +95,17 @@ module.exports = Mn.View.extend({
} }
}, },
'change @ui.cloudflare_switch': function() {
let checked = this.ui.cloudflare_switch.prop('checked');
if (checked) {
this.ui.cloudflare_token.prop('required', 'required');
this.ui.cloudflare.show();
} else {
this.ui.cloudflare_token.prop('required', false);
this.ui.cloudflare.hide();
}
},
'click @ui.add_location_btn': function (e) { 'click @ui.add_location_btn': function (e) {
e.preventDefault(); e.preventDefault();
@ -136,17 +151,20 @@ module.exports = Mn.View.extend({
// Check for any domain names containing wildcards, which are not allowed with letsencrypt // Check for any domain names containing wildcards, which are not allowed with letsencrypt
if (data.certificate_id === 'new') { if (data.certificate_id === 'new') {
let domain_err = false; let domain_err = false;
if (!data.meta.cloudflare_use) {
data.domain_names.map(function (name) { data.domain_names.map(function (name) {
if (name.match(/\*/im)) { if (name.match(/\*/im)) {
domain_err = true; domain_err = true;
} }
}); });
}
if (domain_err) { if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains'); alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.');
return; return;
} }
data.meta.cloudflare_use = data.meta.cloudflare_use === '1';
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
} else { } else {
data.certificate_id = parseInt(data.certificate_id, 10); data.certificate_id = parseInt(data.certificate_id, 10);
@ -163,6 +181,8 @@ module.exports = Mn.View.extend({
} }
this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
method(data) method(data)
.then(result => { .then(result => {
view.model.set(result); view.model.set(result);
@ -176,6 +196,7 @@ module.exports = Mn.View.extend({
.catch(err => { .catch(err => {
alert(err.message); alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
}); });
} }
}, },
@ -203,7 +224,7 @@ module.exports = Mn.View.extend({
text: input text: input
}; };
}, },
createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ createFilter: /^(?:\.)?(?:[^.*]+\.?)+[^.]$/
}); });
// Access Lists // Access Lists

View File

@ -38,7 +38,7 @@
<div class="col-sm-5 col-md-5"> <div class="col-sm-5 col-md-5">
<div class="form-group"> <div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label> <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
<input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required> <input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="200" required>
<span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span> <span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span>
</div> </div>
</div> </div>

View File

@ -97,6 +97,23 @@
</div> </div>
</div> </div>
<!-- CloudFlare -->
<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[cloudflare_use]" value="1">
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 cloudflare letsencrypt">
<div class="form-group">
<label class="form-label">CloudFlare DNS API Token <span class="form-required">*</span></label>
<input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
</div>
</div>
<!-- Lets encrypt --> <!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt"> <div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group"> <div class="form-group">

View File

@ -23,6 +23,9 @@ module.exports = Mn.View.extend({
hsts_enabled: 'input[name="hsts_enabled"]', hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]', hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]', http2_support: 'input[name="http2_support"]',
cloudflare_switch: 'input[name="meta[cloudflare_use]"]',
cloudflare_token: 'input[name="meta[cloudflare_token]"',
cloudflare: '.cloudflare',
letsencrypt: '.letsencrypt' letsencrypt: '.letsencrypt'
}, },
@ -31,6 +34,7 @@ module.exports = Mn.View.extend({
let id = this.ui.certificate_select.val(); let id = this.ui.certificate_select.val();
if (id === 'new') { if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false); this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.cloudflare.hide();
} else { } else {
this.ui.letsencrypt.hide().find('input').prop('disabled', true); this.ui.letsencrypt.hide().find('input').prop('disabled', true);
} }
@ -76,6 +80,17 @@ module.exports = Mn.View.extend({
} }
}, },
'change @ui.cloudflare_switch': function() {
let checked = this.ui.cloudflare_switch.prop('checked');
if (checked) {
this.ui.cloudflare_token.prop('required', 'required');
this.ui.cloudflare.show();
} else {
this.ui.cloudflare_token.prop('required', false);
this.ui.cloudflare.hide();
}
},
'click @ui.save': function (e) { 'click @ui.save': function (e) {
e.preventDefault(); e.preventDefault();
@ -102,17 +117,20 @@ module.exports = Mn.View.extend({
// Check for any domain names containing wildcards, which are not allowed with letsencrypt // Check for any domain names containing wildcards, which are not allowed with letsencrypt
if (data.certificate_id === 'new') { if (data.certificate_id === 'new') {
let domain_err = false; let domain_err = false;
if (!data.meta.cloudflare_use) {
data.domain_names.map(function (name) { data.domain_names.map(function (name) {
if (name.match(/\*/im)) { if (name.match(/\*/im)) {
domain_err = true; domain_err = true;
} }
}); });
}
if (domain_err) { if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains'); alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.');
return; return;
} }
data.meta.cloudflare_use = data.meta.cloudflare_use === '1';
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
} else { } else {
data.certificate_id = parseInt(data.certificate_id, 10); data.certificate_id = parseInt(data.certificate_id, 10);
@ -129,6 +147,8 @@ module.exports = Mn.View.extend({
} }
this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
method(data) method(data)
.then(result => { .then(result => {
view.model.set(result); view.model.set(result);
@ -142,6 +162,7 @@ module.exports = Mn.View.extend({
.catch(err => { .catch(err => {
alert(err.message); alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
}); });
} }
}, },

View File

@ -101,7 +101,8 @@
"letsencrypt-email": "Email Address for Let's Encrypt", "letsencrypt-email": "Email Address for Let's Encrypt",
"letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>", "letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>",
"delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.", "delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.",
"hosts-warning": "These domains must be already configured to point to this installation" "hosts-warning": "These domains must be already configured to point to this installation",
"use-cloudflare": "Use CloudFlare DNS verification"
}, },
"proxy-hosts": { "proxy-hosts": {
"title": "Proxy Hosts", "title": "Proxy Hosts",

File diff suppressed because it is too large Load Diff

3
test/.gitignore vendored
View File

@ -1,3 +1,4 @@
.vscode .vscode
node_modules node_modules
results
cypress/videos

View File

@ -1,4 +1,4 @@
FROM cypress/included:4.6.0 FROM cypress/included:4.12.1
COPY --chown=1000 ./test /test COPY --chown=1000 ./test /test

View File

@ -1,15 +1,12 @@
{ {
"requestTimeout": 30000, "requestTimeout": 30000,
"defaultCommandTimeout": 20000, "defaultCommandTimeout": 20000,
"reporter": "mocha-junit-reporter", "reporter": "cypress-multi-reporters",
"reporterOptions": { "reporterOptions": {
"jenkinsMode": true, "configFile": "multi-reporter.json"
"rootSuiteTitle": "Cypress",
"jenkinsClassnamePrefix": "Cypress.",
"mochaFile": "/results/junit/my-test-output-[hash].xml"
}, },
"videosFolder": "/results/videos", "videosFolder": "results/videos",
"screenshotsFolder": "/results/screenshots", "screenshotsFolder": "results/screenshots",
"env": { "env": {
"swaggerBase": "{{baseUrl}}/api/schema", "swaggerBase": "{{baseUrl}}/api/schema",
"RETRIES": 4 "RETRIES": 4

View File

@ -1,13 +1,14 @@
{ {
"requestTimeout": 30000, "requestTimeout": 30000,
"defaultCommandTimeout": 20000, "defaultCommandTimeout": 20000,
"reporter": "junit", "reporter": "cypress-multi-reporters",
"reporterOptions": { "reporterOptions": {
"mochaFile": "results/junit/my-test-output-[hash].xml" "configFile": "multi-reporter.json"
}, },
"video": false, "videos": false,
"screenshotsFolder": "cypress/results/screenshots", "screenshotsFolder": "results/screenshots",
"env": { "env": {
"swaggerBase": "{{baseUrl}}/api/schema" "swaggerBase": "{{baseUrl}}/api/schema",
"RETRIES": 0
} }
} }

View File

@ -2,17 +2,15 @@
describe('Basic API checks', () => { describe('Basic API checks', () => {
it('Should return a valid health payload', function () { it('Should return a valid health payload', function () {
cy.wait(2000);
cy.task('backendApiGet', { cy.task('backendApiGet', {
path: '/api/', path: '/api/',
}).then((data) => { }).then((data) => {
// Check the swagger schema: // Check the swagger schema:
cy.validateSwaggerSchema('get', '/', data); cy.validateSwaggerSchema('get', 200, '/', data);
}); });
}); });
it('Should return a valid schema payload', function () { it('Should return a valid schema payload', function () {
cy.wait(2000);
cy.task('backendApiGet', { cy.task('backendApiGet', {
path: '/api/schema', path: '/api/schema',
}).then((data) => { }).then((data) => {

View File

@ -0,0 +1,48 @@
/// <reference types="Cypress" />
describe('Users endpoints', () => {
let token;
before(() => {
cy.getToken().then((tok) => {
token = tok;
});
});
it('Should be able to get yourself', function() {
cy.task('backendApiGet', {
token: token,
path: '/api/users/me'
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/users/{userID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
});
});
it('Should be able to get all users', function() {
cy.task('backendApiGet', {
token: token,
path: '/api/users'
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/users', data);
expect(data.length).to.be.greaterThan(0);
});
});
it('Should be able to update yourself', function() {
cy.task('backendApiPut', {
token: token,
path: '/api/users/me',
data: {
name: 'changed name'
}
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/users/{userID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data.name).to.be.equal('changed name');
});
});
});

View File

@ -13,82 +13,30 @@
* Check the swagger schema: * Check the swagger schema:
* *
* @param {string} method API Method in swagger doc, "get", "put", "post", "delete" * @param {string} method API Method in swagger doc, "get", "put", "post", "delete"
* @param {number} statusCode API status code in swagger doc
* @param {string} path Swagger doc endpoint path, exactly as defined in swagger doc * @param {string} path Swagger doc endpoint path, exactly as defined in swagger doc
* @param {*} data The API response data to check against the swagger schema * @param {*} data The API response data to check against the swagger schema
*/ */
Cypress.Commands.add('validateSwaggerSchema', (method, path, data) => { Cypress.Commands.add('validateSwaggerSchema', (method, statusCode, path, data) => {
cy.task('validateSwaggerSchema', { cy.task('validateSwaggerSchema', {
file: Cypress.env('swaggerBase'), file: Cypress.env('swaggerBase'),
endpoint: path, endpoint: path,
method: method, method: method,
statusCode: 200, statusCode: statusCode,
responseSchema: data, responseSchema: data,
verbose: true verbose: true
}).should('equal', null); }).should('equal', null);
}); });
Cypress.Commands.add('getToken', () => { Cypress.Commands.add('getToken', () => {
cy.task('backendApiGet', {
path: '/api/',
}).then((data) => {
// Check the swagger schema:
cy.task('validateSwaggerSchema', {
endpoint: '/',
method: 'get',
statusCode: 200,
responseSchema: data,
verbose: true,
}).should('equal', null);
if (!data.result.setup) {
cy.log('Setup = false');
// create a new user
cy.createInitialUser().then(() => {
return cy.getToken();
});
} else {
cy.log('Setup = true');
// login with existing user // login with existing user
cy.task('backendApiPost', { cy.task('backendApiPost', {
path: '/api/tokens', path: '/api/tokens',
data: { data: {
type: 'password', identity: 'admin@example.com',
identity: 'jc@jc21.com',
secret: 'changeme' secret: 'changeme'
} }
}).then(res => { }).then(res => {
cy.wrap(res.result.token); cy.wrap(res.token);
});
}
});
});
Cypress.Commands.add('createInitialUser', () => {
return cy.task('backendApiPost', {
path: '/api/users',
data: {
name: 'Jamie Curnow',
nickname: 'James',
email: 'jc@jc21.com',
roles: [],
is_disabled: false,
auth: {
type: 'password',
secret: 'changeme'
}
}
}).then((data) => {
// Check the swagger schema:
cy.task('validateSwaggerSchema', {
endpoint: '/users',
method: 'post',
statusCode: 201,
responseSchema: data,
verbose: true
}).should('equal', null);
expect(data.result).to.have.property('id');
expect(data.result.id).to.be.greaterThan(0);
cy.wrap(data.result);
}); });
}); });

9
test/multi-reporter.json Normal file
View File

@ -0,0 +1,9 @@
{
"reporterEnabled": "spec, mocha-junit-reporter",
"mochaJunitReporterReporterOptions": {
"jenkinsMode": true,
"rootSuiteTitle": "Cypress.npm",
"jenkinsClassnamePrefix": "Cypress.npm.",
"mochaFile": "results/junit/cypress.npm.[hash].xml"
}
}

View File

@ -4,21 +4,23 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@jc21/cypress-swagger-validation": "^0.0.5", "@jc21/cypress-swagger-validation": "^0.0.9",
"@jc21/restler": "^3.4.0", "@jc21/restler": "^3.4.0",
"chalk": "^3.0.0", "chalk": "^4.1.0",
"cypress": "^4.6.0", "cypress": "^4.12.1",
"cypress-multi-reporters": "^1.4.0",
"cypress-plugin-retries": "^1.5.2", "cypress-plugin-retries": "^1.5.2",
"eslint": "^6.7.2", "eslint": "^7.6.0",
"eslint-plugin-align-assignments": "^1.1.2", "eslint-plugin-align-assignments": "^1.1.2",
"eslint-plugin-chai-friendly": "^0.5.0", "eslint-plugin-chai-friendly": "^0.6.0",
"eslint-plugin-cypress": "^2.8.0", "eslint-plugin-cypress": "^2.11.1",
"lodash": "^4.17.15", "lodash": "^4.17.19",
"mocha": "^6.2.2", "mocha": "^8.1.1",
"mocha-junit-reporter": "^1.23.1" "mocha-junit-reporter": "^2.0.0"
}, },
"scripts": { "scripts": {
"cypress": "cypress open --config-file=cypress/config/dev.json --config baseUrl=http://127.0.0.1:3081" "cypress": "cypress open --config-file=cypress/config/dev.json --config baseUrl=${BASE_URL:-http://127.0.0.1:3081}",
"cypress:headless": "cypress run --config-file=cypress/config/dev.json --config baseUrl=${BASE_URL:-http://127.0.0.1:3081}"
}, },
"author": "", "author": "",
"license": "ISC" "license": "ISC"

File diff suppressed because it is too large Load Diff