UserUpdater.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. const logger = require('logger-sharelatex')
  2. const mongojs = require('../../infrastructure/mongojs')
  3. const metrics = require('metrics-sharelatex')
  4. const { db } = mongojs
  5. const async = require('async')
  6. const { ObjectId } = mongojs
  7. const { promisify } = require('util')
  8. const UserGetter = require('./UserGetter')
  9. const {
  10. addAffiliation,
  11. removeAffiliation
  12. } = require('../Institutions/InstitutionsAPI')
  13. const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
  14. const EmailHelper = require('../Helpers/EmailHelper')
  15. const Errors = require('../Errors/Errors')
  16. const NewsletterManager = require('../Newsletter/NewsletterManager')
  17. const UserUpdater = {
  18. updateUser(query, update, callback) {
  19. if (callback == null) {
  20. callback = () => {}
  21. }
  22. if (typeof query === 'string') {
  23. query = { _id: ObjectId(query) }
  24. } else if (query instanceof ObjectId) {
  25. query = { _id: query }
  26. } else if (typeof query._id === 'string') {
  27. query._id = ObjectId(query._id)
  28. }
  29. db.users.update(query, update, callback)
  30. },
  31. //
  32. // DEPRECATED
  33. //
  34. // Change the user's main email address by adding a new email, switching the
  35. // default email and removing the old email. Prefer manipulating multiple
  36. // emails and the default rather than calling this method directly
  37. //
  38. changeEmailAddress(userId, newEmail, callback) {
  39. newEmail = EmailHelper.parseEmail(newEmail)
  40. if (newEmail == null) {
  41. return callback(new Error('invalid email'))
  42. }
  43. let oldEmail = null
  44. async.series(
  45. [
  46. cb =>
  47. UserGetter.getUserEmail(userId, (error, email) => {
  48. oldEmail = email
  49. cb(error)
  50. }),
  51. cb => UserUpdater.addEmailAddress(userId, newEmail, cb),
  52. cb => UserUpdater.setDefaultEmailAddress(userId, newEmail, true, cb),
  53. cb => UserUpdater.removeEmailAddress(userId, oldEmail, cb)
  54. ],
  55. callback
  56. )
  57. },
  58. // Add a new email address for the user. Email cannot be already used by this
  59. // or any other user
  60. addEmailAddress(userId, newEmail, affiliationOptions, callback) {
  61. if (callback == null) {
  62. // affiliationOptions is optional
  63. callback = affiliationOptions
  64. affiliationOptions = {}
  65. }
  66. newEmail = EmailHelper.parseEmail(newEmail)
  67. if (newEmail == null) {
  68. return callback(new Error('invalid email'))
  69. }
  70. UserGetter.ensureUniqueEmailAddress(newEmail, error => {
  71. if (error != null) {
  72. return callback(error)
  73. }
  74. addAffiliation(userId, newEmail, affiliationOptions, error => {
  75. if (error != null) {
  76. logger.warn(
  77. { error },
  78. 'problem adding affiliation while adding email'
  79. )
  80. return callback(error)
  81. }
  82. const reversedHostname = newEmail
  83. .split('@')[1]
  84. .split('')
  85. .reverse()
  86. .join('')
  87. const update = {
  88. $push: {
  89. emails: { email: newEmail, createdAt: new Date(), reversedHostname }
  90. }
  91. }
  92. UserUpdater.updateUser(userId, update, error => {
  93. if (error != null) {
  94. logger.warn({ error }, 'problem updating users emails')
  95. return callback(error)
  96. }
  97. callback()
  98. })
  99. })
  100. })
  101. },
  102. // remove one of the user's email addresses. The email cannot be the user's
  103. // default email address
  104. removeEmailAddress(userId, email, callback) {
  105. email = EmailHelper.parseEmail(email)
  106. if (email == null) {
  107. return callback(new Error('invalid email'))
  108. }
  109. removeAffiliation(userId, email, error => {
  110. if (error != null) {
  111. logger.warn({ error }, 'problem removing affiliation')
  112. return callback(error)
  113. }
  114. const query = { _id: userId, email: { $ne: email } }
  115. const update = { $pull: { emails: { email } } }
  116. UserUpdater.updateUser(query, update, (error, res) => {
  117. if (error != null) {
  118. logger.warn({ error }, 'problem removing users email')
  119. return callback(error)
  120. }
  121. if (res.n === 0) {
  122. return callback(new Error('Cannot remove email'))
  123. }
  124. callback()
  125. })
  126. })
  127. },
  128. // set the default email address by setting the `email` attribute. The email
  129. // must be one of the user's multiple emails (`emails` attribute)
  130. setDefaultEmailAddress(userId, email, allowUnconfirmed, callback) {
  131. if (typeof allowUnconfirmed === 'function') {
  132. callback = allowUnconfirmed
  133. allowUnconfirmed = false
  134. }
  135. email = EmailHelper.parseEmail(email)
  136. if (email == null) {
  137. return callback(new Error('invalid email'))
  138. }
  139. UserGetter.getUser(userId, { email: 1, emails: 1 }, (err, user) => {
  140. if (err) {
  141. return callback(err)
  142. }
  143. if (!user) {
  144. return callback(new Error('invalid userId'))
  145. }
  146. const oldEmail = user.email
  147. const userEmail = user.emails.find(e => e.email === email)
  148. if (!userEmail) {
  149. return callback(new Error('Default email does not belong to user'))
  150. }
  151. if (!userEmail.confirmedAt && !allowUnconfirmed) {
  152. return callback(new Errors.UnconfirmedEmailError())
  153. }
  154. const query = { _id: userId, 'emails.email': email }
  155. const update = { $set: { email } }
  156. UserUpdater.updateUser(query, update, (err, res) => {
  157. if (err) {
  158. return callback(err)
  159. }
  160. // this should not happen
  161. if (res.n === 0) {
  162. return callback(new Error('email update error'))
  163. }
  164. NewsletterManager.changeEmail(user, email, err => {
  165. if (err != null) {
  166. logger.warn(
  167. { err, oldEmail, newEmail: email },
  168. 'Failed to change email in newsletter subscription'
  169. )
  170. }
  171. })
  172. callback()
  173. })
  174. })
  175. },
  176. confirmEmail(userId, email, confirmedAt, callback) {
  177. if (arguments.length === 3) {
  178. callback = confirmedAt
  179. confirmedAt = new Date()
  180. }
  181. email = EmailHelper.parseEmail(email)
  182. if (email == null) {
  183. return callback(new Error('invalid email'))
  184. }
  185. logger.log({ userId, email }, 'confirming user email')
  186. addAffiliation(userId, email, { confirmedAt }, error => {
  187. if (error != null) {
  188. logger.warn(
  189. { error },
  190. 'problem adding affiliation while confirming email'
  191. )
  192. return callback(error)
  193. }
  194. const query = {
  195. _id: userId,
  196. 'emails.email': email
  197. }
  198. const update = {
  199. $set: {
  200. 'emails.$.confirmedAt': confirmedAt
  201. }
  202. }
  203. UserUpdater.updateUser(query, update, (error, res) => {
  204. if (error != null) {
  205. return callback(error)
  206. }
  207. if (res.n === 0) {
  208. return callback(
  209. new Errors.NotFoundError('user id and email do no match')
  210. )
  211. }
  212. FeaturesUpdater.refreshFeatures(userId, callback)
  213. })
  214. })
  215. },
  216. removeReconfirmFlag(userId, callback) {
  217. UserUpdater.updateUser(
  218. userId.toString(),
  219. {
  220. $set: { must_reconfirm: false }
  221. },
  222. error => callback(error)
  223. )
  224. }
  225. }
  226. ;[
  227. 'updateUser',
  228. 'changeEmailAddress',
  229. 'setDefaultEmailAddress',
  230. 'addEmailAddress',
  231. 'removeEmailAddress',
  232. 'removeReconfirmFlag'
  233. ].map(method =>
  234. metrics.timeAsyncMethod(UserUpdater, method, 'mongo.UserUpdater', logger)
  235. )
  236. const promises = {
  237. addEmailAddress: promisify(UserUpdater.addEmailAddress),
  238. confirmEmail: promisify(UserUpdater.confirmEmail),
  239. updateUser: promisify(UserUpdater.updateUser)
  240. }
  241. UserUpdater.promises = promises
  242. module.exports = UserUpdater