2019-05-29 05:21:06 -04:00
|
|
|
/* eslint-disable
|
|
|
|
camelcase,
|
|
|
|
handle-callback-err,
|
|
|
|
max-len,
|
|
|
|
no-undef,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS101: Remove unnecessary use of Array.from
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
let UserUpdater
|
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const mongojs = require('../../infrastructure/mongojs')
|
|
|
|
const metrics = require('metrics-sharelatex')
|
|
|
|
const { db } = mongojs
|
|
|
|
const async = require('async')
|
|
|
|
const { ObjectId } = mongojs
|
|
|
|
const UserGetter = require('./UserGetter')
|
|
|
|
const {
|
|
|
|
addAffiliation,
|
|
|
|
removeAffiliation
|
|
|
|
} = require('../Institutions/InstitutionsAPI')
|
|
|
|
const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
|
|
|
|
const EmailHelper = require('../Helpers/EmailHelper')
|
|
|
|
const Errors = require('../Errors/Errors')
|
|
|
|
const Settings = require('settings-sharelatex')
|
|
|
|
const request = require('request')
|
|
|
|
const NewsletterManager = require('../Newsletter/NewsletterManager')
|
|
|
|
|
|
|
|
module.exports = UserUpdater = {
|
|
|
|
updateUser(query, update, callback) {
|
|
|
|
if (callback == null) {
|
|
|
|
callback = function(error) {}
|
|
|
|
}
|
|
|
|
if (typeof query === 'string') {
|
|
|
|
query = { _id: ObjectId(query) }
|
|
|
|
} else if (query instanceof ObjectId) {
|
|
|
|
query = { _id: query }
|
|
|
|
} else if (typeof query._id === 'string') {
|
|
|
|
query._id = ObjectId(query._id)
|
|
|
|
}
|
|
|
|
|
|
|
|
return db.users.update(query, update, callback)
|
|
|
|
},
|
|
|
|
|
|
|
|
//
|
|
|
|
// DEPRECATED
|
|
|
|
//
|
|
|
|
// Change the user's main email address by adding a new email, switching the
|
|
|
|
// default email and removing the old email. Prefer manipulating multiple
|
|
|
|
// emails and the default rather than calling this method directly
|
|
|
|
//
|
|
|
|
changeEmailAddress(userId, newEmail, callback) {
|
|
|
|
newEmail = EmailHelper.parseEmail(newEmail)
|
|
|
|
if (newEmail == null) {
|
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
|
|
|
logger.log({ userId, newEmail }, 'updaing email address of user')
|
|
|
|
|
|
|
|
let oldEmail = null
|
|
|
|
return async.series(
|
|
|
|
[
|
|
|
|
cb =>
|
|
|
|
UserGetter.getUserEmail(userId, function(error, email) {
|
|
|
|
oldEmail = email
|
|
|
|
return cb(error)
|
|
|
|
}),
|
|
|
|
cb => UserUpdater.addEmailAddress(userId, newEmail, cb),
|
|
|
|
cb => UserUpdater.setDefaultEmailAddress(userId, newEmail, cb),
|
|
|
|
cb => UserUpdater.removeEmailAddress(userId, oldEmail, cb)
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
// Add a new email address for the user. Email cannot be already used by this
|
|
|
|
// or any other user
|
|
|
|
addEmailAddress(userId, newEmail, affiliationOptions, callback) {
|
|
|
|
if (callback == null) {
|
|
|
|
// affiliationOptions is optional
|
|
|
|
callback = affiliationOptions
|
|
|
|
affiliationOptions = {}
|
|
|
|
}
|
|
|
|
newEmail = EmailHelper.parseEmail(newEmail)
|
|
|
|
if (newEmail == null) {
|
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
|
|
|
|
|
|
|
return UserGetter.ensureUniqueEmailAddress(newEmail, error => {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
return addAffiliation(userId, newEmail, affiliationOptions, error => {
|
|
|
|
if (error != null) {
|
2019-07-01 09:48:09 -04:00
|
|
|
logger.warn(
|
|
|
|
{ error },
|
|
|
|
'problem adding affiliation while adding email'
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
const reversedHostname = newEmail
|
|
|
|
.split('@')[1]
|
|
|
|
.split('')
|
|
|
|
.reverse()
|
|
|
|
.join('')
|
|
|
|
const update = {
|
|
|
|
$push: {
|
|
|
|
emails: { email: newEmail, createdAt: new Date(), reversedHostname }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.updateUser(userId, update, function(error) {
|
|
|
|
if (error != null) {
|
2019-07-01 09:48:09 -04:00
|
|
|
logger.warn({ error }, 'problem updating users emails')
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
return callback()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
// remove one of the user's email addresses. The email cannot be the user's
|
|
|
|
// default email address
|
|
|
|
removeEmailAddress(userId, email, callback) {
|
|
|
|
email = EmailHelper.parseEmail(email)
|
|
|
|
if (email == null) {
|
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
|
|
|
return removeAffiliation(userId, email, error => {
|
|
|
|
if (error != null) {
|
2019-07-01 09:48:09 -04:00
|
|
|
logger.warn({ error }, 'problem removing affiliation')
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
const query = { _id: userId, email: { $ne: email } }
|
|
|
|
const update = { $pull: { emails: { email } } }
|
|
|
|
return this.updateUser(query, update, function(error, res) {
|
|
|
|
if (error != null) {
|
2019-07-01 09:48:09 -04:00
|
|
|
logger.warn({ error }, 'problem removing users email')
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (res.n === 0) {
|
|
|
|
return callback(new Error('Cannot remove email'))
|
|
|
|
}
|
|
|
|
return callback()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
// set the default email address by setting the `email` attribute. The email
|
|
|
|
// must be one of the user's multiple emails (`emails` attribute)
|
|
|
|
setDefaultEmailAddress(userId, email, callback) {
|
|
|
|
email = EmailHelper.parseEmail(email)
|
|
|
|
if (email == null) {
|
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
|
|
|
return UserGetter.getUserEmail(userId, (error, oldEmail) => {
|
|
|
|
if (typeof err !== 'undefined' && err !== null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
const query = { _id: userId, 'emails.email': email }
|
|
|
|
const update = { $set: { email } }
|
|
|
|
return this.updateUser(query, update, function(error, res) {
|
|
|
|
if (error != null) {
|
2019-07-01 09:48:09 -04:00
|
|
|
logger.warn({ error }, 'problem setting default emails')
|
2019-05-29 05:21:06 -04:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
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) {
|
2019-07-15 09:09:04 -04:00
|
|
|
if (!Settings.apis.v1.url) {
|
2019-05-29 05:21:06 -04:00
|
|
|
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) {
|
|
|
|
if (arguments.length === 3) {
|
|
|
|
callback = confirmedAt
|
|
|
|
confirmedAt = new Date()
|
|
|
|
}
|
|
|
|
email = EmailHelper.parseEmail(email)
|
|
|
|
if (email == null) {
|
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
|
|
|
logger.log({ userId, email }, 'confirming user email')
|
|
|
|
return addAffiliation(userId, email, { confirmedAt }, error => {
|
|
|
|
if (error != null) {
|
2019-07-01 09:48:09 -04:00
|
|
|
logger.warn(
|
2019-05-29 05:21:06 -04:00
|
|
|
{ error },
|
|
|
|
'problem adding affiliation while confirming email'
|
|
|
|
)
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
const query = {
|
|
|
|
_id: userId,
|
|
|
|
'emails.email': email
|
|
|
|
}
|
|
|
|
const update = {
|
|
|
|
$set: {
|
|
|
|
'emails.$.confirmedAt': confirmedAt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.updateUser(query, update, function(error, res) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
logger.log({ res, userId, email }, 'tried to confirm email')
|
|
|
|
if (res.n === 0) {
|
|
|
|
return callback(
|
|
|
|
new Errors.NotFoundError('user id and email do no match')
|
|
|
|
)
|
|
|
|
}
|
2019-06-11 09:14:38 -04:00
|
|
|
return FeaturesUpdater.refreshFeatures(userId, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
removeReconfirmFlag(user_id, callback) {
|
|
|
|
return UserUpdater.updateUser(
|
|
|
|
user_id.toString(),
|
|
|
|
{
|
|
|
|
$set: { must_reconfirm: false }
|
|
|
|
},
|
|
|
|
error => callback(error)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
;[
|
|
|
|
'updateUser',
|
|
|
|
'changeEmailAddress',
|
|
|
|
'setDefaultEmailAddress',
|
|
|
|
'addEmailAddress',
|
|
|
|
'removeEmailAddress',
|
|
|
|
'removeReconfirmFlag'
|
|
|
|
].map(method =>
|
|
|
|
metrics.timeAsyncMethod(UserUpdater, method, 'mongo.UserUpdater', logger)
|
|
|
|
)
|