PasswordResetController.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import PasswordResetHandler from './PasswordResetHandler.mjs'
  2. import AuthenticationController from '../Authentication/AuthenticationController.js'
  3. import AuthenticationManager from '../Authentication/AuthenticationManager.js'
  4. import SessionManager from '../Authentication/SessionManager.js'
  5. import UserGetter from '../User/UserGetter.js'
  6. import UserUpdater from '../User/UserUpdater.js'
  7. import UserSessionsManager from '../User/UserSessionsManager.js'
  8. import OError from '@overleaf/o-error'
  9. import EmailsHelper from '../Helpers/EmailHelper.js'
  10. import { expressify } from '@overleaf/promise-utils'
  11. async function setNewUserPassword(req, res, next) {
  12. let user
  13. let { passwordResetToken, password, email } = req.body
  14. if (!passwordResetToken || !password) {
  15. return res.status(400).json({
  16. message: {
  17. key: 'invalid-password',
  18. },
  19. })
  20. }
  21. const err = AuthenticationManager.validatePassword(password, email)
  22. if (err) {
  23. const message = AuthenticationManager.getMessageForInvalidPasswordError(
  24. err,
  25. req
  26. )
  27. return res.status(400).json({ message })
  28. }
  29. passwordResetToken = passwordResetToken.trim()
  30. const initiatorId = SessionManager.getLoggedInUserId(req.session)
  31. // password reset via tokens can be done while logged in, or not
  32. const auditLog = {
  33. initiatorId,
  34. ip: req.ip,
  35. }
  36. try {
  37. const result = await PasswordResetHandler.promises.setNewUserPassword(
  38. passwordResetToken,
  39. password,
  40. auditLog
  41. )
  42. const { found, reset, userId } = result
  43. if (!found) {
  44. return res.status(404).json({
  45. message: {
  46. key: 'token-expired',
  47. },
  48. })
  49. }
  50. if (!reset) {
  51. return res.status(500).json({
  52. message: req.i18n.translate('error_performing_request'),
  53. })
  54. }
  55. await UserSessionsManager.promises.removeSessionsFromRedis({ _id: userId })
  56. await UserUpdater.promises.removeReconfirmFlag(userId)
  57. if (!req.session.doLoginAfterPasswordReset) {
  58. return res.sendStatus(200)
  59. }
  60. user = await UserGetter.promises.getUser(userId)
  61. } catch (error) {
  62. if (error.name === 'NotFoundError') {
  63. return res.status(404).json({
  64. message: {
  65. key: 'token-expired',
  66. },
  67. })
  68. } else if (error.name === 'InvalidPasswordError') {
  69. return res.status(400).json({
  70. message: {
  71. key: 'invalid-password',
  72. },
  73. })
  74. } else if (error.name === 'PasswordMustBeDifferentError') {
  75. return res.status(400).json({
  76. message: {
  77. key: 'password-must-be-different',
  78. },
  79. })
  80. } else if (error.name === 'PasswordReusedError') {
  81. return res.status(400).json({
  82. message: {
  83. key: 'password-must-be-strong',
  84. },
  85. })
  86. } else {
  87. return res.status(500).json({
  88. message: req.i18n.translate('error_performing_request'),
  89. })
  90. }
  91. }
  92. AuthenticationController.setAuditInfo(req, {
  93. method: 'Password reset, set new password',
  94. })
  95. AuthenticationController.finishLogin(user, req, res, next)
  96. }
  97. async function requestReset(req, res, next) {
  98. const email = EmailsHelper.parseEmail(req.body.email)
  99. if (!email) {
  100. return res.status(400).json({
  101. message: req.i18n.translate('must_be_email_address'),
  102. })
  103. }
  104. let status
  105. try {
  106. status =
  107. await PasswordResetHandler.promises.generateAndEmailResetToken(email)
  108. } catch (err) {
  109. OError.tag(err, 'failed to generate and email password reset token', {
  110. email,
  111. })
  112. if (err.message === 'user does not have permission for change-password') {
  113. return res.status(403).json({
  114. message: {
  115. key: 'no-password-allowed-due-to-sso',
  116. },
  117. })
  118. }
  119. throw err
  120. }
  121. if (status === 'primary') {
  122. return res.status(200).json({
  123. message: req.i18n.translate('password_reset_email_sent'),
  124. })
  125. } else if (status === 'secondary') {
  126. return res.status(404).json({
  127. message: req.i18n.translate('secondary_email_password_reset'),
  128. })
  129. } else {
  130. return res.status(404).json({
  131. message: req.i18n.translate('cant_find_email'),
  132. })
  133. }
  134. }
  135. async function renderSetPasswordForm(req, res, next) {
  136. if (req.query.passwordResetToken != null) {
  137. try {
  138. const result =
  139. await PasswordResetHandler.promises.getUserForPasswordResetToken(
  140. req.query.passwordResetToken
  141. )
  142. const { user, remainingPeeks } = result || {}
  143. if (!user || remainingPeeks <= 0) {
  144. return res.redirect('/user/password/reset?error=token_expired')
  145. }
  146. req.session.resetToken = req.query.passwordResetToken
  147. let emailQuery = ''
  148. if (typeof req.query.email === 'string') {
  149. const email = EmailsHelper.parseEmail(req.query.email)
  150. if (email) {
  151. emailQuery = `?email=${encodeURIComponent(email)}`
  152. }
  153. }
  154. return res.redirect('/user/password/set' + emailQuery)
  155. } catch (err) {
  156. if (err.name === 'ForbiddenError') {
  157. return next(err)
  158. }
  159. return res.redirect('/user/password/reset?error=token_expired')
  160. }
  161. }
  162. if (req.session.resetToken == null) {
  163. return res.redirect('/user/password/reset')
  164. }
  165. const email = EmailsHelper.parseEmail(req.query.email)
  166. // clean up to avoid leaking the token in the session object
  167. const passwordResetToken = req.session.resetToken
  168. delete req.session.resetToken
  169. res.render('user/setPassword', {
  170. title: 'set_password',
  171. email,
  172. passwordResetToken,
  173. })
  174. }
  175. export default {
  176. renderRequestResetForm(req, res) {
  177. const errorQuery = req.query.error
  178. let error = null
  179. if (errorQuery === 'token_expired') {
  180. error = 'password_reset_token_expired'
  181. }
  182. res.render('user/passwordReset', {
  183. title: 'reset_password',
  184. error,
  185. })
  186. },
  187. requestReset: expressify(requestReset),
  188. renderSetPasswordForm: expressify(renderSetPasswordForm),
  189. setNewUserPassword: expressify(setNewUserPassword),
  190. }