Merge pull request #28799 from overleaf/rh-promisify-email-confirmation

Promisify UserEmailsConfirmationHandler

GitOrigin-RevId: 844d478818a374a498ff24cdabb87659b5bc75aa
This commit is contained in:
roo hutton
2025-10-10 10:18:43 +01:00
committed by Copybot
parent 26033aa76a
commit 85f39b75f4
2 changed files with 37 additions and 54 deletions

View File

@@ -5,7 +5,7 @@ const settings = require('@overleaf/settings')
const Errors = require('../Errors/Errors')
const UserUpdater = require('./UserUpdater')
const UserGetter = require('./UserGetter')
const { callbackify, promisify } = require('util')
const { callbackify } = require('util')
const crypto = require('crypto')
const SessionManager = require('../Authentication/SessionManager')
@@ -14,33 +14,27 @@ const TOKEN_EXPIRY_IN_S = 90 * 24 * 60 * 60
const TOKEN_USE = 'email_confirmation'
const CONFIRMATION_CODE_EXPIRY_IN_S = 10 * 60
function sendConfirmationEmail(userId, email, emailTemplate, callback) {
if (arguments.length === 3) {
callback = emailTemplate
emailTemplate = 'confirmEmail'
}
async function sendConfirmationEmail(
userId,
email,
emailTemplate = 'confirmEmail'
) {
email = EmailHelper.parseEmail(email)
if (!email) {
return callback(new Error('invalid email'))
throw new Error('invalid email')
}
const data = { user_id: userId, email }
OneTimeTokenHandler.getNewToken(
const token = await OneTimeTokenHandler.promises.getNewToken(
TOKEN_USE,
data,
{ expiresIn: TOKEN_EXPIRY_IN_S },
function (err, token) {
if (err) {
return callback(err)
}
const emailOptions = {
to: email,
confirmEmailUrl: `${settings.siteUrl}/user/emails/confirm?token=${token}`,
sendingUser_id: userId,
}
EmailHandler.sendEmail(emailTemplate, emailOptions, callback)
}
{ expiresIn: TOKEN_EXPIRY_IN_S }
)
const emailOptions = {
to: email,
confirmEmailUrl: `${settings.siteUrl}/user/emails/confirm?token=${token}`,
sendingUser_id: userId,
}
await EmailHandler.promises.sendEmail(emailTemplate, emailOptions)
}
async function sendConfirmationCode(email, welcomeUser) {
@@ -100,12 +94,12 @@ async function confirmEmailFromToken(req, token) {
}
const UserEmailsConfirmationHandler = {
sendConfirmationEmail,
confirmEmailFromToken: callbackify(confirmEmailFromToken),
sendConfirmationEmail: callbackify(sendConfirmationEmail),
}
UserEmailsConfirmationHandler.promises = {
sendConfirmationEmail: promisify(sendConfirmationEmail),
sendConfirmationEmail,
confirmEmailFromToken,
sendConfirmationCode,
}

View File

@@ -49,7 +49,7 @@ describe('UserEmailsConfirmationHandler', function () {
getUser: sinon.stub().resolves(this.mockUser),
},
}),
'../Email/EmailHandler': (this.EmailHandler = {}),
'../Email/EmailHandler': (this.EmailHandler = { promises: {} }),
'../Helpers/EmailHelper': EmailHelper,
'../Authentication/SessionManager': (this.SessionManager = {
getLoggedInUserId: sinon.stub().returns(this.mockUser._id),
@@ -61,23 +61,22 @@ describe('UserEmailsConfirmationHandler', function () {
describe('sendConfirmationEmail', function () {
beforeEach(function () {
this.OneTimeTokenHandler.getNewToken = sinon
this.OneTimeTokenHandler.promises.getNewToken = sinon
.stub()
.yields(null, (this.token = 'new-token'))
return (this.EmailHandler.sendEmail = sinon.stub().yields())
.resolves((this.token = 'new-token'))
return (this.EmailHandler.promises.sendEmail = sinon.stub().resolves())
})
describe('successfully', function () {
beforeEach(function () {
return this.UserEmailsConfirmationHandler.sendConfirmationEmail(
beforeEach(async function () {
await this.UserEmailsConfirmationHandler.promises.sendConfirmationEmail(
this.user_id,
this.email,
this.callback
this.email
)
})
it('should generate a token for the user which references their id and email', function () {
return this.OneTimeTokenHandler.getNewToken
return this.OneTimeTokenHandler.promises.getNewToken
.calledWith(
'email_confirmation',
{ user_id: this.user_id, email: this.email },
@@ -87,7 +86,7 @@ describe('UserEmailsConfirmationHandler', function () {
})
it('should send an email to the user', function () {
return this.EmailHandler.sendEmail
return this.EmailHandler.promises.sendEmail
.calledWith('confirmEmail', {
to: this.email,
confirmEmailUrl:
@@ -96,40 +95,30 @@ describe('UserEmailsConfirmationHandler', function () {
})
.should.equal(true)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
})
})
describe('with invalid email', function () {
beforeEach(function () {
return this.UserEmailsConfirmationHandler.sendConfirmationEmail(
this.user_id,
'!"£$%^&*()',
this.callback
)
})
it('should return an error', function () {
return this.callback
.calledWith(sinon.match.instanceOf(Error))
.should.equal(true)
it('should reject with an error', async function () {
await expect(
this.UserEmailsConfirmationHandler.promises.sendConfirmationEmail(
this.user_id,
'!"£$%^&*()'
)
).to.be.rejectedWith(Error)
})
})
describe('a custom template', function () {
beforeEach(function () {
return this.UserEmailsConfirmationHandler.sendConfirmationEmail(
beforeEach(async function () {
await this.UserEmailsConfirmationHandler.promises.sendConfirmationEmail(
this.user_id,
this.email,
'myCustomTemplate',
this.callback
'myCustomTemplate'
)
})
it('should send an email with the given template', function () {
return this.EmailHandler.sendEmail
return this.EmailHandler.promises.sendEmail
.calledWith('myCustomTemplate')
.should.equal(true)
})