HttpErrorHandler.mjs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import logger from '@overleaf/logger'
  2. import Settings from '@overleaf/settings'
  3. import { plainTextResponse } from '../../infrastructure/Response.mjs'
  4. function renderJSONError(res, message, info = {}) {
  5. if (info.message) {
  6. logger.warn(
  7. info,
  8. `http error info shouldn't contain a 'message' field, will be overridden`
  9. )
  10. }
  11. if (message != null) {
  12. res.json({ ...info, message })
  13. } else {
  14. res.json(info)
  15. }
  16. }
  17. function handleGeneric500Error(req, res, statusCode, message) {
  18. res.status(statusCode)
  19. switch (req.accepts(['html', 'json'])) {
  20. case 'html':
  21. return res.render('general/500', { title: 'Server Error' })
  22. case 'json':
  23. return renderJSONError(res, message)
  24. default:
  25. return plainTextResponse(res, 'internal server error')
  26. }
  27. }
  28. function handleGeneric400Error(req, res, statusCode, message, info = {}) {
  29. res.status(statusCode)
  30. switch (req.accepts(['html', 'json'])) {
  31. case 'html':
  32. return res.render('general/400', {
  33. title: 'Client Error',
  34. message,
  35. })
  36. case 'json':
  37. return renderJSONError(res, message, info)
  38. default:
  39. return plainTextResponse(res, 'client error')
  40. }
  41. }
  42. let HttpErrorHandler
  43. export default HttpErrorHandler = {
  44. handleErrorByStatusCode(req, res, err, statusCode) {
  45. const is400Error = statusCode >= 400 && statusCode < 500
  46. const is500Error = statusCode >= 500 && statusCode < 600
  47. req.logger.addFields({ err })
  48. if (is400Error) {
  49. req.logger.setLevel('warn')
  50. } else if (is500Error) {
  51. req.logger.setLevel('error')
  52. }
  53. if (statusCode === 403) {
  54. HttpErrorHandler.forbidden(req, res)
  55. } else if (statusCode === 404) {
  56. HttpErrorHandler.notFound(req, res)
  57. } else if (statusCode === 409) {
  58. HttpErrorHandler.conflict(req, res, '')
  59. } else if (statusCode === 422) {
  60. HttpErrorHandler.unprocessableEntity(req, res)
  61. } else if (is400Error) {
  62. handleGeneric400Error(req, res, statusCode)
  63. } else if (is500Error) {
  64. handleGeneric500Error(req, res, statusCode)
  65. } else {
  66. res.sendStatus(500)
  67. }
  68. },
  69. badRequest(req, res, message, info = {}) {
  70. handleGeneric400Error(req, res, 400, message, info)
  71. },
  72. conflict(req, res, message, info = {}) {
  73. res.status(409)
  74. switch (req.accepts(['html', 'json'])) {
  75. case 'html':
  76. return res.render('general/400', {
  77. title: 'Client Error',
  78. message,
  79. })
  80. case 'json':
  81. return renderJSONError(res, message, info)
  82. default:
  83. return plainTextResponse(res, 'conflict')
  84. }
  85. },
  86. forbidden(req, res, message = 'restricted', info = {}) {
  87. res.status(403)
  88. switch (req.accepts(['html', 'json'])) {
  89. case 'html':
  90. return res.render('user/restricted', { title: 'restricted' })
  91. case 'json':
  92. return renderJSONError(res, message, info)
  93. default:
  94. return plainTextResponse(res, 'restricted')
  95. }
  96. },
  97. notFound(req, res, message = 'not found', info = {}) {
  98. res.status(404)
  99. switch (req.accepts(['html', 'json'])) {
  100. case 'html':
  101. return res.render('general/404', { title: 'page_not_found' })
  102. case 'json':
  103. return renderJSONError(res, message, info)
  104. default:
  105. return plainTextResponse(res, 'not found')
  106. }
  107. },
  108. unprocessableEntity(req, res, message = 'unprocessable entity', info = {}) {
  109. res.status(422)
  110. switch (req.accepts(['html', 'json'])) {
  111. case 'html':
  112. return res.render('general/400', {
  113. title: 'Client Error',
  114. message,
  115. })
  116. case 'json':
  117. return renderJSONError(res, message, info)
  118. default:
  119. return plainTextResponse(res, 'unprocessable entity')
  120. }
  121. },
  122. legacyInternal(req, res, message, err) {
  123. req.logger.addFields({ err })
  124. req.logger.setLevel('error')
  125. handleGeneric500Error(req, res, 500, message)
  126. },
  127. maintenance(req, res) {
  128. // load balancer health checks require a success response for /
  129. if (req.url === '/') {
  130. res.status(200)
  131. } else {
  132. res.status(503)
  133. }
  134. let message = Settings.maintenanceMessage
  135. if (!message) {
  136. message = `${Settings.appName} is currently down for maintenance.`
  137. if (Settings.statusPageUrl) {
  138. message += ` Please check ${Settings.statusPageUrl} for updates.`
  139. }
  140. }
  141. switch (req.accepts(['html', 'json'])) {
  142. case 'html':
  143. return res.render('general/closed', { title: 'maintenance' })
  144. case 'json':
  145. return renderJSONError(res, message, {})
  146. default:
  147. return plainTextResponse(res, message)
  148. }
  149. },
  150. }