feat: let's encrypt
This commit is contained in:
parent
73da73a595
commit
c6933a2d20
@ -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!
|
||||
</template>
|
||||
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
EXPOSE 3000
|
||||
VOLUME ["/wiki/data/content"]
|
||||
|
||||
# HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://localhost/healthz
|
||||
EXPOSE 3000
|
||||
EXPOSE 3443
|
||||
|
||||
# HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://localhost:3000/healthz
|
||||
|
||||
CMD ["node", "server"]
|
||||
|
@ -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
|
||||
|
@ -27,6 +27,7 @@ services:
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:3000"
|
||||
- "443:3443"
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
|
@ -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...'))
|
||||
|
||||
|
@ -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",
|
||||
|
25
server/controllers/letsencrypt.js
Normal file
25
server/controllers/letsencrypt.js
Normal file
@ -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
|
@ -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) {
|
||||
|
125
server/core/letsencrypt.js
Normal file
125
server/core/letsencrypt.js
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
159
server/core/servers.js
Normal file
159
server/core/servers.js
Normal file
@ -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
|
||||
}
|
||||
}
|
142
server/master.js
142
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 = {}
|
||||
await WIKI.servers.startGraphQL()
|
||||
await WIKI.servers.startHTTP()
|
||||
|
||||
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 ]')
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
|
76
yarn.lock
76
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"
|
||||
|
Loading…
Reference in New Issue
Block a user