Server.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. const Path = require('path')
  2. const express = require('express')
  3. const Settings = require('@overleaf/settings')
  4. const logger = require('logger-sharelatex')
  5. const metrics = require('@overleaf/metrics')
  6. const expressLocals = require('./ExpressLocals')
  7. const Validation = require('./Validation')
  8. const csp = require('./CSP')
  9. const Router = require('../router')
  10. const helmet = require('helmet')
  11. const UserSessionsRedis = require('../Features/User/UserSessionsRedis')
  12. const Csrf = require('./Csrf')
  13. const sessionsRedisClient = UserSessionsRedis.client()
  14. const SessionAutostartMiddleware = require('./SessionAutostartMiddleware')
  15. const SessionStoreManager = require('./SessionStoreManager')
  16. const AnalyticsManager = require('../Features/Analytics/AnalyticsManager')
  17. const session = require('express-session')
  18. const RedisStore = require('connect-redis')(session)
  19. const bodyParser = require('./BodyParserWrapper')
  20. const methodOverride = require('method-override')
  21. const cookieParser = require('cookie-parser')
  22. const bearerToken = require('express-bearer-token')
  23. const passport = require('passport')
  24. const LocalStrategy = require('passport-local').Strategy
  25. const oneDayInMilliseconds = 86400000
  26. const ReferalConnect = require('../Features/Referal/ReferalConnect')
  27. const RedirectManager = require('./RedirectManager')
  28. const ProxyManager = require('./ProxyManager')
  29. const translations = require('./Translations')
  30. const Modules = require('./Modules')
  31. const Views = require('./Views')
  32. const Features = require('./Features')
  33. const ErrorController = require('../Features/Errors/ErrorController')
  34. const HttpErrorHandler = require('../Features/Errors/HttpErrorHandler')
  35. const UserSessionsManager = require('../Features/User/UserSessionsManager')
  36. const AuthenticationController = require('../Features/Authentication/AuthenticationController')
  37. const SessionManager = require('../Features/Authentication/SessionManager')
  38. const STATIC_CACHE_AGE = Settings.cacheStaticAssets
  39. ? oneDayInMilliseconds * 365
  40. : 0
  41. // Init the session store
  42. const sessionStore = new RedisStore({ client: sessionsRedisClient })
  43. const app = express()
  44. const webRouter = express.Router()
  45. const privateApiRouter = express.Router()
  46. const publicApiRouter = express.Router()
  47. if (Settings.behindProxy) {
  48. app.set('trust proxy', Settings.trustedProxyIps || true)
  49. /**
  50. * Handle the X-Original-Forwarded-For header.
  51. *
  52. * The nginx ingress sends us the contents of X-Forwarded-For it received in
  53. * X-Original-Forwarded-For. Express expects all proxy IPs to be in a comma
  54. * separated list in X-Forwarded-For.
  55. */
  56. app.use((req, res, next) => {
  57. if (
  58. req.headers['x-original-forwarded-for'] &&
  59. req.headers['x-forwarded-for']
  60. ) {
  61. req.headers['x-forwarded-for'] =
  62. req.headers['x-original-forwarded-for'] +
  63. ', ' +
  64. req.headers['x-forwarded-for']
  65. }
  66. next()
  67. })
  68. }
  69. if (Settings.exposeHostname) {
  70. const HOSTNAME = require('os').hostname()
  71. app.use((req, res, next) => {
  72. res.setHeader('X-Served-By', HOSTNAME)
  73. next()
  74. })
  75. }
  76. webRouter.get(
  77. '/serviceWorker.js',
  78. express.static(Path.join(__dirname, '/../../../public'), {
  79. maxAge: oneDayInMilliseconds,
  80. })
  81. )
  82. webRouter.use(
  83. express.static(Path.join(__dirname, '/../../../public'), {
  84. maxAge: STATIC_CACHE_AGE,
  85. })
  86. )
  87. app.set('views', Path.join(__dirname, '/../../views'))
  88. app.set('view engine', 'pug')
  89. Modules.loadViewIncludes(app)
  90. app.use(bodyParser.urlencoded({ extended: true, limit: '2mb' }))
  91. app.use(bodyParser.json({ limit: Settings.max_json_request_size }))
  92. app.use(methodOverride())
  93. app.use(bearerToken())
  94. app.use(metrics.http.monitor(logger))
  95. RedirectManager.apply(webRouter)
  96. ProxyManager.apply(publicApiRouter)
  97. webRouter.use(cookieParser(Settings.security.sessionSecret))
  98. SessionAutostartMiddleware.applyInitialMiddleware(webRouter)
  99. webRouter.use(
  100. session({
  101. resave: false,
  102. saveUninitialized: false,
  103. secret: Settings.security.sessionSecret,
  104. proxy: Settings.behindProxy,
  105. cookie: {
  106. domain: Settings.cookieDomain,
  107. maxAge: Settings.cookieSessionLength, // in milliseconds, see https://github.com/expressjs/session#cookiemaxage
  108. secure: Settings.secureCookie,
  109. sameSite: Settings.sameSiteCookie,
  110. },
  111. store: sessionStore,
  112. key: Settings.cookieName,
  113. rolling: true,
  114. })
  115. )
  116. if (Features.hasFeature('saas')) {
  117. webRouter.use(AnalyticsManager.analyticsIdMiddleware)
  118. }
  119. // patch the session store to generate a validation token for every new session
  120. SessionStoreManager.enableValidationToken(sessionStore)
  121. // use middleware to reject all requests with invalid tokens
  122. webRouter.use(SessionStoreManager.validationMiddleware)
  123. // passport
  124. webRouter.use(passport.initialize())
  125. webRouter.use(passport.session())
  126. passport.use(
  127. new LocalStrategy(
  128. {
  129. passReqToCallback: true,
  130. usernameField: 'email',
  131. passwordField: 'password',
  132. },
  133. AuthenticationController.doPassportLogin
  134. )
  135. )
  136. passport.serializeUser(AuthenticationController.serializeUser)
  137. passport.deserializeUser(AuthenticationController.deserializeUser)
  138. Modules.hooks.fire('passportSetup', passport, function (err) {
  139. if (err != null) {
  140. logger.err({ err }, 'error setting up passport in modules')
  141. }
  142. })
  143. Modules.applyNonCsrfRouter(webRouter, privateApiRouter, publicApiRouter)
  144. webRouter.csrf = new Csrf()
  145. webRouter.use(webRouter.csrf.middleware)
  146. webRouter.use(translations.i18nMiddleware)
  147. webRouter.use(translations.setLangBasedOnDomainMiddleware)
  148. // Measure expiry from last request, not last login
  149. webRouter.use(function (req, res, next) {
  150. if (!req.session.noSessionCallback) {
  151. req.session.touch()
  152. if (SessionManager.isUserLoggedIn(req.session)) {
  153. UserSessionsManager.touch(
  154. SessionManager.getSessionUser(req.session),
  155. err => {
  156. if (err) {
  157. logger.err({ err }, 'error extending user session')
  158. }
  159. }
  160. )
  161. }
  162. }
  163. next()
  164. })
  165. webRouter.use(ReferalConnect.use)
  166. expressLocals(webRouter, privateApiRouter, publicApiRouter)
  167. webRouter.use(SessionAutostartMiddleware.invokeCallbackMiddleware)
  168. webRouter.use(function (req, res, next) {
  169. if (Settings.siteIsOpen) {
  170. next()
  171. } else if (
  172. SessionManager.getSessionUser(req.session) &&
  173. SessionManager.getSessionUser(req.session).isAdmin
  174. ) {
  175. next()
  176. } else {
  177. HttpErrorHandler.maintenance(req, res)
  178. }
  179. })
  180. webRouter.use(function (req, res, next) {
  181. if (Settings.editorIsOpen) {
  182. next()
  183. } else if (req.url.indexOf('/admin') === 0) {
  184. next()
  185. } else {
  186. HttpErrorHandler.maintenance(req, res)
  187. }
  188. })
  189. webRouter.use(AuthenticationController.validateAdmin)
  190. // add security headers using Helmet
  191. const noCacheMiddleware = require('nocache')()
  192. webRouter.use(function (req, res, next) {
  193. const isLoggedIn = SessionManager.isUserLoggedIn(req.session)
  194. const isProjectPage = !!req.path.match('^/project/[a-f0-9]{24}$')
  195. if (isLoggedIn || isProjectPage) {
  196. noCacheMiddleware(req, res, next)
  197. } else {
  198. next()
  199. }
  200. })
  201. webRouter.use(
  202. helmet({
  203. // note that more headers are added by default
  204. dnsPrefetchControl: false,
  205. referrerPolicy: { policy: 'origin-when-cross-origin' },
  206. hsts: false,
  207. })
  208. )
  209. // add CSP header to HTML-rendering routes, if enabled
  210. if (Settings.csp && Settings.csp.enabled) {
  211. logger.info('adding CSP header to rendered routes', Settings.csp)
  212. webRouter.use(csp(Settings.csp))
  213. }
  214. logger.info('creating HTTP server'.yellow)
  215. const server = require('http').createServer(app)
  216. // provide settings for separate web and api processes
  217. if (Settings.enabledServices.includes('api')) {
  218. logger.info('providing api router')
  219. app.use(privateApiRouter)
  220. app.use(Validation.errorMiddleware)
  221. app.use(ErrorController.handleApiError)
  222. }
  223. if (Settings.enabledServices.includes('web')) {
  224. logger.info('providing web router')
  225. if (Settings.precompilePugTemplatesAtBootTime) {
  226. logger.info('precompiling views for web in production environment')
  227. Views.precompileViews(app)
  228. }
  229. if (app.get('env') === 'test') {
  230. logger.info('enabling view cache for acceptance tests')
  231. app.enable('view cache')
  232. }
  233. app.use(publicApiRouter) // public API goes with web router for public access
  234. app.use(Validation.errorMiddleware)
  235. app.use(ErrorController.handleApiError)
  236. app.use(webRouter)
  237. app.use(Validation.errorMiddleware)
  238. app.use(ErrorController.handleError)
  239. }
  240. metrics.injectMetricsRoute(webRouter)
  241. metrics.injectMetricsRoute(privateApiRouter)
  242. Router.initialize(webRouter, privateApiRouter, publicApiRouter)
  243. module.exports = {
  244. app,
  245. server,
  246. }