SessionAutostartMiddleware.mjs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import Settings from '@overleaf/settings'
  2. import OError from '@overleaf/o-error'
  3. const botUserAgents = [
  4. 'kube-probe',
  5. 'GoogleStackdriverMonitoring',
  6. 'GoogleHC',
  7. 'Googlebot',
  8. 'bingbot',
  9. 'facebookexternal',
  10. ].map(agent => {
  11. return agent.toLowerCase()
  12. })
  13. // SessionAutostartMiddleware provides a mechanism to force certain routes not
  14. // to get an automatic session where they don't have one already. This allows us
  15. // to work around issues where we might overwrite a user's login cookie with one
  16. // that is hidden by a `SameSite` setting.
  17. //
  18. // When registering a route with disableSessionAutostartForRoute, a callback
  19. // should be provided that handles the case that a session is not available.
  20. // This will be called as a standard middleware with (req, res, next) - calling
  21. // next will continue and sett up a session as normal, otherwise the app can
  22. // perform a different operation as usual
  23. class SessionAutostartMiddleware {
  24. constructor() {
  25. this.middleware = this.middleware.bind(this)
  26. this._cookieName = Settings.cookieName
  27. this._noAutostartCallbacks = new Map()
  28. }
  29. static applyInitialMiddleware(router) {
  30. const middleware = new SessionAutostartMiddleware()
  31. router.sessionAutostartMiddleware = middleware
  32. router.use(middleware.middleware)
  33. }
  34. disableSessionAutostartForRoute(route, method, callback) {
  35. if (typeof callback !== 'function') {
  36. throw new Error('callback not provided when disabling session autostart')
  37. }
  38. if (!this._noAutostartCallbacks[route]) {
  39. this._noAutostartCallbacks[route] = new Map()
  40. }
  41. this._noAutostartCallbacks[route][method] = callback
  42. }
  43. applyDefaultPostGatewayForRoute(route) {
  44. this.disableSessionAutostartForRoute(
  45. route,
  46. 'POST',
  47. SessionAutostartMiddleware.genericPostGatewayMiddleware
  48. )
  49. }
  50. autostartCallbackForRequest(req) {
  51. return (
  52. this._noAutostartCallbacks[req.path] &&
  53. this._noAutostartCallbacks[req.path][req.method]
  54. )
  55. }
  56. reqIsBot(req) {
  57. const agent = (req.headers['user-agent'] || '').toLowerCase()
  58. const foundMatch = botUserAgents.find(botAgent => {
  59. return agent.includes(botAgent)
  60. })
  61. if (foundMatch) {
  62. return true
  63. } else {
  64. return false
  65. }
  66. }
  67. middleware(req, _res, next) {
  68. if (
  69. !req.signedCookies[this._cookieName] &&
  70. req.query?.autostartSession !== 'true'
  71. ) {
  72. const callback = this.autostartCallbackForRequest(req)
  73. if (callback) {
  74. req.session = {
  75. noSessionCallback: callback,
  76. }
  77. } else if (this.reqIsBot(req)) {
  78. req.session = {
  79. noSessionCallback: (_req, _res, next) => {
  80. next()
  81. },
  82. // prevent exception for bot accesses to /project (which requires
  83. // login and regenerates session)
  84. regenerate: cb => cb(),
  85. }
  86. }
  87. }
  88. next()
  89. }
  90. static invokeCallbackMiddleware(req, res, next) {
  91. if (req.session.noSessionCallback) {
  92. return req.session.noSessionCallback(req, res, next)
  93. }
  94. next()
  95. }
  96. static genericPostGatewayMiddleware(req, res, next) {
  97. if (req.method !== 'POST') {
  98. return next(
  99. new OError('post gateway invoked for non-POST request', {
  100. path: req.path,
  101. method: req.method,
  102. })
  103. )
  104. }
  105. if (req.body.viaGateway) {
  106. return next()
  107. }
  108. res.render('general/post-gateway', { form_data: req.body })
  109. }
  110. }
  111. export default SessionAutostartMiddleware