do not use v1 for setting default email

GitOrigin-RevId: 00d4610ae55c0a90699d4bc79e7d08d432087abe
This commit is contained in:
Ersun Warncke 2019-07-22 12:55:35 -04:00 committed by sharelatex
parent 0a2a32120f
commit d9b5941642
5 changed files with 117 additions and 144 deletions

View file

@ -87,18 +87,12 @@ module.exports = UserEmailsController = {
if (email == null) { if (email == null) {
return res.sendStatus(422) return res.sendStatus(422)
} }
UserUpdater.setDefaultEmailAddress(userId, email, err => {
return UserUpdater.updateV1AndSetDefaultEmailAddress( if (err) {
userId, return UserEmailsController._handleEmailError(err, req, res, next)
email,
function(error) {
if (error != null) {
return UserEmailsController._handleEmailError(error, req, res, next)
} else {
return res.sendStatus(200)
} }
} res.sendStatus(200)
) })
}, },
endorse(req, res, next) { endorse(req, res, next) {

View file

@ -28,8 +28,6 @@ const {
const FeaturesUpdater = require('../Subscription/FeaturesUpdater') const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
const EmailHelper = require('../Helpers/EmailHelper') const EmailHelper = require('../Helpers/EmailHelper')
const Errors = require('../Errors/Errors') const Errors = require('../Errors/Errors')
const Settings = require('settings-sharelatex')
const request = require('request')
const NewsletterManager = require('../Newsletter/NewsletterManager') const NewsletterManager = require('../Newsletter/NewsletterManager')
module.exports = UserUpdater = { module.exports = UserUpdater = {
@ -71,7 +69,7 @@ module.exports = UserUpdater = {
return cb(error) return cb(error)
}), }),
cb => UserUpdater.addEmailAddress(userId, newEmail, cb), cb => UserUpdater.addEmailAddress(userId, newEmail, cb),
cb => UserUpdater.setDefaultEmailAddress(userId, newEmail, cb), cb => UserUpdater.setDefaultEmailAddress(userId, newEmail, true, cb),
cb => UserUpdater.removeEmailAddress(userId, oldEmail, cb) cb => UserUpdater.removeEmailAddress(userId, oldEmail, cb)
], ],
callback callback
@ -156,112 +154,47 @@ module.exports = UserUpdater = {
// set the default email address by setting the `email` attribute. The email // set the default email address by setting the `email` attribute. The email
// must be one of the user's multiple emails (`emails` attribute) // must be one of the user's multiple emails (`emails` attribute)
setDefaultEmailAddress(userId, email, callback) { setDefaultEmailAddress(userId, email, allowUnconfirmed, callback) {
if (typeof allowUnconfirmed === 'function') {
callback = allowUnconfirmed
allowUnconfirmed = false
}
email = EmailHelper.parseEmail(email) email = EmailHelper.parseEmail(email)
if (email == null) { if (email == null) {
return callback(new Error('invalid email')) return callback(new Error('invalid email'))
} }
return UserGetter.getUserEmail(userId, (error, oldEmail) => { UserGetter.getUser(userId, { email: 1, emails: 1 }, (err, user) => {
if (typeof err !== 'undefined' && err !== null) { if (err) {
return callback(error) return callback(err)
}
if (!user) {
return callback(new Error('invalid userId'))
}
const oldEmail = user.email
const userEmail = user.emails.find(e => e.email === email)
if (!userEmail) {
return callback(new Error('Default email does not belong to user'))
}
if (!userEmail.confirmedAt && !allowUnconfirmed) {
return callback(new Errors.UnconfirmedEmailError())
} }
const query = { _id: userId, 'emails.email': email } const query = { _id: userId, 'emails.email': email }
const update = { $set: { email } } const update = { $set: { email } }
return this.updateUser(query, update, function(error, res) { UserUpdater.updateUser(query, update, (err, res) => {
if (error != null) { if (err) {
logger.warn({ error }, 'problem setting default emails') return callback(err)
return callback(error)
} else if (res.n === 0) {
// TODO: Check n or nMatched?
return callback(new Error('Default email does not belong to user'))
} else {
NewsletterManager.changeEmail(oldEmail, email, function() {})
return callback()
} }
// this should not happen
if (res.n === 0) {
return callback(new Error('email update error'))
}
// do not wait - this will log its own errors
NewsletterManager.changeEmail(oldEmail, email, () => {})
callback()
}) })
}) })
}, },
updateV1AndSetDefaultEmailAddress(userId, email, callback) {
return this.updateEmailAddressInV1(userId, email, error => {
if (error != null) {
return callback(error)
}
return this.setDefaultEmailAddress(userId, email, callback)
})
},
updateEmailAddressInV1(userId, newEmail, callback) {
if (!Settings.apis.v1.url) {
return callback()
}
return UserGetter.getUser(userId, { 'overleaf.id': 1, emails: 1 }, function(
error,
user
) {
let email
if (error != null) {
return callback(error)
}
if (user == null) {
return callback(new Errors.NotFoundError('no user found'))
}
if ((user.overleaf != null ? user.overleaf.id : undefined) == null) {
return callback()
}
let newEmailIsConfirmed = false
for (email of Array.from(user.emails)) {
if (email.email === newEmail && email.confirmedAt != null) {
newEmailIsConfirmed = true
break
}
}
if (!newEmailIsConfirmed) {
return callback(
new Errors.UnconfirmedEmailError(
"can't update v1 with unconfirmed email"
)
)
}
return request(
{
baseUrl: Settings.apis.v1.url,
url: `/api/v1/sharelatex/users/${user.overleaf.id}/email`,
method: 'PUT',
auth: {
user: Settings.apis.v1.user,
pass: Settings.apis.v1.pass,
sendImmediately: true
},
json: {
user: {
email: newEmail
}
},
timeout: 5 * 1000
},
function(error, response, body) {
if (error != null) {
if (error.code === 'ECONNREFUSED') {
error = new Errors.V1ConnectionError('No V1 connection')
}
return callback(error)
}
if (response.statusCode === 409) {
// Conflict
return callback(new Errors.EmailExistsError('email exists in v1'))
} else if (response.statusCode >= 200 && response.statusCode < 300) {
return callback()
} else {
return callback(
new Error(`non-success code from v1: ${response.statusCode}`)
)
}
}
)
})
},
confirmEmail(userId, email, confirmedAt, callback) { confirmEmail(userId, email, confirmedAt, callback) {
if (arguments.length === 3) { if (arguments.length === 3) {
callback = confirmedAt callback = confirmedAt

View file

@ -653,7 +653,7 @@ describe('UserEmails', function() {
) )
}) })
it('should update the email in v1 if confirmed', function(done) { it('should not update the email in v1', function(done) {
const token = null const token = null
return async.series( return async.series(
[ [
@ -725,18 +725,13 @@ describe('UserEmails', function() {
if (error != null) { if (error != null) {
return done(error) return done(error)
} }
expect( expect(MockV1Api.updateEmail.callCount).to.equal(0)
MockV1Api.updateEmail.calledWith( done()
42,
'new-confirmed-default-in-v1@example.com'
)
).to.equal(true)
return done()
} }
) )
}) })
it('should return an error if the email exists in v1', function(done) { it('should not return an error if the email exists in v1', function(done) {
MockV1Api.existingEmails.push('exists-in-v1@example.com') MockV1Api.existingEmails.push('exists-in-v1@example.com')
return async.series( return async.series(
[ [
@ -798,11 +793,8 @@ describe('UserEmails', function() {
if (error != null) { if (error != null) {
return done(error) return done(error)
} }
expect(response.statusCode).to.equal(409) expect(response.statusCode).to.equal(200)
expect(body).to.deep.equal({ cb()
message: 'This email is already registered'
})
return cb()
} }
) )
}, },
@ -810,8 +802,8 @@ describe('UserEmails', function() {
return this.user.request( return this.user.request(
{ url: '/user/emails', json: true }, { url: '/user/emails', json: true },
function(error, response, body) { function(error, response, body) {
expect(body[0].default).to.equal(true) expect(body[0].default).to.equal(false)
expect(body[1].default).to.equal(false) expect(body[1].default).to.equal(true)
return cb() return cb()
} }
) )

View file

@ -187,22 +187,22 @@ describe('UserEmailsController', function() {
beforeEach(function() { beforeEach(function() {
this.email = 'email_to_set_default@bar.com' this.email = 'email_to_set_default@bar.com'
this.req.body.email = this.email this.req.body.email = this.email
return this.EmailHelper.parseEmail.returns(this.email) this.EmailHelper.parseEmail.returns(this.email)
}) })
it('sets default email', function(done) { it('sets default email', function(done) {
this.UserUpdater.updateV1AndSetDefaultEmailAddress.callsArgWith(2, null) this.UserUpdater.setDefaultEmailAddress.yields()
return this.UserEmailsController.setDefault(this.req, { this.UserEmailsController.setDefault(this.req, {
sendStatus: code => { sendStatus: code => {
code.should.equal(200) code.should.equal(200)
assertCalledWith(this.EmailHelper.parseEmail, this.email) assertCalledWith(this.EmailHelper.parseEmail, this.email)
assertCalledWith( assertCalledWith(
this.UserUpdater.updateV1AndSetDefaultEmailAddress, this.UserUpdater.setDefaultEmailAddress,
this.user._id, this.user._id,
this.email this.email
) )
return done() done()
} }
}) })
}) })
@ -210,7 +210,7 @@ describe('UserEmailsController', function() {
it('handles email parse error', function(done) { it('handles email parse error', function(done) {
this.EmailHelper.parseEmail.returns(null) this.EmailHelper.parseEmail.returns(null)
return this.UserEmailsController.setDefault(this.req, { this.UserEmailsController.setDefault(this.req, {
sendStatus: code => { sendStatus: code => {
code.should.equal(422) code.should.equal(422)
assertNotCalled(this.UserUpdater.setDefaultEmailAddress) assertNotCalled(this.UserUpdater.setDefaultEmailAddress)

View file

@ -74,7 +74,8 @@ describe('UserUpdater', function() {
name: 'bob', name: 'bob',
email: 'hello@world.com' email: 'hello@world.com'
} }
return (this.newEmail = 'bob@bob.com') this.newEmail = 'bob@bob.com'
this.callback = sinon.stub()
}) })
afterEach(() => tk.reset()) afterEach(() => tk.reset())
@ -83,7 +84,7 @@ describe('UserUpdater', function() {
beforeEach(function() { beforeEach(function() {
this.UserGetter.getUserEmail.callsArgWith(1, null, this.stubbedUser.email) this.UserGetter.getUserEmail.callsArgWith(1, null, this.stubbedUser.email)
this.UserUpdater.addEmailAddress = sinon.stub().callsArgWith(2) this.UserUpdater.addEmailAddress = sinon.stub().callsArgWith(2)
this.UserUpdater.setDefaultEmailAddress = sinon.stub().callsArgWith(2) this.UserUpdater.setDefaultEmailAddress = sinon.stub().yields()
return (this.UserUpdater.removeEmailAddress = sinon return (this.UserUpdater.removeEmailAddress = sinon
.stub() .stub()
.callsArgWith(2)) .callsArgWith(2))
@ -325,14 +326,20 @@ describe('UserUpdater', function() {
describe('setDefaultEmailAddress', function() { describe('setDefaultEmailAddress', function() {
beforeEach(function() { beforeEach(function() {
this.UserGetter.getUserEmail.callsArgWith(1, null, this.stubbedUser.email) this.stubbedUser.emails = [
return this.NewsletterManager.changeEmail.callsArgWith(2, null) {
email: this.newEmail,
confirmedAt: new Date()
}
]
this.UserGetter.getUser = sinon.stub().yields(null, this.stubbedUser)
this.NewsletterManager.changeEmail.callsArgWith(2, null)
}) })
it('set default', function(done) { it('set default', function(done) {
this.UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, { n: 1 }) this.UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, { n: 1 })
return this.UserUpdater.setDefaultEmailAddress( this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id, this.stubbedUser._id,
this.newEmail, this.newEmail,
err => { err => {
@ -343,7 +350,7 @@ describe('UserUpdater', function() {
{ $set: { email: this.newEmail } } { $set: { email: this.newEmail } }
) )
.should.equal(true) .should.equal(true)
return done() done()
} }
) )
}) })
@ -351,7 +358,7 @@ describe('UserUpdater', function() {
it('set changed the email in newsletter', function(done) { it('set changed the email in newsletter', function(done) {
this.UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, { n: 1 }) this.UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, { n: 1 })
return this.UserUpdater.setDefaultEmailAddress( this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id, this.stubbedUser._id,
this.newEmail, this.newEmail,
err => { err => {
@ -359,7 +366,7 @@ describe('UserUpdater', function() {
this.NewsletterManager.changeEmail this.NewsletterManager.changeEmail
.calledWith(this.stubbedUser.email, this.newEmail) .calledWith(this.stubbedUser.email, this.newEmail)
.should.equal(true) .should.equal(true)
return done() done()
} }
) )
}) })
@ -369,12 +376,12 @@ describe('UserUpdater', function() {
.stub() .stub()
.callsArgWith(2, new Error('nope')) .callsArgWith(2, new Error('nope'))
return this.UserUpdater.setDefaultEmailAddress( this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id, this.stubbedUser._id,
this.newEmail, this.newEmail,
err => { err => {
should.exist(err) should.exist(err)
return done() done()
} }
) )
}) })
@ -382,26 +389,73 @@ describe('UserUpdater', function() {
it('handle missed update', function(done) { it('handle missed update', function(done) {
this.UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, { n: 0 }) this.UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, { n: 0 })
return this.UserUpdater.setDefaultEmailAddress( this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id, this.stubbedUser._id,
this.newEmail, this.newEmail,
err => { err => {
should.exist(err) should.exist(err)
return done() done()
} }
) )
}) })
it('validates email', function(done) { it('validates email', function(done) {
return this.UserUpdater.setDefaultEmailAddress( this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id, this.stubbedUser._id,
'.edu', '.edu',
err => { err => {
should.exist(err) should.exist(err)
return done() done()
} }
) )
}) })
describe('when email not confirmed', () => {
beforeEach(function() {
this.stubbedUser.emails = [
{
email: this.newEmail,
confirmedAt: null
}
]
this.UserGetter.getUser = sinon.stub().yields(null, this.stubbedUser)
this.UserUpdater.updateUser = sinon.stub()
this.NewsletterManager.changeEmail = sinon.stub()
})
it('should callback with error', function() {
this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id,
this.newEmail,
this.callback
)
this.callback.firstCall.args[0].name.should.equal(
'UnconfirmedEmailError'
)
this.UserUpdater.updateUser.callCount.should.equal(0)
this.NewsletterManager.changeEmail.callCount.should.equal(0)
})
})
describe('when email does not belong to user', () => {
beforeEach(function() {
this.stubbedUser.emails = []
this.UserGetter.getUser = sinon.stub().yields(null, this.stubbedUser)
this.UserUpdater.updateUser = sinon.stub()
this.NewsletterManager.changeEmail = sinon.stub()
})
it('should callback with error', function() {
this.UserUpdater.setDefaultEmailAddress(
this.stubbedUser._id,
this.newEmail,
this.callback
)
this.callback.firstCall.args[0].name.should.equal('Error')
this.UserUpdater.updateUser.callCount.should.equal(0)
this.NewsletterManager.changeEmail.callCount.should.equal(0)
})
})
}) })
describe('confirmEmail', function() { describe('confirmEmail', function() {