2019-05-29 05:21:06 -04:00
|
|
|
const Path = require('path')
|
|
|
|
const express = require('express')
|
|
|
|
const Settings = require('settings-sharelatex')
|
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const metrics = require('metrics-sharelatex')
|
|
|
|
const expressLocals = require('./ExpressLocals')
|
2019-10-07 08:13:41 -04:00
|
|
|
const Validation = require('./Validation')
|
2019-05-29 05:21:06 -04:00
|
|
|
const Router = require('../router')
|
|
|
|
const helmet = require('helmet')
|
|
|
|
const UserSessionsRedis = require('../Features/User/UserSessionsRedis')
|
|
|
|
const Csrf = require('./Csrf')
|
|
|
|
|
|
|
|
const sessionsRedisClient = UserSessionsRedis.client()
|
|
|
|
|
2020-01-21 04:33:58 -05:00
|
|
|
const SessionAutostartMiddleware = require('./SessionAutostartMiddleware')
|
2019-10-18 08:59:00 -04:00
|
|
|
const SessionStoreManager = require('./SessionStoreManager')
|
2019-05-29 05:21:06 -04:00
|
|
|
const session = require('express-session')
|
|
|
|
const RedisStore = require('connect-redis')(session)
|
2020-03-09 09:36:13 -04:00
|
|
|
const bodyParser = require('./BodyParserWrapper')
|
2019-05-29 05:21:06 -04:00
|
|
|
const methodOverride = require('method-override')
|
|
|
|
const cookieParser = require('cookie-parser')
|
|
|
|
const bearerToken = require('express-bearer-token')
|
|
|
|
|
|
|
|
const passport = require('passport')
|
|
|
|
const LocalStrategy = require('passport-local').Strategy
|
|
|
|
|
|
|
|
const oneDayInMilliseconds = 86400000
|
|
|
|
const ReferalConnect = require('../Features/Referal/ReferalConnect')
|
|
|
|
const RedirectManager = require('./RedirectManager')
|
|
|
|
const ProxyManager = require('./ProxyManager')
|
2020-08-18 06:09:28 -04:00
|
|
|
const translations = require('./Translations')
|
2019-05-29 05:21:06 -04:00
|
|
|
const Modules = require('./Modules')
|
2019-11-26 05:02:06 -05:00
|
|
|
const Views = require('./Views')
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
const ErrorController = require('../Features/Errors/ErrorController')
|
2020-07-27 06:46:27 -04:00
|
|
|
const HttpErrorHandler = require('../Features/Errors/HttpErrorHandler')
|
2019-05-29 05:21:06 -04:00
|
|
|
const UserSessionsManager = require('../Features/User/UserSessionsManager')
|
|
|
|
const AuthenticationController = require('../Features/Authentication/AuthenticationController')
|
|
|
|
|
2019-08-06 08:20:52 -04:00
|
|
|
const STATIC_CACHE_AGE = Settings.cacheStaticAssets
|
|
|
|
? oneDayInMilliseconds * 365
|
|
|
|
: 0
|
|
|
|
|
|
|
|
// Init the session store
|
|
|
|
const sessionStore = new RedisStore({ client: sessionsRedisClient })
|
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
const app = express()
|
|
|
|
|
|
|
|
const webRouter = express.Router()
|
|
|
|
const privateApiRouter = express.Router()
|
|
|
|
const publicApiRouter = express.Router()
|
|
|
|
|
|
|
|
if (Settings.behindProxy) {
|
2019-08-06 08:20:52 -04:00
|
|
|
app.set('trust proxy', Settings.trustedProxyIps || true)
|
|
|
|
/**
|
|
|
|
* Handle the X-Original-Forwarded-For header.
|
|
|
|
*
|
|
|
|
* The nginx ingress sends us the contents of X-Forwarded-For it received in
|
|
|
|
* X-Original-Forwarded-For. Express expects all proxy IPs to be in a comma
|
|
|
|
* separated list in X-Forwarded-For.
|
|
|
|
*/
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
if (
|
|
|
|
req.headers['x-original-forwarded-for'] &&
|
|
|
|
req.headers['x-forwarded-for']
|
|
|
|
) {
|
|
|
|
req.headers['x-forwarded-for'] =
|
|
|
|
req.headers['x-original-forwarded-for'] +
|
|
|
|
', ' +
|
|
|
|
req.headers['x-forwarded-for']
|
|
|
|
}
|
|
|
|
next()
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2020-07-07 08:55:42 -04:00
|
|
|
if (Settings.exposeHostname) {
|
|
|
|
const HOSTNAME = require('os').hostname()
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
res.setHeader('X-Served-By', HOSTNAME)
|
|
|
|
next()
|
|
|
|
})
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
webRouter.use(
|
2019-08-06 08:20:52 -04:00
|
|
|
express.static(Path.join(__dirname, '/../../../public'), {
|
|
|
|
maxAge: STATIC_CACHE_AGE
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
)
|
2019-08-06 08:20:52 -04:00
|
|
|
app.set('views', Path.join(__dirname, '/../../views'))
|
2019-05-29 05:21:06 -04:00
|
|
|
app.set('view engine', 'pug')
|
|
|
|
Modules.loadViewIncludes(app)
|
|
|
|
|
|
|
|
app.use(bodyParser.urlencoded({ extended: true, limit: '2mb' }))
|
2020-03-18 10:26:53 -04:00
|
|
|
app.use(bodyParser.json({ limit: Settings.max_json_request_size }))
|
2019-05-29 05:21:06 -04:00
|
|
|
app.use(methodOverride())
|
|
|
|
app.use(bearerToken())
|
|
|
|
|
|
|
|
app.use(metrics.http.monitor(logger))
|
|
|
|
RedirectManager.apply(webRouter)
|
|
|
|
ProxyManager.apply(publicApiRouter)
|
|
|
|
|
|
|
|
webRouter.use(cookieParser(Settings.security.sessionSecret))
|
2020-01-21 04:33:58 -05:00
|
|
|
SessionAutostartMiddleware.applyInitialMiddleware(webRouter)
|
2019-05-29 05:21:06 -04:00
|
|
|
webRouter.use(
|
|
|
|
session({
|
|
|
|
resave: false,
|
|
|
|
saveUninitialized: false,
|
|
|
|
secret: Settings.security.sessionSecret,
|
|
|
|
proxy: Settings.behindProxy,
|
|
|
|
cookie: {
|
|
|
|
domain: Settings.cookieDomain,
|
2019-09-12 04:04:06 -04:00
|
|
|
maxAge: Settings.cookieSessionLength, // in milliseconds, see https://github.com/expressjs/session#cookiemaxage
|
2020-01-21 05:57:18 -05:00
|
|
|
secure: Settings.secureCookie,
|
|
|
|
sameSite: Settings.sameSiteCookie
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
store: sessionStore,
|
|
|
|
key: Settings.cookieName,
|
|
|
|
rolling: true
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
2019-10-18 08:59:00 -04:00
|
|
|
// patch the session store to generate a validation token for every new session
|
|
|
|
SessionStoreManager.enableValidationToken(sessionStore)
|
2019-10-22 03:29:19 -04:00
|
|
|
// use middleware to reject all requests with invalid tokens
|
|
|
|
webRouter.use(SessionStoreManager.validationMiddleware)
|
2019-10-18 08:59:00 -04:00
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
// passport
|
|
|
|
webRouter.use(passport.initialize())
|
|
|
|
webRouter.use(passport.session())
|
|
|
|
|
|
|
|
passport.use(
|
|
|
|
new LocalStrategy(
|
|
|
|
{
|
|
|
|
passReqToCallback: true,
|
|
|
|
usernameField: 'email',
|
|
|
|
passwordField: 'password'
|
|
|
|
},
|
|
|
|
AuthenticationController.doPassportLogin
|
|
|
|
)
|
|
|
|
)
|
|
|
|
passport.serializeUser(AuthenticationController.serializeUser)
|
|
|
|
passport.deserializeUser(AuthenticationController.deserializeUser)
|
|
|
|
|
|
|
|
Modules.hooks.fire('passportSetup', passport, function(err) {
|
|
|
|
if (err != null) {
|
2019-08-06 08:20:52 -04:00
|
|
|
logger.err({ err }, 'error setting up passport in modules')
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
Modules.applyNonCsrfRouter(webRouter, privateApiRouter, publicApiRouter)
|
|
|
|
|
|
|
|
webRouter.csrf = new Csrf()
|
|
|
|
webRouter.use(webRouter.csrf.middleware)
|
|
|
|
webRouter.use(translations.expressMiddlewear)
|
|
|
|
webRouter.use(translations.setLangBasedOnDomainMiddlewear)
|
|
|
|
|
|
|
|
// Measure expiry from last request, not last login
|
|
|
|
webRouter.use(function(req, res, next) {
|
2020-01-21 04:33:58 -05:00
|
|
|
if (!req.session.noSessionCallback) {
|
|
|
|
req.session.touch()
|
|
|
|
if (AuthenticationController.isUserLoggedIn(req)) {
|
|
|
|
UserSessionsManager.touch(
|
|
|
|
AuthenticationController.getSessionUser(req),
|
|
|
|
err => {
|
|
|
|
if (err) {
|
|
|
|
logger.err({ err }, 'error extending user session')
|
|
|
|
}
|
2019-08-07 04:45:39 -04:00
|
|
|
}
|
2020-01-21 04:33:58 -05:00
|
|
|
)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-08-06 08:20:52 -04:00
|
|
|
next()
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
webRouter.use(ReferalConnect.use)
|
2019-10-29 11:41:20 -04:00
|
|
|
expressLocals(webRouter, privateApiRouter, publicApiRouter)
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2020-01-21 04:33:58 -05:00
|
|
|
webRouter.use(SessionAutostartMiddleware.invokeCallbackMiddleware)
|
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
webRouter.use(function(req, res, next) {
|
|
|
|
if (Settings.siteIsOpen) {
|
2019-08-06 08:20:52 -04:00
|
|
|
next()
|
2020-02-20 11:08:08 -05:00
|
|
|
} else if (
|
|
|
|
AuthenticationController.getSessionUser(req) &&
|
|
|
|
AuthenticationController.getSessionUser(req).isAdmin
|
|
|
|
) {
|
|
|
|
next()
|
2019-05-29 05:21:06 -04:00
|
|
|
} else {
|
2020-07-27 06:46:27 -04:00
|
|
|
HttpErrorHandler.maintenance(req, res)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
webRouter.use(function(req, res, next) {
|
|
|
|
if (Settings.editorIsOpen) {
|
2019-08-06 08:20:52 -04:00
|
|
|
next()
|
2019-05-29 05:21:06 -04:00
|
|
|
} else if (req.url.indexOf('/admin') === 0) {
|
2019-08-06 08:20:52 -04:00
|
|
|
next()
|
2019-05-29 05:21:06 -04:00
|
|
|
} else {
|
2020-07-27 06:46:27 -04:00
|
|
|
HttpErrorHandler.maintenance(req, res)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// add security headers using Helmet
|
2020-06-26 05:49:52 -04:00
|
|
|
const noCacheMiddleware = require('nocache')()
|
2019-05-29 05:21:06 -04:00
|
|
|
webRouter.use(function(req, res, next) {
|
|
|
|
const isLoggedIn = AuthenticationController.isUserLoggedIn(req)
|
|
|
|
const isProjectPage = !!req.path.match('^/project/[a-f0-9]{24}$')
|
2020-06-26 05:49:52 -04:00
|
|
|
if (isLoggedIn || isProjectPage) {
|
|
|
|
noCacheMiddleware(req, res, next)
|
|
|
|
} else {
|
|
|
|
next()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
webRouter.use(
|
2019-08-06 08:20:52 -04:00
|
|
|
helmet({
|
2019-05-29 05:21:06 -04:00
|
|
|
// note that more headers are added by default
|
|
|
|
dnsPrefetchControl: false,
|
|
|
|
referrerPolicy: { policy: 'origin-when-cross-origin' },
|
2019-09-18 06:29:43 -04:00
|
|
|
hsts: false
|
2020-06-26 05:49:52 -04:00
|
|
|
})
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
logger.info('creating HTTP server'.yellow)
|
|
|
|
const server = require('http').createServer(app)
|
|
|
|
|
|
|
|
// provide settings for separate web and api processes
|
|
|
|
// if enableApiRouter and enableWebRouter are not defined they default
|
|
|
|
// to true.
|
|
|
|
const notDefined = x => x == null
|
|
|
|
const enableApiRouter =
|
|
|
|
Settings.web != null ? Settings.web.enableApiRouter : undefined
|
|
|
|
if (enableApiRouter || notDefined(enableApiRouter)) {
|
|
|
|
logger.info('providing api router')
|
|
|
|
app.use(privateApiRouter)
|
2019-10-07 08:13:41 -04:00
|
|
|
app.use(Validation.errorMiddleware)
|
2019-05-29 05:21:06 -04:00
|
|
|
app.use(ErrorController.handleApiError)
|
|
|
|
}
|
|
|
|
|
|
|
|
const enableWebRouter =
|
|
|
|
Settings.web != null ? Settings.web.enableWebRouter : undefined
|
|
|
|
if (enableWebRouter || notDefined(enableWebRouter)) {
|
|
|
|
logger.info('providing web router')
|
2019-10-07 08:13:41 -04:00
|
|
|
|
2020-02-26 05:51:45 -05:00
|
|
|
if (app.get('env') === 'production') {
|
|
|
|
logger.info('precompiling views for web in production environment')
|
|
|
|
Views.precompileViews(app)
|
|
|
|
}
|
2020-03-30 14:12:56 -04:00
|
|
|
if (app.get('env') === 'test') {
|
|
|
|
logger.info('enabling view cache for acceptance tests')
|
|
|
|
app.enable('view cache')
|
|
|
|
}
|
2020-02-26 05:51:45 -05:00
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
app.use(publicApiRouter) // public API goes with web router for public access
|
2019-10-07 08:13:41 -04:00
|
|
|
app.use(Validation.errorMiddleware)
|
2019-05-29 05:21:06 -04:00
|
|
|
app.use(ErrorController.handleApiError)
|
2019-10-07 08:13:41 -04:00
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
app.use(webRouter)
|
2019-10-07 08:13:41 -04:00
|
|
|
app.use(Validation.errorMiddleware)
|
2019-05-29 05:21:06 -04:00
|
|
|
app.use(ErrorController.handleError)
|
|
|
|
}
|
|
|
|
|
|
|
|
metrics.injectMetricsRoute(webRouter)
|
2020-04-08 09:43:30 -04:00
|
|
|
metrics.injectMetricsRoute(privateApiRouter)
|
2020-01-21 04:33:58 -05:00
|
|
|
|
2019-08-06 08:20:52 -04:00
|
|
|
Router.initialize(webRouter, privateApiRouter, publicApiRouter)
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
app,
|
|
|
|
server
|
|
|
|
}
|