From c6933a2d20d30780d66729c3e26d7f03f8509adc Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 11 Jan 2020 22:33:19 -0500 Subject: [PATCH] feat: let's encrypt --- .../admin/admin-pages-visualize.vue | 55 ++++-- config.sample.yml | 11 +- dev/build/Dockerfile | 6 +- dev/build/config.yml | 7 +- dev/examples/docker-compose.yml | 1 + dev/index.js | 4 +- package.json | 5 + server/controllers/letsencrypt.js | 25 +++ server/core/kernel.js | 1 + server/core/letsencrypt.js | 125 ++++++++++++++ server/core/servers.js | 159 ++++++++++++++++++ server/master.js | 142 ++-------------- yarn.lock | 76 ++++++++- 13 files changed, 458 insertions(+), 159 deletions(-) create mode 100644 server/controllers/letsencrypt.js create mode 100644 server/core/letsencrypt.js create mode 100644 server/core/servers.js diff --git a/client/components/admin/admin-pages-visualize.vue b/client/components/admin/admin-pages-visualize.vue index 568ef91f..765d7ef9 100644 --- a/client/components/admin/admin-pages-visualize.vue +++ b/client/components/admin/admin-pages-visualize.vue @@ -29,7 +29,6 @@ v-btn.px-5(value='rradial') v-icon(left, :color='graphMode === `rradial` ? `primary` : `grey darken-3`') mdi-blur-radial span.text-none Relational Radial - v-chip.ml-3(x-small) Beta .admin-pages-visualize-svg.pa-10(ref='svgContainer') v-alert(v-if='pages.length < 1', outlined, type='warning', style='max-width: 650px; margin: 0 auto;') Looks like there's no data yet to graph! @@ -61,9 +60,14 @@ export default { } }, methods: { + goToPage (d) { + if (_.get(d, 'data.id', 0) > 0) { + this.$router.push(`${d.data.id}`) + } + }, bilink (root) { - const map = new Map(root.leaves().map(d => [d.data.path, d])) - for (const d of root.leaves()) { + const map = new Map(root.descendants().map(d => [d.data.path, d])) + for (const d of root.descendants()) { d.incoming = [] d.outgoing = [] d.data.links.forEach(i => { @@ -73,7 +77,7 @@ export default { } }) } - for (const d of root.leaves()) { + for (const d of root.descendants()) { for (const o of d.outgoing) { if (o[1]) { o[1].incoming.push(o) @@ -112,8 +116,11 @@ export default { children: result } }, + /** + * Relational Radial + */ drawRelations () { - const data = this.hierarchy(this.pages) + const data = this.hierarchy(this.pages, true) const line = d3.lineRadial() .curve(d3.curveBundle.beta(0.85)) @@ -124,16 +131,26 @@ export default { .size([2 * Math.PI, this.radius - 100]) const root = tree(this.bilink(d3.hierarchy(data) - .sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.title, b.data.title)))) + .sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.path, b.data.path)))) const svg = d3.create('svg') .attr('viewBox', [-this.width / 2, -this.width / 2, this.width, this.width]) + const link = svg.append('g') + .attr('stroke', '#CCC') + .attr('fill', 'none') + .selectAll('path') + .data(root.descendants().flatMap(leaf => leaf.outgoing)) + .join('path') + .style('mix-blend-mode', 'multiply') + .attr('d', ([i, o]) => line(i.path(o))) + .each(function(d) { d.path = this }) + svg.append('g') .attr('font-family', 'sans-serif') .attr('font-size', 10) .selectAll('g') - .data(root.leaves()) + .data(root.descendants()) .join('g') .attr('transform', d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`) .append('text') @@ -142,23 +159,17 @@ export default { .attr('text-anchor', d => d.x < Math.PI ? 'start' : 'end') .attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null) .attr('fill', this.$vuetify.theme.dark ? 'white' : '') + .attr('cursor', 'pointer') .text(d => d.data.title) .each(function(d) { d.text = this }) .on('mouseover', overed) .on('mouseout', outed) + .on('click', d => this.goToPage(d)) .call(text => text.append('title').text(d => `${d.data.path} ${d.outgoing.length} outgoing ${d.incoming.length} incoming`)) - - const link = svg.append('g') - .attr('stroke', '#CCC') - .attr('fill', 'none') - .selectAll('path') - .data(root.leaves().flatMap(leaf => leaf.outgoing)) - .join('path') - .style('mix-blend-mode', 'multiply') - .attr('d', ([i, o]) => line(i.path(o))) - .each(function(d) { d.path = this }) + .clone(true).lower() + .attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white') function overed(d) { link.style('mix-blend-mode', null) @@ -180,6 +191,9 @@ export default { this.$refs.svgContainer.appendChild(svg.node()) }, + /** + * Hierarchical Tree + */ drawTree () { const data = this.hierarchy(this.pages, true) @@ -232,12 +246,17 @@ export default { .attr('x', d => d.children ? -6 : 6) .attr('text-anchor', d => d.children ? 'end' : 'start') .attr('fill', this.$vuetify.theme.dark ? 'white' : '') + .attr('cursor', 'pointer') .text(d => d.data.title) + .on('click', d => this.goToPage(d)) .clone(true).lower() .attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white') this.$refs.svgContainer.appendChild(svg.node()) }, + /** + * Hierarchical Radial + */ drawRadialTree () { const data = this.hierarchy(this.pages) @@ -286,7 +305,9 @@ export default { .attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null) /* eslint-enable no-mixed-operators */ .attr('fill', this.$vuetify.theme.dark ? 'white' : '') + .attr('cursor', 'pointer') .text(d => d.data.title) + .on('click', d => this.goToPage(d)) .clone(true).lower() .attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white') diff --git a/config.sample.yml b/config.sample.yml index 4b520310..d3073231 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -59,7 +59,12 @@ db: ssl: enabled: false + port: 3443 + # Provider to use, possible values: custom, letsencrypt + provider: custom + + # ++++++ For custom only ++++++ # Certificate format, either 'pem' or 'pfx': format: pem # Using PEM format: @@ -73,9 +78,9 @@ ssl: # to 1024 bits (default: null): dhparam: null - # Listen on this HTTP port and redirect all requests to HTTPS. - # Set to false to disable (default: 80): - redirectNonSSLPort: 80 + # ++++++ For letsencrypt only ++++++ + domain: wiki.yourdomain.com + maintainerEmail: admin@example.com # --------------------------------------------------------------------- # Database Pool Options diff --git a/dev/build/Dockerfile b/dev/build/Dockerfile index 7c520a8c..80b18135 100644 --- a/dev/build/Dockerfile +++ b/dev/build/Dockerfile @@ -29,6 +29,7 @@ LABEL maintainer="requarks.io" RUN apk add bash curl git openssh gnupg sqlite --no-cache && \ mkdir -p /wiki && \ mkdir -p /logs && \ + mkdir -p /wiki/data/content && \ chown -R node:node /wiki /logs WORKDIR /wiki @@ -44,8 +45,11 @@ COPY --chown=node:node ./LICENSE ./LICENSE USER node +VOLUME ["/wiki/data/content"] + EXPOSE 3000 +EXPOSE 3443 -# HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://localhost/healthz +# HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://localhost:3000/healthz CMD ["node", "server"] diff --git a/dev/build/config.yml b/dev/build/config.yml index 46f1a868..ca082f64 100644 --- a/dev/build/config.yml +++ b/dev/build/config.yml @@ -9,5 +9,10 @@ db: db: $(DB_NAME) storage: $(DB_FILEPATH) ssl: $(DB_SSL) -trustProxy: $(TRUST_PROXY) +ssl: + enabled: $(SSL_ACTIVE) + port: 3443 + provider: letsencrypt + domain: $(LETSENCRYPT_DOMAIN) + maintainerEmail: $(LETSENCRYPT_EMAIL) logLevel: info diff --git a/dev/examples/docker-compose.yml b/dev/examples/docker-compose.yml index 7ab65cf0..28009b07 100644 --- a/dev/examples/docker-compose.yml +++ b/dev/examples/docker-compose.yml @@ -27,6 +27,7 @@ services: restart: unless-stopped ports: - "80:3000" + - "443:3443" volumes: db-data: diff --git a/dev/index.js b/dev/index.js index aa3bf14b..8f8d759b 100644 --- a/dev/index.js +++ b/dev/index.js @@ -67,8 +67,8 @@ const init = { console.warn(chalk.yellow('--- Closing DB connections...')) await global.WIKI.models.knex.destroy() console.warn(chalk.yellow('--- Closing Server connections...')) - if (global.WIKI.server) { - await new Promise((resolve, reject) => global.WIKI.server.destroy(resolve)) + if (global.WIKI.servers) { + await global.WIKI.servers.stopServers() } console.warn(chalk.yellow('--- Purging node modules cache...')) diff --git a/package.json b/package.json index c4ce2a63..3eada1c9 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,10 @@ "@azure/storage-blob": "12.0.1", "@bugsnag/js": "6.5.0", "@exlinc/keycloak-passport": "1.0.2", + "@root/csr": "0.8.1", + "@root/keypairs": "0.9.0", + "@root/pem": "1.0.4", + "acme": "3.0.3", "algoliasearch": "3.35.1", "apollo-fetch": "0.7.0", "apollo-server": "2.9.15", @@ -143,6 +147,7 @@ "pg-query-stream": "2.1.2", "pg-tsquery": "8.1.0", "pug": "2.0.4", + "punycode": "2.1.1", "qr-image": "3.2.0", "raven": "2.6.4", "remove-markdown": "0.3.0", diff --git a/server/controllers/letsencrypt.js b/server/controllers/letsencrypt.js new file mode 100644 index 00000000..8d4d39f4 --- /dev/null +++ b/server/controllers/letsencrypt.js @@ -0,0 +1,25 @@ +const express = require('express') +const router = express.Router() +const _ = require('lodash') + +/* global WIKI */ + +/** + * Let's Encrypt Challenge + */ +router.get('/.well-known/acme-challenge/:token', (req, res, next) => { + res.type('text/plain') + if (_.get(WIKI.config, 'letsencrypt.challenge', false)) { + if (WIKI.config.letsencrypt.challenge.token === req.params.token) { + res.send(WIKI.config.letsencrypt.challenge.keyAuthorization) + WIKI.logger.info(`(LETSENCRYPT) Received valid challenge request. [ ACCEPTED ]`) + } else { + res.status(406).send('Invalid Challenge Token!') + WIKI.logger.warn(`(LETSENCRYPT) Received invalid challenge request. [ REJECTED ]`) + } + } else { + res.status(418).end() + } +}) + +module.exports = router diff --git a/server/core/kernel.js b/server/core/kernel.js index f1f87860..050769ed 100644 --- a/server/core/kernel.js +++ b/server/core/kernel.js @@ -34,6 +34,7 @@ module.exports = { await this.initTelemetry() WIKI.cache = require('./cache').init() WIKI.scheduler = require('./scheduler').init() + WIKI.servers = require('./servers') WIKI.sideloader = require('./sideloader').init() WIKI.events = new EventEmitter() } catch (err) { diff --git a/server/core/letsencrypt.js b/server/core/letsencrypt.js new file mode 100644 index 00000000..f3c67e95 --- /dev/null +++ b/server/core/letsencrypt.js @@ -0,0 +1,125 @@ +const ACME = require('acme') +const Keypairs = require('@root/keypairs') +const _ = require('lodash') +const moment = require('moment') +const CSR = require('@root/csr') +const PEM = require('@root/pem') +// eslint-disable-next-line node/no-deprecated-api +const punycode = require('punycode') + +/* global WIKI */ + +module.exports = { + apiDirectory: WIKI.dev ? 'https://acme-staging-v02.api.letsencrypt.org/directory' : 'https://acme-v02.api.letsencrypt.org/directory', + acme: null, + async init () { + if (!_.get(WIKI.config, 'letsencrypt.payload', false)) { + await this.requestCertificate() + } else if (WIKI.config.letsencrypt.domain !== WIKI.config.ssl.domain) { + WIKI.logger.info(`(LETSENCRYPT) Domain has changed. Requesting new certificates...`) + await this.requestCertificate() + } else if (moment(WIKI.config.letsencrypt.payload.expires).isSameOrBefore(moment().add(5, 'days'))) { + WIKI.logger.info(`(LETSENCRYPT) Certificate is about to or has expired, requesting a new one...`) + await this.requestCertificate() + } else { + WIKI.logger.info(`(LETSENCRYPT) Using existing certificate for ${WIKI.config.ssl.domain}, expires on ${WIKI.config.letsencrypt.payload.expires}: [ OK ]`) + } + WIKI.config.ssl.format = 'pem' + WIKI.config.ssl.inline = true + WIKI.config.ssl.key = WIKI.config.letsencrypt.serverKey + WIKI.config.ssl.cert = WIKI.config.letsencrypt.payload.cert + '\n' + WIKI.config.letsencrypt.payload.chain + WIKI.config.ssl.passphrase = null + WIKI.config.ssl.dhparam = null + }, + async requestCertificate () { + try { + WIKI.logger.info(`(LETSENCRYPT) Initializing Let's Encrypt client...`) + this.acme = ACME.create({ + maintainerEmail: WIKI.config.ssl.maintainerEmail, + packageAgent: `wikijs/${WIKI.version}`, + notify: (ev, msg) => { + if (_.includes(['warning', 'error'], ev)) { + WIKI.logger.warn(`${ev}: ${msg}`) + } else { + WIKI.logger.debug(`${ev}: ${JSON.stringify(msg)}`) + } + } + }) + + await this.acme.init(this.apiDirectory) + + // -> Create ACME Subscriber account + + if (!_.get(WIKI.config, 'letsencrypt.account', false)) { + WIKI.logger.info(`(LETSENCRYPT) Setting up account for the first time...`) + const accountKeypair = await Keypairs.generate({ kty: 'EC', format: 'jwk' }) + const account = await this.acme.accounts.create({ + subscriberEmail: WIKI.config.ssl.maintainerEmail, + agreeToTerms: true, + accountKey: accountKeypair.private + }) + WIKI.config.letsencrypt = { + accountKeypair: accountKeypair, + account: account, + domain: WIKI.config.ssl.domain + } + await WIKI.configSvc.saveToDb(['letsencrypt']) + WIKI.logger.info(`(LETSENCRYPT) Account was setup successfully [ OK ]`) + } + + // -> Create Server Keypair + + if (!WIKI.config.letsencrypt.serverKey) { + WIKI.logger.info(`(LETSENCRYPT) Generating server keypairs...`) + const serverKeypair = await Keypairs.generate({ kty: 'RSA', format: 'jwk' }) + WIKI.config.letsencrypt.serverKey = await Keypairs.export({ jwk: serverKeypair.private }) + WIKI.logger.info(`(LETSENCRYPT) Server keypairs generated successfully [ OK ]`) + } + + // -> Create CSR + + WIKI.logger.info(`(LETSENCRYPT) Generating certificate signing request (CSR)...`) + const domains = [ punycode.toASCII(WIKI.config.ssl.domain) ] + const serverKey = await Keypairs.import({ pem: WIKI.config.letsencrypt.serverKey }) + const csrDer = await CSR.csr({ jwk: serverKey, domains, encoding: 'der' }) + const csr = PEM.packBlock({ type: 'CERTIFICATE REQUEST', bytes: csrDer }) + WIKI.logger.info(`(LETSENCRYPT) CSR generated successfully [ OK ]`) + + // -> Verify Domain + Get Certificate + + WIKI.logger.info(`(LETSENCRYPT) Requesting certificate from Let's Encrypt...`) + const certResp = await this.acme.certificates.create({ + account: WIKI.config.letsencrypt.account, + accountKey: WIKI.config.letsencrypt.accountKeypair.private, + csr, + domains, + challenges: { + 'http-01': { + init () {}, + set (data) { + WIKI.logger.info(`(LETSENCRYPT) Setting HTTP challenge for ${data.challenge.hostname}: [ READY ]`) + WIKI.config.letsencrypt.challenge = data.challenge + WIKI.logger.info(`(LETSENCRYPT) Waiting for challenge to complete...`) + return null // <- this is needed, cannot be undefined + }, + get (data) { + return WIKI.config.letsencrypt.challenge + }, + async remove (data) { + WIKI.logger.info(`(LETSENCRYPT) Removing HTTP challenge: [ OK ]`) + WIKI.config.letsencrypt.challenge = null + return null // <- this is needed, cannot be undefined + } + } + } + }) + WIKI.logger.info(`(LETSENCRYPT) New certifiate received successfully: [ COMPLETED ]`) + WIKI.config.letsencrypt.payload = certResp + WIKI.config.letsencrypt.domain = WIKI.config.ssl.domain + await WIKI.configSvc.saveToDb(['letsencrypt']) + } catch (err) { + WIKI.logger.warn(`(LETSENCRYPT) ${err}`) + throw err + } + } +} diff --git a/server/core/servers.js b/server/core/servers.js new file mode 100644 index 00000000..d2c5f4da --- /dev/null +++ b/server/core/servers.js @@ -0,0 +1,159 @@ +const fs = require('fs-extra') +const http = require('http') +const https = require('https') +const { ApolloServer } = require('apollo-server-express') +const Promise = require('bluebird') +const _ = require('lodash') + +/* global WIKI */ + +module.exports = { + servers: { + graph: null, + http: null, + https: null + }, + connections: new Map(), + le: null, + /** + * Start HTTP Server + */ + async startHTTP () { + WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`) + this.servers.http = http.createServer(WIKI.app) + this.servers.graph.installSubscriptionHandlers(this.servers.http) + + this.servers.http.listen(WIKI.config.port, WIKI.config.bindIP) + this.servers.http.on('error', (error) => { + if (error.syscall !== 'listen') { + throw error + } + + switch (error.code) { + case 'EACCES': + WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!') + return process.exit(1) + case 'EADDRINUSE': + WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!') + return process.exit(1) + default: + throw error + } + }) + + this.servers.http.on('listening', () => { + WIKI.logger.info('HTTP Server: [ RUNNING ]') + }) + + this.servers.http.on('connection', conn => { + let connKey = `${conn.remoteAddress}:${conn.remotePort}` + this.connections.set(connKey, conn) + conn.on('close', () => { + this.connections.delete(connKey) + }) + }) + }, + /** + * Start HTTPS Server + */ + async startHTTPS () { + if (WIKI.config.ssl.provider === 'letsencrypt') { + this.le = require('./letsencrypt') + await this.le.init() + } + + WIKI.logger.info(`HTTPS Server on port: [ ${WIKI.config.ssl.port} ]`) + const tlsOpts = {} + try { + if (WIKI.config.ssl.format === 'pem') { + tlsOpts.key = WIKI.config.ssl.inline ? WIKI.config.ssl.key : fs.readFileSync(WIKI.config.ssl.key) + tlsOpts.cert = WIKI.config.ssl.inline ? WIKI.config.ssl.cert : fs.readFileSync(WIKI.config.ssl.cert) + } else { + tlsOpts.pfx = WIKI.config.ssl.inline ? WIKI.config.ssl.pfx : fs.readFileSync(WIKI.config.ssl.pfx) + } + if (!_.isEmpty(WIKI.config.ssl.passphrase)) { + tlsOpts.passphrase = WIKI.config.ssl.passphrase + } + if (!_.isEmpty(WIKI.config.ssl.dhparam)) { + tlsOpts.dhparam = WIKI.config.ssl.dhparam + } + } catch (err) { + WIKI.logger.error('Failed to setup HTTPS server parameters:') + WIKI.logger.error(err) + return process.exit(1) + } + this.servers.https = https.createServer(tlsOpts, WIKI.app) + this.servers.graph.installSubscriptionHandlers(this.servers.https) + + this.servers.https.listen(WIKI.config.ssl.port, WIKI.config.bindIP) + this.servers.https.on('error', (error) => { + if (error.syscall !== 'listen') { + throw error + } + + switch (error.code) { + case 'EACCES': + WIKI.logger.error('Listening on port ' + WIKI.config.ssl.port + ' requires elevated privileges!') + return process.exit(1) + case 'EADDRINUSE': + WIKI.logger.error('Port ' + WIKI.config.ssl.port + ' is already in use!') + return process.exit(1) + default: + throw error + } + }) + + this.servers.https.on('listening', () => { + WIKI.logger.info('HTTPS Server: [ RUNNING ]') + }) + + this.servers.https.on('connection', conn => { + let connKey = `${conn.remoteAddress}:${conn.remotePort}` + this.connections.set(connKey, conn) + conn.on('close', () => { + this.connections.delete(connKey) + }) + }) + }, + /** + * Start GraphQL Server + */ + async startGraphQL () { + const graphqlSchema = require('../graph') + this.servers.graph = new ApolloServer({ + ...graphqlSchema, + context: ({ req, res }) => ({ req, res }), + subscriptions: { + onConnect: (connectionParams, webSocket) => { + + }, + path: '/graphql-subscriptions' + } + }) + this.servers.graph.applyMiddleware({ app: WIKI.app }) + }, + /** + * Close all active connections + */ + closeConnections () { + for (const conn of this.connections) { + conn.destroy() + } + this.connections.clear() + }, + /** + * Stop all servers + */ + async stopServers () { + this.closeConnections() + if (this.servers.http) { + await Promise.fromCallback(cb => { this.servers.http.close(cb) }) + this.servers.http = null + } + if (this.servers.https) { + await Promise.fromCallback(cb => { this.servers.https.close(cb) }) + this.servers.https = null + } + this.servers.graph = null + } +} diff --git a/server/master.js b/server/master.js index c6de57dc..1369bdf6 100644 --- a/server/master.js +++ b/server/master.js @@ -7,12 +7,8 @@ const express = require('express') const session = require('express-session') const KnexSessionStore = require('connect-session-knex')(session) const favicon = require('serve-favicon') -const fs = require('fs-extra') -const http = require('http') -const https = require('https') const path = require('path') const _ = require('lodash') -const { ApolloServer } = require('apollo-server-express') /* global WIKI */ @@ -62,6 +58,12 @@ module.exports = async () => { maxAge: '7d' })) + // ---------------------------------------- + // Let's Encrypt Challenge + // ---------------------------------------- + + app.use('/', ctrl.letsencrypt) + // ---------------------------------------- // Passport Authentication // ---------------------------------------- @@ -104,6 +106,7 @@ module.exports = async () => { // View accessible data // ---------------------------------------- + app.locals.siteConfig = {} app.locals.analyticsCode = {} app.locals.basedir = WIKI.ROOTPATH app.locals.config = WIKI.config @@ -124,23 +127,6 @@ module.exports = async () => { app.use(global.WP_DEV.hotMiddleware) } - // ---------------------------------------- - // Apollo Server (GraphQL) - // ---------------------------------------- - - const graphqlSchema = require('./graph') - const apolloServer = new ApolloServer({ - ...graphqlSchema, - context: ({ req, res }) => ({ req, res }), - subscriptions: { - onConnect: (connectionParams, webSocket) => { - - }, - path: '/graphql-subscriptions' - } - }) - apolloServer.applyMiddleware({ app }) - // ---------------------------------------- // Routing // ---------------------------------------- @@ -184,118 +170,14 @@ module.exports = async () => { }) // ---------------------------------------- - // HTTP/S server + // Start HTTP Server(s) // ---------------------------------------- - let srvConnections = {} - - app.set('port', WIKI.config.port) - if (WIKI.config.ssl.enabled) { - WIKI.logger.info(`HTTPS Server on port: [ ${WIKI.config.port} ]`) - const tlsOpts = {} - try { - if (WIKI.config.ssl.format === 'pem') { - tlsOpts.key = fs.readFileSync(WIKI.config.ssl.key) - tlsOpts.cert = fs.readFileSync(WIKI.config.ssl.cert) - } else { - tlsOpts.pfx = fs.readFileSync(WIKI.config.ssl.pfx) - } - if (!_.isEmpty(WIKI.config.ssl.passphrase)) { - tlsOpts.passphrase = WIKI.config.ssl.passphrase - } - if (!_.isEmpty(WIKI.config.ssl.dhparam)) { - tlsOpts.dhparam = WIKI.config.ssl.dhparam - } - } catch (err) { - WIKI.logger.error('Failed to setup HTTPS server parameters:') - WIKI.logger.error(err) - return process.exit(1) - } - WIKI.server = https.createServer(tlsOpts, app) - - // HTTP Redirect Server - if (WIKI.config.ssl.redirectNonSSLPort) { - WIKI.serverAlt = http.createServer((req, res) => { - res.writeHead(301, { 'Location': 'https://' + req.headers['host'] + req.url }) - res.end() - }) - } - } else { - WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`) - WIKI.server = http.createServer(app) - } - apolloServer.installSubscriptionHandlers(WIKI.server) - - WIKI.server.listen(WIKI.config.port, WIKI.config.bindIP) - WIKI.server.on('error', (error) => { - if (error.syscall !== 'listen') { - throw error - } - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!') - return process.exit(1) - case 'EADDRINUSE': - WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!') - return process.exit(1) - default: - throw error - } - }) - - WIKI.server.on('connection', conn => { - let key = `${conn.remoteAddress}:${conn.remotePort}` - srvConnections[key] = conn - conn.on('close', function() { - delete srvConnections[key] - }) - }) - - WIKI.server.on('listening', () => { - if (WIKI.config.ssl.enabled) { - WIKI.logger.info('HTTPS Server: [ RUNNING ]') - - // Start HTTP Redirect Server - if (WIKI.config.ssl.redirectNonSSLPort) { - WIKI.serverAlt.listen(WIKI.config.ssl.redirectNonSSLPort, WIKI.config.bindIP) - - WIKI.serverAlt.on('error', (error) => { - if (error.syscall !== 'listen') { - throw error - } - - switch (error.code) { - case 'EACCES': - WIKI.logger.error('(HTTP Redirect) Listening on port ' + WIKI.config.port + ' requires elevated privileges!') - return process.exit(1) - case 'EADDRINUSE': - WIKI.logger.error('(HTTP Redirect) Port ' + WIKI.config.port + ' is already in use!') - return process.exit(1) - default: - throw error - } - }) - - WIKI.serverAlt.on('listening', () => { - WIKI.logger.info('HTTP Server: [ RUNNING in redirect mode ]') - }) - } - } else { - WIKI.logger.info('HTTP Server: [ RUNNING ]') - } - }) + await WIKI.servers.startGraphQL() + await WIKI.servers.startHTTP() - WIKI.server.destroy = (cb) => { - WIKI.server.close(cb) - for (let key in srvConnections) { - srvConnections[key].destroy() - } - - if (WIKI.config.ssl.enabled && WIKI.config.ssl.redirectNonSSLPort) { - WIKI.serverAlt.close(cb) - } + if (WIKI.config.ssl.enabled === true || WIKI.config.ssl.enabled === 'true' || WIKI.config.ssl.enabled === 1 || WIKI.config.ssl.enabled === '1') { + await WIKI.servers.startHTTPS() } return true diff --git a/yarn.lock b/yarn.lock index e7070238..a1218270 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1553,6 +1553,65 @@ resolved "https://registry.yarnpkg.com/@requarks/ckeditor5/-/ckeditor5-12.4.0-wiki.14.tgz#eca91568cc5f1471fff684df18c638dfab9f0aef" integrity sha512-3DD2GmagyTYeSHsm437FR0OxlzNFYnc3PEflt7p0/mTM68nRHUHv/pO9wvbUXaRk667WKsRVqckbbMQbdRK43g== +"@root/acme@^3.0.2": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@root/acme/-/acme-3.0.9.tgz#437f4b46015e70587fb50119807889c944370388" + integrity sha512-/FgJF6RUrkqNpLmxqjktHaWMsLOwma6D+e4EBoxKtTjTAI+dBqW8Z8cH38feUsiIBR5LimPeYmBo/oqU3oMkKQ== + dependencies: + "@root/encoding" "^1.0.1" + "@root/keypairs" "^0.9.0" + "@root/pem" "^1.0.4" + "@root/request" "^1.3.11" + "@root/x509" "^0.7.2" + +"@root/asn1@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@root/asn1/-/asn1-1.0.0.tgz#8748cf7b4497324de91a154b606ca1ddfe97c5d7" + integrity sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig== + dependencies: + "@root/encoding" "^1.0.1" + +"@root/csr@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@root/csr/-/csr-0.8.1.tgz#97a1b821331a4ed5895eee33bedb6ad49cdef74e" + integrity sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ== + dependencies: + "@root/asn1" "^1.0.0" + "@root/pem" "^1.0.4" + "@root/x509" "^0.7.2" + +"@root/encoding@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@root/encoding/-/encoding-1.0.1.tgz#60cf3ccb9e2239226b30e8394c7a4a258a161c3e" + integrity sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ== + +"@root/keypairs@0.9.0", "@root/keypairs@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@root/keypairs/-/keypairs-0.9.0.tgz#0340a8a9cd865a9adc3ad5dd1f98221a7cb310df" + integrity sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg== + dependencies: + "@root/encoding" "^1.0.1" + "@root/pem" "^1.0.4" + "@root/x509" "^0.7.2" + +"@root/pem@1.0.4", "@root/pem@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@root/pem/-/pem-1.0.4.tgz#9ecc3419c0a05c92b8ecbfe0a41f474970f8c72f" + integrity sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA== + +"@root/request@^1.3.11": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@root/request/-/request-1.4.2.tgz#a51e93c64eb8b9b0df06f34677e63d8239c4311c" + integrity sha512-J8FM4+SJuc7WRC+Jz17m+VT2lgI7HtatHhxN1F2ck5aIKUAxJEaR4u/gLBsgT60mVHevKCjKN0O8115UtJjwLw== + +"@root/x509@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@root/x509/-/x509-0.7.2.tgz#422ca7e46c09f5ddf3af86306383778eb004ace1" + integrity sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ== + dependencies: + "@root/asn1" "^1.0.0" + "@root/encoding" "^1.0.1" + "@types/accepts@*", "@types/accepts@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" @@ -2232,6 +2291,13 @@ accepts@^1.3.5, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acme@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/acme/-/acme-3.0.3.tgz#e3dd9086027118d2cda7bf72572446c02e0696ea" + integrity sha512-NMEugs9cWvryNRidu9GeyV3EbqDlb5XFKQjaJ6E99RFpzrHM/iYYLImdrdpnkbS4vFTCrB2nMA7MQB0vAk801w== + dependencies: + "@root/acme" "^3.0.2" + acorn-globals@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" @@ -12071,16 +12137,16 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= +punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - q@^1.1.2, q@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"