UserEmailsConfirmationHandler.mjs 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import EmailHelper from '../Helpers/EmailHelper.mjs'
  2. import EmailHandler from '../Email/EmailHandler.mjs'
  3. import OneTimeTokenHandler from '../Security/OneTimeTokenHandler.mjs'
  4. import Errors from '../Errors/Errors.js'
  5. import UserUpdater from './UserUpdater.mjs'
  6. import UserGetter from './UserGetter.mjs'
  7. import { callbackify } from 'node:util'
  8. import crypto from 'node:crypto'
  9. import SessionManager from '../Authentication/SessionManager.mjs'
  10. // Reject email confirmation tokens after 90 days
  11. const TOKEN_USE = 'email_confirmation'
  12. const CONFIRMATION_CODE_EXPIRY_IN_S = 10 * 60
  13. async function sendConfirmationCode(email, welcomeUser) {
  14. if (!EmailHelper.parseEmail(email)) {
  15. throw new Error('invalid email')
  16. }
  17. const confirmCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, '0')
  18. const confirmCodeExpiresTimestamp =
  19. Date.now() + CONFIRMATION_CODE_EXPIRY_IN_S * 1000
  20. await EmailHandler.promises.sendEmail('confirmCode', {
  21. to: email,
  22. confirmCode,
  23. welcomeUser,
  24. category: ['ConfirmEmail'],
  25. })
  26. return {
  27. confirmCode,
  28. confirmCodeExpiresTimestamp,
  29. }
  30. }
  31. async function confirmEmailFromToken(req, token) {
  32. const { data } = await OneTimeTokenHandler.promises.peekValueFromToken(
  33. TOKEN_USE,
  34. token
  35. )
  36. if (!data) {
  37. throw new Errors.NotFoundError('no token found')
  38. }
  39. const loggedInUserId = SessionManager.getLoggedInUserId(req.session)
  40. // user_id may be stored as an ObjectId or string
  41. const userId = data.user_id?.toString()
  42. const email = data.email
  43. if (!userId || email !== EmailHelper.parseEmail(email)) {
  44. throw new Errors.NotFoundError('invalid data')
  45. }
  46. if (loggedInUserId !== userId) {
  47. throw new Errors.ForbiddenError('logged in user does not match token user')
  48. }
  49. const user = await UserGetter.promises.getUser(userId, { emails: 1 })
  50. if (!user) {
  51. throw new Errors.NotFoundError('user not found')
  52. }
  53. const emailExists = user.emails.some(emailData => emailData.email === email)
  54. if (!emailExists) {
  55. throw new Errors.NotFoundError('email missing for user')
  56. }
  57. await OneTimeTokenHandler.promises.expireToken(TOKEN_USE, token)
  58. await UserUpdater.promises.confirmEmail(userId, email)
  59. return { userId, email }
  60. }
  61. const UserEmailsConfirmationHandler = {
  62. confirmEmailFromToken: callbackify(confirmEmailFromToken),
  63. }
  64. UserEmailsConfirmationHandler.promises = {
  65. confirmEmailFromToken,
  66. sendConfirmationCode,
  67. }
  68. export default UserEmailsConfirmationHandler