Skip to content

Instantly share code, notes, and snippets.

@ndelitski
Created September 8, 2018 08:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ndelitski/bd6d349859d952213683cef57f80a9be to your computer and use it in GitHub Desktop.
Save ndelitski/bd6d349859d952213683cef57f80a9be to your computer and use it in GitHub Desktop.
/* eslint-disable global-require */
import express from 'express'
import compression from 'compression'
import favicon from 'serve-favicon'
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import helmet from 'helmet'
import csrf from 'csurf'
import apiProxyMiddleware from './middlewares/apiProxyMiddleware'
import authenticate from './middlewares/authenticate'
import generateNonceForInlineScripts from './middlewares/nonce'
import cspViolationReportHandler from './middlewares/cspViolationReport'
import errorHandler from './middlewares/errorHandler'
import ensureSecureConnection from './middlewares/ensureSecureConnection'
import createHealthcheck from './middlewares/healthcheck'
const SIX_MONTH_IN_SECONDS = 6 * 30 * 24 * 60 * 60
const isGoogleUserAgent = req =>
(req.headers['user-agent'] || '').toLowerCase().indexOf('googlehc') !== -1
export default class Server {
constructor(options) {
const { host, port } = options
this.options = options
this._port = port
this._host = host
this._app = express()
}
setupCommon() {
const { apiEndpointPath, backendUrl, faviconPath, cspRules } = this.options
const app = this._app
app.use(compression())
// Security headers
// Remove the X-Powered-By header
app.use(helmet.hidePoweredBy())
// Sets "X-Frame-Options: DENY"
app.use(helmet.frameguard({ action: 'deny' }))
// Sets "X-DNS-Prefetch-Control: off"
app.use(helmet.dnsPrefetchControl({ allow: false }))
// Sets "Strict-Transport-Security: max-age=15780000".
app.use(
helmet.hsts({ maxAge: SIX_MONTH_IN_SECONDS, includeSubDomains: false })
)
// Sets "X-Download-Options: noopen".
app.use(helmet.ieNoOpen())
// Sets "X-Content-Type-Options: nosniff"
app.use(helmet.noSniff())
// Sets "X-XSS-Protection: 1; mode=block"
app.use(helmet.xssFilter())
// CSP Rules
// docs https://helmetjs.github.io/docs/csp/
if (cspRules) {
app.use(generateNonceForInlineScripts)
const REPORT_URL = '/report-violation'
app.use(
helmet.contentSecurityPolicy({
...cspRules,
browserSniff: false,
reportUri: REPORT_URL,
})
)
app.post(
REPORT_URL,
bodyParser.json({
type: ['json', 'application/csp-report'],
}),
cspViolationReportHandler
)
}
// TODO TEMP way to handle favicon - use Helmet and manifest.json
app.use(favicon(faviconPath))
// enable csrf protection to all routes except ['GET', 'HEAD', 'OPTIONS']
app.use(cookieParser())
app.use(csrf({ cookie: true }))
app.use(apiEndpointPath, apiProxyMiddleware('/api', backendUrl))
}
setupAuthentication() {
const {
authCookieName,
backendUrl,
graphqlEndpointPath,
findUser,
} = this.options
this._app.use(
authenticate({
authCookieName,
backendUrl,
graphqlEndpointPath,
findUser,
})
)
}
setupDev({ webpackClientConfig, webpackServerConfig }) {
this.setupCommon()
const { initRoutes } = this.options
const config = [webpackClientConfig, webpackServerConfig]
const compiler = require('webpack')(config)
this._app.use(
require('webpack-dev-middleware')(compiler, {
noInfo: true,
stats: false,
serverSideRender: true,
})
)
// we do not want authenticate asset requests, so place auth middleware after
this.setupAuthentication()
initRoutes(this._app, this.options)
this._app.use(require('webpack-hot-server-middleware')(compiler))
}
setupProd({ stats, assetsDir, ssr, healthcheck, ensureHttps }) {
this._app.use(createHealthcheck(healthcheck || isGoogleUserAgent))
if (ensureHttps) {
this._app.use(ensureSecureConnection)
}
this.setupCommon()
this._app.use(
'/assets',
express.static(assetsDir, {
maxage: '30d',
etag: false,
setHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*')
},
})
)
// we do not want authenticate asset requests, so place auth middleware after
this.setupAuthentication()
if (typeof this.options.initRoutes === 'function') {
this.options.initRoutes(this._app, this.options)
}
this._app.use(ssr({ clientStats: stats }))
}
listen() {
this._app.use(errorHandler)
this._app.listen(this._port, this._host, () => {
// eslint-disable-next-line no-console
console.log(`server started http://${this._host}:${this._port}`)
if (typeof process.send === 'function') {
process.send({
started: true,
url: `http://${this._host}:${this._port}`,
})
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment