HttpErrorHandler.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. const logger = require('@overleaf/logger')
  2. const Settings = require('@overleaf/settings')
  3. const { plainTextResponse } = require('../../infrastructure/Response')
  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: 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. module.exports = HttpErrorHandler = {
  44. handleErrorByStatusCode(req, res, error, statusCode) {
  45. const is400Error = statusCode >= 400 && statusCode < 500
  46. const is500Error = statusCode >= 500 && statusCode < 600
  47. if (is400Error) {
  48. logger.warn(error)
  49. } else if (is500Error) {
  50. logger.error(error)
  51. }
  52. if (statusCode === 403) {
  53. HttpErrorHandler.forbidden(req, res)
  54. } else if (statusCode === 404) {
  55. HttpErrorHandler.notFound(req, res)
  56. } else if (statusCode === 409) {
  57. HttpErrorHandler.conflict(req, res, '')
  58. } else if (statusCode === 422) {
  59. HttpErrorHandler.unprocessableEntity(req, res)
  60. } else if (is400Error) {
  61. handleGeneric400Error(req, res, statusCode)
  62. } else if (is500Error) {
  63. handleGeneric500Error(req, res, statusCode)
  64. } else {
  65. logger.error(
  66. { err: error, statusCode },
  67. `unable to handle error with status code ${statusCode}`
  68. )
  69. res.sendStatus(500)
  70. }
  71. },
  72. badRequest(req, res, message, info = {}) {
  73. handleGeneric400Error(req, res, 400, message, info)
  74. },
  75. conflict(req, res, message, info = {}) {
  76. res.status(409)
  77. switch (req.accepts(['html', 'json'])) {
  78. case 'html':
  79. return res.render('general/400', {
  80. title: 'Client Error',
  81. message: message,
  82. })
  83. case 'json':
  84. return renderJSONError(res, message, info)
  85. default:
  86. return plainTextResponse(res, 'conflict')
  87. }
  88. },
  89. forbidden(req, res, message = 'restricted', info = {}) {
  90. res.status(403)
  91. switch (req.accepts(['html', 'json'])) {
  92. case 'html':
  93. return res.render('user/restricted', { title: 'restricted' })
  94. case 'json':
  95. return renderJSONError(res, message, info)
  96. default:
  97. return plainTextResponse(res, 'restricted')
  98. }
  99. },
  100. notFound(req, res, message = 'not found', info = {}) {
  101. res.status(404)
  102. switch (req.accepts(['html', 'json'])) {
  103. case 'html':
  104. return res.render('general/404', { title: 'page_not_found' })
  105. case 'json':
  106. return renderJSONError(res, message, info)
  107. default:
  108. return plainTextResponse(res, 'not found')
  109. }
  110. },
  111. unprocessableEntity(req, res, message = 'unprocessable entity', info = {}) {
  112. res.status(422)
  113. switch (req.accepts(['html', 'json'])) {
  114. case 'html':
  115. return res.render('general/400', {
  116. title: 'Client Error',
  117. message: message,
  118. })
  119. case 'json':
  120. return renderJSONError(res, message, info)
  121. default:
  122. return plainTextResponse(res, 'unprocessable entity')
  123. }
  124. },
  125. legacyInternal(req, res, message, error) {
  126. logger.error(error)
  127. handleGeneric500Error(req, res, 500, message)
  128. },
  129. maintenance(req, res) {
  130. // load balancer health checks require a success response for /
  131. if (req.url === '/') {
  132. res.status(200)
  133. } else {
  134. res.status(503)
  135. }
  136. let message = `${Settings.appName} is currently down for maintenance.`
  137. if (Settings.statusPageUrl) {
  138. message += ` Please check https://${Settings.statusPageUrl} for updates.`
  139. }
  140. switch (req.accepts(['html', 'json'])) {
  141. case 'html':
  142. return res.render('general/closed', { title: 'maintenance' })
  143. case 'json':
  144. return renderJSONError(res, message, {})
  145. default:
  146. return plainTextResponse(res, message)
  147. }
  148. },
  149. }