PasswordResetHandler.mjs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import settings from '@overleaf/settings'
  2. import UserAuditLogHandler from '../User/UserAuditLogHandler.js'
  3. import UserGetter from '../User/UserGetter.js'
  4. import OneTimeTokenHandler from '../Security/OneTimeTokenHandler.js'
  5. import EmailHandler from '../Email/EmailHandler.js'
  6. import AuthenticationManager from '../Authentication/AuthenticationManager.js'
  7. import { callbackify, promisify } from 'node:util'
  8. import PermissionsManager from '../Authorization/PermissionsManager.js'
  9. const assertUserPermissions = PermissionsManager.promises.assertUserPermissions
  10. const AUDIT_LOG_TOKEN_PREFIX_LENGTH = 10
  11. async function generateAndEmailResetToken(email) {
  12. const user = await UserGetter.promises.getUserByAnyEmail(email)
  13. if (!user) {
  14. return null
  15. }
  16. if (user.email !== email) {
  17. return 'secondary'
  18. }
  19. await assertUserPermissions(user, ['change-password'])
  20. const data = { user_id: user._id.toString(), email }
  21. const token = await OneTimeTokenHandler.promises.getNewToken('password', data)
  22. const emailOptions = {
  23. to: email,
  24. setNewPasswordUrl: `${
  25. settings.siteUrl
  26. }/user/password/set?passwordResetToken=${token}&email=${encodeURIComponent(
  27. email
  28. )}`,
  29. }
  30. await EmailHandler.promises.sendEmail('passwordResetRequested', emailOptions)
  31. return 'primary'
  32. }
  33. function expirePasswordResetToken(token, callback) {
  34. OneTimeTokenHandler.expireToken('password', token, err => {
  35. return callback(err)
  36. })
  37. }
  38. async function getUserForPasswordResetToken(token) {
  39. let result
  40. try {
  41. result = await OneTimeTokenHandler.promises.peekValueFromToken(
  42. 'password',
  43. token
  44. )
  45. } catch (err) {
  46. if (err.name === 'NotFoundError') {
  47. return
  48. } else {
  49. throw err
  50. }
  51. }
  52. const { data, remainingPeeks } = result || {}
  53. if (data == null || data.email == null) {
  54. return { user: null, remainingPeeks }
  55. }
  56. const user = await UserGetter.promises.getUserByMainEmail(data.email, {
  57. _id: 1,
  58. 'overleaf.id': 1,
  59. email: 1,
  60. })
  61. await assertUserPermissions(user, ['change-password'])
  62. if (user == null) {
  63. return { user: null, remainingPeeks: 0 }
  64. } else if (data.user_id != null && data.user_id === user._id.toString()) {
  65. return { user, remainingPeeks }
  66. } else if (
  67. data.v1_user_id != null &&
  68. user.overleaf != null &&
  69. data.v1_user_id === user.overleaf.id
  70. ) {
  71. return { user, remainingPeeks }
  72. } else {
  73. return { user: null, remainingPeeks: 0 }
  74. }
  75. }
  76. async function setNewUserPassword(token, password, auditLog) {
  77. const result =
  78. await PasswordResetHandler.promises.getUserForPasswordResetToken(token)
  79. const { user } = result || {}
  80. if (!user) {
  81. return {
  82. found: false,
  83. reset: false,
  84. userId: null,
  85. }
  86. }
  87. await UserAuditLogHandler.promises.addEntry(
  88. user._id,
  89. 'reset-password',
  90. auditLog.initiatorId,
  91. auditLog.ip,
  92. { token: token.substring(0, AUDIT_LOG_TOKEN_PREFIX_LENGTH) }
  93. )
  94. const reset = await AuthenticationManager.promises.setUserPassword(
  95. user,
  96. password
  97. )
  98. await PasswordResetHandler.promises.expirePasswordResetToken(token)
  99. return { found: true, reset, userId: user._id }
  100. }
  101. const PasswordResetHandler = {
  102. generateAndEmailResetToken: callbackify(generateAndEmailResetToken),
  103. setNewUserPassword: callbackify(setNewUserPassword),
  104. getUserForPasswordResetToken: callbackify(getUserForPasswordResetToken),
  105. expirePasswordResetToken,
  106. }
  107. PasswordResetHandler.promises = {
  108. generateAndEmailResetToken,
  109. getUserForPasswordResetToken,
  110. expirePasswordResetToken: promisify(
  111. PasswordResetHandler.expirePasswordResetToken
  112. ),
  113. setNewUserPassword,
  114. }
  115. export default PasswordResetHandler