master.js 8.43 KB
Newer Older
1 2 3 4 5 6
const autoload = require('auto-load')
const bodyParser = require('body-parser')
const compression = require('compression')
const cookieParser = require('cookie-parser')
const cors = require('cors')
const express = require('express')
Nick's avatar
Nick committed
7
const session = require('express-session')
8
const KnexSessionStore = require('connect-session-knex')(session)
9
const favicon = require('serve-favicon')
10
const fs = require('fs-extra')
11
const http = require('http')
Nick's avatar
Nick committed
12
const https = require('https')
13
const path = require('path')
Nick's avatar
Nick committed
14
const _ = require('lodash')
15
const { ApolloServer } = require('apollo-server-express')
16

17
/* global WIKI */
NGPixel's avatar
NGPixel committed
18

NGPixel's avatar
NGPixel committed
19
module.exports = async () => {
20
  // ----------------------------------------
21
  // Load core modules
22 23
  // ----------------------------------------

24 25
  WIKI.auth = require('./core/auth').init()
  WIKI.lang = require('./core/localization').init()
26
  WIKI.mail = require('./core/mail').init()
27
  WIKI.system = require('./core/system').init()
28 29

  // ----------------------------------------
30
  // Load middlewares
31 32
  // ----------------------------------------

33 34
  var mw = autoload(path.join(WIKI.SERVERPATH, '/middlewares'))
  var ctrl = autoload(path.join(WIKI.SERVERPATH, '/controllers'))
35 36 37 38 39 40

  // ----------------------------------------
  // Define Express App
  // ----------------------------------------

  const app = express()
41
  WIKI.app = app
42 43 44 45 46 47 48
  app.use(compression())

  // ----------------------------------------
  // Security
  // ----------------------------------------

  app.use(mw.security)
49 50
  app.use(cors(WIKI.config.cors))
  app.options('*', cors(WIKI.config.cors))
51 52 53
  if (WIKI.config.trustProxy) {
    app.enable('trust proxy')
  }
54 55 56 57 58

  // ----------------------------------------
  // Public Assets
  // ----------------------------------------

59 60
  app.use(favicon(path.join(WIKI.ROOTPATH, 'assets', 'favicon.ico')))
  app.use(express.static(path.join(WIKI.ROOTPATH, 'assets'), {
61 62 63 64 65 66 67 68 69
    index: false,
    maxAge: '7d'
  }))

  // ----------------------------------------
  // Passport Authentication
  // ----------------------------------------

  app.use(cookieParser())
Nick's avatar
Nick committed
70 71 72
  app.use(session({
    secret: WIKI.config.sessionSecret,
    resave: false,
73 74 75 76
    saveUninitialized: false,
    store: new KnexSessionStore({
      knex: WIKI.models.knex
    })
Nick's avatar
Nick committed
77
  }))
78
  app.use(WIKI.auth.passport.initialize())
79
  app.use(WIKI.auth.authenticate)
80 81 82 83 84 85 86 87 88 89

  // ----------------------------------------
  // SEO
  // ----------------------------------------

  app.use(mw.seo)

  // ----------------------------------------
  // View Engine Setup
  // ----------------------------------------
NGPixel's avatar
NGPixel committed
90

91
  app.set('views', path.join(WIKI.SERVERPATH, 'views'))
92
  app.set('view engine', 'pug')
NGPixel's avatar
NGPixel committed
93

94 95
  app.use(bodyParser.json({ limit: '1mb' }))
  app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
NGPixel's avatar
NGPixel committed
96

97 98 99 100
  // ----------------------------------------
  // Localization
  // ----------------------------------------

101
  WIKI.lang.attachMiddleware(app)
102

103 104 105
  // ----------------------------------------
  // View accessible data
  // ----------------------------------------
NGPixel's avatar
NGPixel committed
106

107
  app.locals.analyticsCode = {}
108 109
  app.locals.basedir = WIKI.ROOTPATH
  app.locals.config = WIKI.config
110 111 112 113 114 115
  app.locals.pageMeta = {
    title: '',
    description: WIKI.config.description,
    image: '',
    url: '/'
  }
NGPixel's avatar
NGPixel committed
116

NGPixel's avatar
NGPixel committed
117 118 119 120 121
  // ----------------------------------------
  // HMR (Dev Mode Only)
  // ----------------------------------------

  if (global.DEV) {
122 123
    app.use(global.WP_DEV.devMiddleware)
    app.use(global.WP_DEV.hotMiddleware)
NGPixel's avatar
NGPixel committed
124 125
  }

126
  // ----------------------------------------
127 128 129 130 131 132
  // Apollo Server (GraphQL)
  // ----------------------------------------

  const graphqlSchema = require('./graph')
  const apolloServer = new ApolloServer({
    ...graphqlSchema,
133 134 135 136 137 138 139
    context: ({ req, res }) => ({ req, res }),
    subscriptions: {
      onConnect: (connectionParams, webSocket) => {

      },
      path: '/graphql-subscriptions'
    }
140 141 142 143 144
  })
  apolloServer.applyMiddleware({ app })

  // ----------------------------------------
  // Routing
145
  // ----------------------------------------
NGPixel's avatar
NGPixel committed
146

147 148 149 150 151 152 153 154 155 156
  app.use(async (req, res, next) => {
    res.locals.siteConfig = {
      title: WIKI.config.title,
      theme: WIKI.config.theming.theme,
      darkMode: WIKI.config.theming.darkMode,
      lang: WIKI.config.lang.code,
      rtl: WIKI.config.lang.rtl,
      company: WIKI.config.company
    }
    res.locals.langs = await WIKI.models.locales.getNavLocales({ cache: true })
Nick's avatar
Nick committed
157
    res.locals.analyticsCode = await WIKI.models.analytics.getCode({ cache: true })
158 159 160
    next()
  })

161
  app.use('/', ctrl.auth)
162
  app.use('/', ctrl.upload)
163
  app.use('/', ctrl.common)
NGPixel's avatar
NGPixel committed
164

165 166 167
  // ----------------------------------------
  // Error handling
  // ----------------------------------------
NGPixel's avatar
NGPixel committed
168

169
  app.use((req, res, next) => {
170 171 172 173
    var err = new Error('Not Found')
    err.status = 404
    next(err)
  })
NGPixel's avatar
NGPixel committed
174

175
  app.use((err, req, res, next) => {
176
    res.status(err.status || 500)
177
    _.set(res.locals, 'pageMeta.title', 'Error')
178 179
    res.render('error', {
      message: err.message,
180
      error: WIKI.IS_DEBUG ? err : {}
181
    })
NGPixel's avatar
NGPixel committed
182 183
  })

184
  // ----------------------------------------
185
  // HTTP/S server
186 187
  // ----------------------------------------

NGPixel's avatar
NGPixel committed
188 189
  let srvConnections = {}

190
  app.set('port', WIKI.config.port)
Nick's avatar
Nick committed
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
  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)
213 214 215 216 217 218 219 220

    // 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()
      })
    }
Nick's avatar
Nick committed
221 222 223 224
  } else {
    WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`)
    WIKI.server = http.createServer(app)
  }
225
  apolloServer.installSubscriptionHandlers(WIKI.server)
226

227
  WIKI.server.listen(WIKI.config.port, WIKI.config.bindIP)
228
  WIKI.server.on('error', (error) => {
229
    if (error.syscall !== 'listen') {
NGPixel's avatar
NGPixel committed
230
      throw error
231 232 233 234 235
    }

    // handle specific listen errors with friendly messages
    switch (error.code) {
      case 'EACCES':
236
        WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!')
237 238
        return process.exit(1)
      case 'EADDRINUSE':
239
        WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!')
240 241 242 243 244
        return process.exit(1)
      default:
        throw error
    }
  })
NGPixel's avatar
NGPixel committed
245

246
  WIKI.server.on('connection', conn => {
NGPixel's avatar
NGPixel committed
247 248 249 250 251 252 253
    let key = `${conn.remoteAddress}:${conn.remotePort}`
    srvConnections[key] = conn
    conn.on('close', function() {
      delete srvConnections[key]
    })
  })

254
  WIKI.server.on('listening', () => {
Nick's avatar
Nick committed
255 256
    if (WIKI.config.ssl.enabled) {
      WIKI.logger.info('HTTPS Server: [ RUNNING ]')
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

      // 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 ]')
        })
      }
Nick's avatar
Nick committed
283 284 285
    } else {
      WIKI.logger.info('HTTP Server: [ RUNNING ]')
    }
286
  })
NGPixel's avatar
NGPixel committed
287

288 289
  WIKI.server.destroy = (cb) => {
    WIKI.server.close(cb)
NGPixel's avatar
NGPixel committed
290 291 292
    for (let key in srvConnections) {
      srvConnections[key].destroy()
    }
293 294 295 296

    if (WIKI.config.ssl.enabled && WIKI.config.ssl.redirectNonSSLPort) {
      WIKI.serverAlt.close(cb)
    }
NGPixel's avatar
NGPixel committed
297 298
  }

299
  return true
NGPixel's avatar
NGPixel committed
300
}