feat: let's encrypt
This commit is contained in:
@@ -29,7 +29,6 @@
|
|||||||
v-btn.px-5(value='rradial')
|
v-btn.px-5(value='rradial')
|
||||||
v-icon(left, :color='graphMode === `rradial` ? `primary` : `grey darken-3`') mdi-blur-radial
|
v-icon(left, :color='graphMode === `rradial` ? `primary` : `grey darken-3`') mdi-blur-radial
|
||||||
span.text-none Relational Radial
|
span.text-none Relational Radial
|
||||||
v-chip.ml-3(x-small) Beta
|
|
||||||
.admin-pages-visualize-svg.pa-10(ref='svgContainer')
|
.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!
|
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>
|
</template>
|
||||||
@@ -61,9 +60,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
goToPage (d) {
|
||||||
|
if (_.get(d, 'data.id', 0) > 0) {
|
||||||
|
this.$router.push(`${d.data.id}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
bilink (root) {
|
bilink (root) {
|
||||||
const map = new Map(root.leaves().map(d => [d.data.path, d]))
|
const map = new Map(root.descendants().map(d => [d.data.path, d]))
|
||||||
for (const d of root.leaves()) {
|
for (const d of root.descendants()) {
|
||||||
d.incoming = []
|
d.incoming = []
|
||||||
d.outgoing = []
|
d.outgoing = []
|
||||||
d.data.links.forEach(i => {
|
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) {
|
for (const o of d.outgoing) {
|
||||||
if (o[1]) {
|
if (o[1]) {
|
||||||
o[1].incoming.push(o)
|
o[1].incoming.push(o)
|
||||||
@@ -112,8 +116,11 @@ export default {
|
|||||||
children: result
|
children: result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Relational Radial
|
||||||
|
*/
|
||||||
drawRelations () {
|
drawRelations () {
|
||||||
const data = this.hierarchy(this.pages)
|
const data = this.hierarchy(this.pages, true)
|
||||||
|
|
||||||
const line = d3.lineRadial()
|
const line = d3.lineRadial()
|
||||||
.curve(d3.curveBundle.beta(0.85))
|
.curve(d3.curveBundle.beta(0.85))
|
||||||
@@ -124,16 +131,26 @@ export default {
|
|||||||
.size([2 * Math.PI, this.radius - 100])
|
.size([2 * Math.PI, this.radius - 100])
|
||||||
|
|
||||||
const root = tree(this.bilink(d3.hierarchy(data)
|
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')
|
const svg = d3.create('svg')
|
||||||
.attr('viewBox', [-this.width / 2, -this.width / 2, this.width, this.width])
|
.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')
|
svg.append('g')
|
||||||
.attr('font-family', 'sans-serif')
|
.attr('font-family', 'sans-serif')
|
||||||
.attr('font-size', 10)
|
.attr('font-size', 10)
|
||||||
.selectAll('g')
|
.selectAll('g')
|
||||||
.data(root.leaves())
|
.data(root.descendants())
|
||||||
.join('g')
|
.join('g')
|
||||||
.attr('transform', d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
|
.attr('transform', d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
|
||||||
.append('text')
|
.append('text')
|
||||||
@@ -142,23 +159,17 @@ export default {
|
|||||||
.attr('text-anchor', d => d.x < Math.PI ? 'start' : 'end')
|
.attr('text-anchor', d => d.x < Math.PI ? 'start' : 'end')
|
||||||
.attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null)
|
.attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null)
|
||||||
.attr('fill', this.$vuetify.theme.dark ? 'white' : '')
|
.attr('fill', this.$vuetify.theme.dark ? 'white' : '')
|
||||||
|
.attr('cursor', 'pointer')
|
||||||
.text(d => d.data.title)
|
.text(d => d.data.title)
|
||||||
.each(function(d) { d.text = this })
|
.each(function(d) { d.text = this })
|
||||||
.on('mouseover', overed)
|
.on('mouseover', overed)
|
||||||
.on('mouseout', outed)
|
.on('mouseout', outed)
|
||||||
|
.on('click', d => this.goToPage(d))
|
||||||
.call(text => text.append('title').text(d => `${d.data.path}
|
.call(text => text.append('title').text(d => `${d.data.path}
|
||||||
${d.outgoing.length} outgoing
|
${d.outgoing.length} outgoing
|
||||||
${d.incoming.length} incoming`))
|
${d.incoming.length} incoming`))
|
||||||
|
.clone(true).lower()
|
||||||
const link = svg.append('g')
|
.attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white')
|
||||||
.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 })
|
|
||||||
|
|
||||||
function overed(d) {
|
function overed(d) {
|
||||||
link.style('mix-blend-mode', null)
|
link.style('mix-blend-mode', null)
|
||||||
@@ -180,6 +191,9 @@ export default {
|
|||||||
|
|
||||||
this.$refs.svgContainer.appendChild(svg.node())
|
this.$refs.svgContainer.appendChild(svg.node())
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Hierarchical Tree
|
||||||
|
*/
|
||||||
drawTree () {
|
drawTree () {
|
||||||
const data = this.hierarchy(this.pages, true)
|
const data = this.hierarchy(this.pages, true)
|
||||||
|
|
||||||
@@ -232,12 +246,17 @@ export default {
|
|||||||
.attr('x', d => d.children ? -6 : 6)
|
.attr('x', d => d.children ? -6 : 6)
|
||||||
.attr('text-anchor', d => d.children ? 'end' : 'start')
|
.attr('text-anchor', d => d.children ? 'end' : 'start')
|
||||||
.attr('fill', this.$vuetify.theme.dark ? 'white' : '')
|
.attr('fill', this.$vuetify.theme.dark ? 'white' : '')
|
||||||
|
.attr('cursor', 'pointer')
|
||||||
.text(d => d.data.title)
|
.text(d => d.data.title)
|
||||||
|
.on('click', d => this.goToPage(d))
|
||||||
.clone(true).lower()
|
.clone(true).lower()
|
||||||
.attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white')
|
.attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white')
|
||||||
|
|
||||||
this.$refs.svgContainer.appendChild(svg.node())
|
this.$refs.svgContainer.appendChild(svg.node())
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Hierarchical Radial
|
||||||
|
*/
|
||||||
drawRadialTree () {
|
drawRadialTree () {
|
||||||
const data = this.hierarchy(this.pages)
|
const data = this.hierarchy(this.pages)
|
||||||
|
|
||||||
@@ -286,7 +305,9 @@ export default {
|
|||||||
.attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null)
|
.attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null)
|
||||||
/* eslint-enable no-mixed-operators */
|
/* eslint-enable no-mixed-operators */
|
||||||
.attr('fill', this.$vuetify.theme.dark ? 'white' : '')
|
.attr('fill', this.$vuetify.theme.dark ? 'white' : '')
|
||||||
|
.attr('cursor', 'pointer')
|
||||||
.text(d => d.data.title)
|
.text(d => d.data.title)
|
||||||
|
.on('click', d => this.goToPage(d))
|
||||||
.clone(true).lower()
|
.clone(true).lower()
|
||||||
.attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white')
|
.attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white')
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,12 @@ db:
|
|||||||
|
|
||||||
ssl:
|
ssl:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
port: 3443
|
||||||
|
|
||||||
|
# Provider to use, possible values: custom, letsencrypt
|
||||||
|
provider: custom
|
||||||
|
|
||||||
|
# ++++++ For custom only ++++++
|
||||||
# Certificate format, either 'pem' or 'pfx':
|
# Certificate format, either 'pem' or 'pfx':
|
||||||
format: pem
|
format: pem
|
||||||
# Using PEM format:
|
# Using PEM format:
|
||||||
@@ -73,9 +78,9 @@ ssl:
|
|||||||
# to 1024 bits (default: null):
|
# to 1024 bits (default: null):
|
||||||
dhparam: null
|
dhparam: null
|
||||||
|
|
||||||
# Listen on this HTTP port and redirect all requests to HTTPS.
|
# ++++++ For letsencrypt only ++++++
|
||||||
# Set to false to disable (default: 80):
|
domain: wiki.yourdomain.com
|
||||||
redirectNonSSLPort: 80
|
maintainerEmail: admin@example.com
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Database Pool Options
|
# Database Pool Options
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ LABEL maintainer="requarks.io"
|
|||||||
RUN apk add bash curl git openssh gnupg sqlite --no-cache && \
|
RUN apk add bash curl git openssh gnupg sqlite --no-cache && \
|
||||||
mkdir -p /wiki && \
|
mkdir -p /wiki && \
|
||||||
mkdir -p /logs && \
|
mkdir -p /logs && \
|
||||||
|
mkdir -p /wiki/data/content && \
|
||||||
chown -R node:node /wiki /logs
|
chown -R node:node /wiki /logs
|
||||||
|
|
||||||
WORKDIR /wiki
|
WORKDIR /wiki
|
||||||
@@ -44,8 +45,11 @@ COPY --chown=node:node ./LICENSE ./LICENSE
|
|||||||
|
|
||||||
USER node
|
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"]
|
CMD ["node", "server"]
|
||||||
|
|||||||
@@ -9,5 +9,10 @@ db:
|
|||||||
db: $(DB_NAME)
|
db: $(DB_NAME)
|
||||||
storage: $(DB_FILEPATH)
|
storage: $(DB_FILEPATH)
|
||||||
ssl: $(DB_SSL)
|
ssl: $(DB_SSL)
|
||||||
trustProxy: $(TRUST_PROXY)
|
ssl:
|
||||||
|
enabled: $(SSL_ACTIVE)
|
||||||
|
port: 3443
|
||||||
|
provider: letsencrypt
|
||||||
|
domain: $(LETSENCRYPT_DOMAIN)
|
||||||
|
maintainerEmail: $(LETSENCRYPT_EMAIL)
|
||||||
logLevel: info
|
logLevel: info
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "80:3000"
|
- "80:3000"
|
||||||
|
- "443:3443"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db-data:
|
db-data:
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ const init = {
|
|||||||
console.warn(chalk.yellow('--- Closing DB connections...'))
|
console.warn(chalk.yellow('--- Closing DB connections...'))
|
||||||
await global.WIKI.models.knex.destroy()
|
await global.WIKI.models.knex.destroy()
|
||||||
console.warn(chalk.yellow('--- Closing Server connections...'))
|
console.warn(chalk.yellow('--- Closing Server connections...'))
|
||||||
if (global.WIKI.server) {
|
if (global.WIKI.servers) {
|
||||||
await new Promise((resolve, reject) => global.WIKI.server.destroy(resolve))
|
await global.WIKI.servers.stopServers()
|
||||||
}
|
}
|
||||||
console.warn(chalk.yellow('--- Purging node modules cache...'))
|
console.warn(chalk.yellow('--- Purging node modules cache...'))
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@
|
|||||||
"@azure/storage-blob": "12.0.1",
|
"@azure/storage-blob": "12.0.1",
|
||||||
"@bugsnag/js": "6.5.0",
|
"@bugsnag/js": "6.5.0",
|
||||||
"@exlinc/keycloak-passport": "1.0.2",
|
"@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",
|
"algoliasearch": "3.35.1",
|
||||||
"apollo-fetch": "0.7.0",
|
"apollo-fetch": "0.7.0",
|
||||||
"apollo-server": "2.9.15",
|
"apollo-server": "2.9.15",
|
||||||
@@ -143,6 +147,7 @@
|
|||||||
"pg-query-stream": "2.1.2",
|
"pg-query-stream": "2.1.2",
|
||||||
"pg-tsquery": "8.1.0",
|
"pg-tsquery": "8.1.0",
|
||||||
"pug": "2.0.4",
|
"pug": "2.0.4",
|
||||||
|
"punycode": "2.1.1",
|
||||||
"qr-image": "3.2.0",
|
"qr-image": "3.2.0",
|
||||||
"raven": "2.6.4",
|
"raven": "2.6.4",
|
||||||
"remove-markdown": "0.3.0",
|
"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()
|
await this.initTelemetry()
|
||||||
WIKI.cache = require('./cache').init()
|
WIKI.cache = require('./cache').init()
|
||||||
WIKI.scheduler = require('./scheduler').init()
|
WIKI.scheduler = require('./scheduler').init()
|
||||||
|
WIKI.servers = require('./servers')
|
||||||
WIKI.sideloader = require('./sideloader').init()
|
WIKI.sideloader = require('./sideloader').init()
|
||||||
WIKI.events = new EventEmitter()
|
WIKI.events = new EventEmitter()
|
||||||
} catch (err) {
|
} 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 session = require('express-session')
|
||||||
const KnexSessionStore = require('connect-session-knex')(session)
|
const KnexSessionStore = require('connect-session-knex')(session)
|
||||||
const favicon = require('serve-favicon')
|
const favicon = require('serve-favicon')
|
||||||
const fs = require('fs-extra')
|
|
||||||
const http = require('http')
|
|
||||||
const https = require('https')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const { ApolloServer } = require('apollo-server-express')
|
|
||||||
|
|
||||||
/* global WIKI */
|
/* global WIKI */
|
||||||
|
|
||||||
@@ -62,6 +58,12 @@ module.exports = async () => {
|
|||||||
maxAge: '7d'
|
maxAge: '7d'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Let's Encrypt Challenge
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.use('/', ctrl.letsencrypt)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Passport Authentication
|
// Passport Authentication
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@@ -104,6 +106,7 @@ module.exports = async () => {
|
|||||||
// View accessible data
|
// View accessible data
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.locals.siteConfig = {}
|
||||||
app.locals.analyticsCode = {}
|
app.locals.analyticsCode = {}
|
||||||
app.locals.basedir = WIKI.ROOTPATH
|
app.locals.basedir = WIKI.ROOTPATH
|
||||||
app.locals.config = WIKI.config
|
app.locals.config = WIKI.config
|
||||||
@@ -124,23 +127,6 @@ module.exports = async () => {
|
|||||||
app.use(global.WP_DEV.hotMiddleware)
|
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
|
// 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 === true || WIKI.config.ssl.enabled === 'true' || WIKI.config.ssl.enabled === 1 || WIKI.config.ssl.enabled === '1') {
|
||||||
if (WIKI.config.ssl.enabled) {
|
await WIKI.servers.startHTTPS()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
Reference in New Issue
Block a user