[web] When switching primary email, delete the old primary if it's unconfirmed (#23688)

* Add note to ConfirmModal: unconfirmed primary will be deleted

* Change confirm button copy

* Promisify `UserEmailsController.setDefault`

* Update tests after promisification

* Delete unconfirmed primary when swapped

* Fixup apostrophe in translation

* `npm run extract-translations`

* Add unit tests

* Add acceptance tests

* Fix frontend tests

* Make email address bold

* Add "We removed the previous primary..." to the email

GitOrigin-RevId: c971e219e36e509f9963e1720acdd44f562a05b5
This commit is contained in:
Antoine Clausse
2025-02-21 08:42:26 +01:00
committed by Copybot
parent f0d94f06ed
commit 34cac93f9a
11 changed files with 195 additions and 70 deletions
@@ -38,16 +38,16 @@ describe('UserEmailsController', function () {
hasFeature: sinon.stub(),
}
this.UserSessionsManager = {
removeSessionsFromRedis: sinon.stub().yields(),
promises: { removeSessionsFromRedis: sinon.stub().resolves() },
}
this.UserUpdater = {
addEmailAddress: sinon.stub(),
setDefaultEmailAddress: sinon.stub(),
updateV1AndSetDefaultEmailAddress: sinon.stub(),
promises: {
addEmailAddress: sinon.stub().resolves(),
confirmEmail: sinon.stub().resolves(),
removeEmailAddress: sinon.stub(),
setDefaultEmailAddress: sinon.stub().resolves(),
},
}
this.EmailHelper = { parseEmail: sinon.stub() }
@@ -555,8 +555,6 @@ describe('UserEmailsController', function () {
})
it('sets default email', function (done) {
this.UserUpdater.setDefaultEmailAddress.yields()
this.UserEmailsController.setDefault(this.req, {
sendStatus: code => {
code.should.equal(200)
@@ -569,7 +567,7 @@ describe('UserEmailsController', function () {
}
)
assertCalledWith(
this.UserUpdater.setDefaultEmailAddress,
this.UserUpdater.promises.setDefaultEmailAddress,
this.user._id,
this.email
)
@@ -578,24 +576,68 @@ describe('UserEmailsController', function () {
})
})
it('deletes unconfirmed primary if delete-unconfirmed-primary is set', function (done) {
this.user.emails = [{ email: 'example@overleaf.com' }]
this.req.query['delete-unconfirmed-primary'] = ''
this.UserEmailsController.setDefault(this.req, {
sendStatus: () => {
assertCalledWith(
this.UserUpdater.promises.removeEmailAddress,
this.user._id,
'example@overleaf.com',
{
initiatorId: this.user._id,
ipAddress: this.req.ip,
extraInfo: {
info: 'removed unconfirmed email after setting new primary',
},
}
)
done()
},
})
})
it('doesnt delete a confirmed primary', function (done) {
this.user.emails = [
{ email: 'example@overleaf.com', confirmedAt: '2000-01-01' },
]
this.req.query['delete-unconfirmed-primary'] = ''
this.UserEmailsController.setDefault(this.req, {
sendStatus: () => {
assertNotCalled(this.UserUpdater.promises.removeEmailAddress)
done()
},
})
})
it('doesnt delete primary if delete-unconfirmed-primary is not set', function (done) {
this.UserEmailsController.setDefault(this.req, {
sendStatus: () => {
assertNotCalled(this.UserUpdater.promises.removeEmailAddress)
done()
},
})
})
it('handles email parse error', function (done) {
this.EmailHelper.parseEmail.returns(null)
this.UserEmailsController.setDefault(this.req, {
sendStatus: code => {
code.should.equal(422)
assertNotCalled(this.UserUpdater.setDefaultEmailAddress)
assertNotCalled(this.UserUpdater.promises.setDefaultEmailAddress)
done()
},
})
})
it('should reset the users other sessions', function (done) {
this.UserUpdater.setDefaultEmailAddress.yields()
this.res.callback = () => {
expect(
this.UserSessionsManager.removeSessionsFromRedis
this.UserSessionsManager.promises.removeSessionsFromRedis
).to.have.been.calledWith(this.user, this.req.sessionID)
done()
}
@@ -604,11 +646,10 @@ describe('UserEmailsController', function () {
})
it('handles error from revoking sessions and returns 200', function (done) {
this.UserUpdater.setDefaultEmailAddress.yields()
const redisError = new Error('redis error')
this.UserSessionsManager.removeSessionsFromRedis = sinon
this.UserSessionsManager.promises.removeSessionsFromRedis = sinon
.stub()
.yields(redisError)
.rejects(redisError)
this.res.callback = () => {
expect(this.res.statusCode).to.equal(200)