UserController.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. const UserHandler = require('./UserHandler')
  2. const UserDeleter = require('./UserDeleter')
  3. const UserGetter = require('./UserGetter')
  4. const { User } = require('../../models/User')
  5. const NewsletterManager = require('../Newsletter/NewsletterManager')
  6. const UserRegistrationHandler = require('./UserRegistrationHandler')
  7. const logger = require('logger-sharelatex')
  8. const metrics = require('metrics-sharelatex')
  9. const AuthenticationManager = require('../Authentication/AuthenticationManager')
  10. const AuthenticationController = require('../Authentication/AuthenticationController')
  11. const Features = require('../../infrastructure/Features')
  12. const UserAuditLogHandler = require('./UserAuditLogHandler')
  13. const UserSessionsManager = require('./UserSessionsManager')
  14. const UserUpdater = require('./UserUpdater')
  15. const SudoModeHandler = require('../SudoMode/SudoModeHandler')
  16. const Errors = require('../Errors/Errors')
  17. const HttpErrorHandler = require('../Errors/HttpErrorHandler')
  18. const OError = require('@overleaf/o-error')
  19. const EmailHandler = require('../Email/EmailHandler')
  20. const UrlHelper = require('../Helpers/UrlHelper')
  21. const { promisify } = require('util')
  22. const { expressify } = require('../../util/promises')
  23. async function _ensureAffiliation(userId, emailData) {
  24. if (emailData.samlProviderId) {
  25. await UserUpdater.promises.confirmEmail(userId, emailData.email)
  26. } else {
  27. await UserUpdater.promises.addAffiliationForNewUser(userId, emailData.email)
  28. }
  29. }
  30. async function changePassword(req, res, next) {
  31. metrics.inc('user.password-change')
  32. const userId = AuthenticationController.getLoggedInUserId(req)
  33. const user = await AuthenticationManager.promises.authenticate(
  34. { _id: userId },
  35. req.body.currentPassword
  36. )
  37. if (!user) {
  38. return HttpErrorHandler.badRequest(req, res, 'Your old password is wrong')
  39. }
  40. if (req.body.newPassword1 !== req.body.newPassword2) {
  41. return HttpErrorHandler.badRequest(
  42. req,
  43. res,
  44. req.i18n.translate('password_change_passwords_do_not_match')
  45. )
  46. }
  47. const validationError = AuthenticationManager.validatePassword(
  48. req.body.newPassword1
  49. )
  50. if (validationError != null) {
  51. return HttpErrorHandler.badRequest(req, res, validationError.message)
  52. }
  53. await UserAuditLogHandler.promises.addEntry(
  54. user._id,
  55. 'update-password',
  56. user._id,
  57. req.ip
  58. )
  59. await AuthenticationManager.promises.setUserPassword(
  60. user._id,
  61. req.body.newPassword1
  62. )
  63. const emailOptions = {
  64. to: user.email,
  65. actionDescribed: `your password has been changed on your account ${
  66. user.email
  67. }`,
  68. action: 'password changed'
  69. }
  70. EmailHandler.sendEmail('securityAlert', emailOptions, error => {
  71. if (error) {
  72. // log error when sending security alert email but do not pass back
  73. logger.error({ err: error })
  74. }
  75. })
  76. await UserSessionsManager.promises.revokeAllUserSessions(user, [
  77. req.sessionID
  78. ])
  79. return res.json({
  80. message: {
  81. type: 'success',
  82. email: user.email,
  83. text: req.i18n.translate('password_change_successful')
  84. }
  85. })
  86. }
  87. async function clearSessions(req, res, next) {
  88. metrics.inc('user.clear-sessions')
  89. const user = AuthenticationController.getSessionUser(req)
  90. const sessions = await UserSessionsManager.promises.getAllUserSessions(user, [
  91. req.sessionID
  92. ])
  93. await UserAuditLogHandler.promises.addEntry(
  94. user._id,
  95. 'clear-sessions',
  96. user._id,
  97. req.ip,
  98. { sessions }
  99. )
  100. await UserSessionsManager.promises.revokeAllUserSessions(user, [
  101. req.sessionID
  102. ])
  103. const emailOptions = {
  104. to: user.email,
  105. actionDescribed: `active sessions were cleared on your account ${
  106. user.email
  107. }`,
  108. action: 'active sessions cleared'
  109. }
  110. try {
  111. await EmailHandler.promises.sendEmail('securityAlert', emailOptions)
  112. } catch (error) {
  113. logger.error(
  114. { userId: user._id },
  115. 'could not send security alert email when sessions cleared'
  116. )
  117. }
  118. res.sendStatus(201)
  119. }
  120. async function ensureAffiliation(user) {
  121. if (!Features.hasFeature('affiliations')) {
  122. return
  123. }
  124. const flaggedEmails = user.emails.filter(email => email.affiliationUnchecked)
  125. if (flaggedEmails.length === 0) {
  126. return
  127. }
  128. if (flaggedEmails.length > 1) {
  129. logger.error(
  130. { userId: user._id },
  131. `Unexpected number of flagged emails: ${flaggedEmails.length}`
  132. )
  133. }
  134. await _ensureAffiliation(user._id, flaggedEmails[0])
  135. }
  136. async function ensureAffiliationMiddleware(req, res, next) {
  137. let user
  138. if (!Features.hasFeature('affiliations') || !req.query.ensureAffiliation) {
  139. return next()
  140. }
  141. const userId = AuthenticationController.getLoggedInUserId(req)
  142. try {
  143. user = await UserGetter.promises.getUser(userId)
  144. } catch (error) {
  145. return new Errors.UserNotFoundError({ info: { userId } })
  146. }
  147. try {
  148. await ensureAffiliation(user)
  149. } catch (error) {
  150. return next(error)
  151. }
  152. return next()
  153. }
  154. const UserController = {
  155. clearSessions: expressify(clearSessions),
  156. tryDeleteUser(req, res, next) {
  157. const userId = AuthenticationController.getLoggedInUserId(req)
  158. const { password } = req.body
  159. if (password == null || password === '') {
  160. logger.err(
  161. { userId },
  162. 'no password supplied for attempt to delete account'
  163. )
  164. return res.sendStatus(403)
  165. }
  166. AuthenticationManager.authenticate(
  167. { _id: userId },
  168. password,
  169. (err, user) => {
  170. if (err != null) {
  171. logger.warn(
  172. { userId },
  173. 'error authenticating during attempt to delete account'
  174. )
  175. return next(err)
  176. }
  177. if (!user) {
  178. logger.err({ userId }, 'auth failed during attempt to delete account')
  179. return res.sendStatus(403)
  180. }
  181. UserDeleter.deleteUser(
  182. userId,
  183. { deleterUser: user, ipAddress: req.ip },
  184. err => {
  185. if (err) {
  186. let errorData = {
  187. message: 'error while deleting user account',
  188. info: { userId }
  189. }
  190. if (err instanceof Errors.SubscriptionAdminDeletionError) {
  191. // set info.public.error for JSON response so frontend can display
  192. // a specific message
  193. errorData.info.public = {
  194. error: 'SubscriptionAdminDeletionError'
  195. }
  196. logger.warn(OError.tag(err, errorData.message, errorData.info))
  197. return HttpErrorHandler.unprocessableEntity(
  198. req,
  199. res,
  200. errorData.message,
  201. errorData.info.public
  202. )
  203. } else {
  204. return next(OError.tag(err, errorData.message, errorData.info))
  205. }
  206. }
  207. const sessionId = req.sessionID
  208. if (typeof req.logout === 'function') {
  209. req.logout()
  210. }
  211. req.session.destroy(err => {
  212. if (err != null) {
  213. logger.warn({ err }, 'error destorying session')
  214. return next(err)
  215. }
  216. UserSessionsManager.untrackSession(user, sessionId)
  217. res.sendStatus(200)
  218. })
  219. }
  220. )
  221. }
  222. )
  223. },
  224. unsubscribe(req, res, next) {
  225. const userId = AuthenticationController.getLoggedInUserId(req)
  226. UserGetter.getUser(userId, (err, user) => {
  227. if (err != null) {
  228. return next(err)
  229. }
  230. NewsletterManager.unsubscribe(user, err => {
  231. if (err != null) {
  232. logger.warn(
  233. { err, user },
  234. 'Failed to unsubscribe user from newsletter'
  235. )
  236. }
  237. res.sendStatus(200)
  238. })
  239. })
  240. },
  241. updateUserSettings(req, res, next) {
  242. const userId = AuthenticationController.getLoggedInUserId(req)
  243. User.findById(userId, (err, user) => {
  244. if (err != null || user == null) {
  245. logger.err({ err, userId }, 'problem updaing user settings')
  246. return res.sendStatus(500)
  247. }
  248. if (req.body.first_name != null) {
  249. user.first_name = req.body.first_name.trim()
  250. }
  251. if (req.body.last_name != null) {
  252. user.last_name = req.body.last_name.trim()
  253. }
  254. if (req.body.role != null) {
  255. user.role = req.body.role.trim()
  256. }
  257. if (req.body.institution != null) {
  258. user.institution = req.body.institution.trim()
  259. }
  260. if (req.body.mode != null) {
  261. user.ace.mode = req.body.mode
  262. }
  263. if (req.body.editorTheme != null) {
  264. user.ace.theme = req.body.editorTheme
  265. }
  266. if (req.body.overallTheme != null) {
  267. user.ace.overallTheme = req.body.overallTheme
  268. }
  269. if (req.body.fontSize != null) {
  270. user.ace.fontSize = req.body.fontSize
  271. }
  272. if (req.body.autoComplete != null) {
  273. user.ace.autoComplete = req.body.autoComplete
  274. }
  275. if (req.body.autoPairDelimiters != null) {
  276. user.ace.autoPairDelimiters = req.body.autoPairDelimiters
  277. }
  278. if (req.body.spellCheckLanguage != null) {
  279. user.ace.spellCheckLanguage = req.body.spellCheckLanguage
  280. }
  281. if (req.body.pdfViewer != null) {
  282. user.ace.pdfViewer = req.body.pdfViewer
  283. }
  284. if (req.body.syntaxValidation != null) {
  285. user.ace.syntaxValidation = req.body.syntaxValidation
  286. }
  287. if (req.body.fontFamily != null) {
  288. user.ace.fontFamily = req.body.fontFamily
  289. }
  290. if (req.body.lineHeight != null) {
  291. user.ace.lineHeight = req.body.lineHeight
  292. }
  293. user.save(err => {
  294. if (err != null) {
  295. return next(err)
  296. }
  297. const newEmail =
  298. req.body.email != null
  299. ? req.body.email.trim().toLowerCase()
  300. : undefined
  301. if (
  302. newEmail == null ||
  303. newEmail === user.email ||
  304. req.externalAuthenticationSystemUsed()
  305. ) {
  306. // end here, don't update email
  307. AuthenticationController.setInSessionUser(req, {
  308. first_name: user.first_name,
  309. last_name: user.last_name
  310. })
  311. res.sendStatus(200)
  312. } else if (newEmail.indexOf('@') === -1) {
  313. // email invalid
  314. res.sendStatus(400)
  315. } else {
  316. // update the user email
  317. UserUpdater.changeEmailAddress(userId, newEmail, err => {
  318. if (err) {
  319. if (err instanceof Errors.EmailExistsError) {
  320. const translation = req.i18n.translate(
  321. 'email_already_registered'
  322. )
  323. return HttpErrorHandler.conflict(req, res, translation)
  324. } else {
  325. return HttpErrorHandler.legacyInternal(
  326. req,
  327. res,
  328. req.i18n.translate('problem_changing_email_address'),
  329. OError.tag(err, 'problem_changing_email_address', {
  330. userId,
  331. newEmail
  332. })
  333. )
  334. }
  335. }
  336. User.findById(userId, (err, user) => {
  337. if (err != null) {
  338. logger.err(
  339. { err, userId },
  340. 'error getting user for email update'
  341. )
  342. return res.sendStatus(500)
  343. }
  344. AuthenticationController.setInSessionUser(req, {
  345. email: user.email,
  346. first_name: user.first_name,
  347. last_name: user.last_name
  348. })
  349. UserHandler.populateTeamInvites(user, err => {
  350. // need to refresh this in the background
  351. if (err != null) {
  352. logger.err({ err }, 'error populateTeamInvites')
  353. }
  354. res.sendStatus(200)
  355. })
  356. })
  357. })
  358. }
  359. })
  360. })
  361. },
  362. doLogout(req, cb) {
  363. metrics.inc('user.logout')
  364. const user = AuthenticationController.getSessionUser(req)
  365. logger.log({ user }, 'logging out')
  366. const sessionId = req.sessionID
  367. if (typeof req.logout === 'function') {
  368. req.logout()
  369. } // passport logout
  370. req.session.destroy(err => {
  371. if (err) {
  372. logger.warn({ err }, 'error destorying session')
  373. return cb(err)
  374. }
  375. if (user != null) {
  376. UserSessionsManager.untrackSession(user, sessionId)
  377. SudoModeHandler.clearSudoMode(user._id)
  378. }
  379. cb()
  380. })
  381. },
  382. logout(req, res, next) {
  383. const requestedRedirect = req.body.redirect
  384. ? UrlHelper.getSafeRedirectPath(req.body.redirect)
  385. : undefined
  386. const redirectUrl = requestedRedirect || '/login'
  387. UserController.doLogout(req, err => {
  388. if (err != null) {
  389. return next(err)
  390. }
  391. res.redirect(redirectUrl)
  392. })
  393. },
  394. expireDeletedUser(req, res, next) {
  395. const userId = req.params.userId
  396. UserDeleter.expireDeletedUser(userId, error => {
  397. if (error) {
  398. return next(error)
  399. }
  400. res.sendStatus(204)
  401. })
  402. },
  403. expireDeletedUsersAfterDuration(req, res, next) {
  404. UserDeleter.expireDeletedUsersAfterDuration(error => {
  405. if (error) {
  406. return next(error)
  407. }
  408. res.sendStatus(204)
  409. })
  410. },
  411. register(req, res, next) {
  412. const { email } = req.body
  413. if (email == null || email === '') {
  414. return res.sendStatus(422) // Unprocessable Entity
  415. }
  416. UserRegistrationHandler.registerNewUserAndSendActivationEmail(
  417. email,
  418. (error, user, setNewPasswordUrl) => {
  419. if (error != null) {
  420. return next(error)
  421. }
  422. res.json({
  423. email: user.email,
  424. setNewPasswordUrl
  425. })
  426. }
  427. )
  428. },
  429. changePassword: expressify(changePassword)
  430. }
  431. UserController.promises = {
  432. doLogout: promisify(UserController.doLogout),
  433. ensureAffiliation,
  434. ensureAffiliationMiddleware
  435. }
  436. module.exports = UserController