UserControllerTests.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. /* eslint-disable
  2. max-len,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const sinon = require('sinon')
  14. const chai = require('chai')
  15. const should = chai.should()
  16. const { expect } = chai
  17. const modulePath = '../../../../app/src/Features/User/UserController.js'
  18. const SandboxedModule = require('sandboxed-module')
  19. const events = require('events')
  20. const MockResponse = require('../helpers/MockResponse')
  21. const MockRequest = require('../helpers/MockRequest')
  22. const { ObjectId } = require('mongojs')
  23. const assert = require('assert')
  24. const OError = require('@overleaf/o-error')
  25. const Errors = require('../../../../app/src/Features/Errors/Errors')
  26. const HttpErrors = require('@overleaf/o-error/http')
  27. describe('UserController', function() {
  28. beforeEach(function() {
  29. this.user_id = '323123'
  30. this.user = {
  31. _id: this.user_id,
  32. save: sinon.stub().callsArgWith(0),
  33. ace: {}
  34. }
  35. this.req = {
  36. user: {},
  37. session: {
  38. destroy() {},
  39. user: {
  40. _id: this.user_id,
  41. email: 'old@something.com'
  42. }
  43. },
  44. body: {},
  45. i18n: {
  46. translate: text => text
  47. }
  48. }
  49. this.UserDeleter = { deleteUser: sinon.stub().yields() }
  50. this.UserGetter = { getUser: sinon.stub().callsArgWith(1, null, this.user) }
  51. this.User = { findById: sinon.stub().callsArgWith(1, null, this.user) }
  52. this.NewsLetterManager = { unsubscribe: sinon.stub().callsArgWith(1) }
  53. this.UserRegistrationHandler = { registerNewUser: sinon.stub() }
  54. this.AuthenticationController = {
  55. establishUserSession: sinon.stub().callsArg(2),
  56. getLoggedInUserId: sinon.stub().returns(this.user._id),
  57. getSessionUser: sinon.stub().returns(this.req.session.user),
  58. setInSessionUser: sinon.stub()
  59. }
  60. this.AuthenticationManager = {
  61. authenticate: sinon.stub(),
  62. setUserPassword: sinon.stub(),
  63. validatePassword: sinon.stub()
  64. }
  65. this.ReferalAllocator = { allocate: sinon.stub() }
  66. this.SubscriptionDomainHandler = { autoAllocate: sinon.stub() }
  67. this.UserUpdater = { changeEmailAddress: sinon.stub() }
  68. this.settings = { siteUrl: 'sharelatex.example.com' }
  69. this.UserHandler = { populateTeamInvites: sinon.stub().callsArgWith(1) }
  70. this.UserSessionsManager = {
  71. trackSession: sinon.stub(),
  72. untrackSession: sinon.stub(),
  73. revokeAllUserSessions: sinon.stub().callsArgWith(2, null)
  74. }
  75. this.SudoModeHandler = { clearSudoMode: sinon.stub() }
  76. this.UserController = SandboxedModule.require(modulePath, {
  77. globals: {
  78. console: console
  79. },
  80. requires: {
  81. './UserGetter': this.UserGetter,
  82. './UserDeleter': this.UserDeleter,
  83. './UserUpdater': this.UserUpdater,
  84. '../../models/User': {
  85. User: this.User
  86. },
  87. '../Newsletter/NewsletterManager': this.NewsLetterManager,
  88. './UserRegistrationHandler': this.UserRegistrationHandler,
  89. '../Authentication/AuthenticationController': this
  90. .AuthenticationController,
  91. '../Authentication/AuthenticationManager': this.AuthenticationManager,
  92. '../Referal/ReferalAllocator': this.ReferalAllocator,
  93. '../Subscription/SubscriptionDomainHandler': this
  94. .SubscriptionDomainHandler,
  95. './UserHandler': this.UserHandler,
  96. './UserSessionsManager': this.UserSessionsManager,
  97. '../SudoMode/SudoModeHandler': this.SudoModeHandler,
  98. 'settings-sharelatex': this.settings,
  99. 'logger-sharelatex': {
  100. log() {},
  101. warn() {},
  102. err() {}
  103. },
  104. 'metrics-sharelatex': {
  105. inc() {}
  106. },
  107. '../Errors/Errors': Errors,
  108. '@overleaf/o-error/http': HttpErrors,
  109. '../Email/EmailHandler': { sendEmail: sinon.stub() }
  110. }
  111. })
  112. this.res = {
  113. send: sinon.stub(),
  114. status: sinon.stub(),
  115. sendStatus: sinon.stub(),
  116. json: sinon.stub()
  117. }
  118. this.res.status.returns(this.res)
  119. this.next = sinon.stub()
  120. this.callback = sinon.stub()
  121. })
  122. describe('tryDeleteUser', function() {
  123. beforeEach(function() {
  124. this.req.body.password = 'wat'
  125. this.req.logout = sinon.stub()
  126. this.req.session.destroy = sinon.stub().callsArgWith(0, null)
  127. this.AuthenticationController.getLoggedInUserId = sinon
  128. .stub()
  129. .returns(this.user._id)
  130. this.AuthenticationManager.authenticate = sinon
  131. .stub()
  132. .callsArgWith(2, null, this.user)
  133. })
  134. it('should send 200', function(done) {
  135. this.res.sendStatus = code => {
  136. code.should.equal(200)
  137. return done()
  138. }
  139. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  140. })
  141. it('should try to authenticate user', function(done) {
  142. this.res.sendStatus = code => {
  143. this.AuthenticationManager.authenticate.callCount.should.equal(1)
  144. this.AuthenticationManager.authenticate
  145. .calledWith({ _id: this.user._id }, this.req.body.password)
  146. .should.equal(true)
  147. return done()
  148. }
  149. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  150. })
  151. it('should delete the user', function(done) {
  152. this.res.sendStatus = code => {
  153. this.UserDeleter.deleteUser.callCount.should.equal(1)
  154. this.UserDeleter.deleteUser.calledWith(this.user._id).should.equal(true)
  155. return done()
  156. }
  157. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  158. })
  159. describe('when no password is supplied', function() {
  160. beforeEach(function() {
  161. return (this.req.body.password = '')
  162. })
  163. it('should return 403', function(done) {
  164. this.res.sendStatus = code => {
  165. code.should.equal(403)
  166. return done()
  167. }
  168. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  169. })
  170. })
  171. describe('when authenticate produces an error', function() {
  172. beforeEach(function() {
  173. return (this.AuthenticationManager.authenticate = sinon
  174. .stub()
  175. .callsArgWith(2, new Error('woops')))
  176. })
  177. it('should call next with an error', function(done) {
  178. this.next = err => {
  179. expect(err).to.not.equal(null)
  180. expect(err).to.be.instanceof(Error)
  181. return done()
  182. }
  183. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  184. })
  185. })
  186. describe('when authenticate does not produce a user', function() {
  187. beforeEach(function() {
  188. return (this.AuthenticationManager.authenticate = sinon
  189. .stub()
  190. .callsArgWith(2, null, null))
  191. })
  192. it('should return 403', function(done) {
  193. this.res.sendStatus = code => {
  194. code.should.equal(403)
  195. return done()
  196. }
  197. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  198. })
  199. })
  200. describe('when deleteUser produces an error', function() {
  201. beforeEach(function() {
  202. return (this.UserDeleter.deleteUser = sinon
  203. .stub()
  204. .yields(new Error('woops')))
  205. })
  206. it('should call next with an error', function(done) {
  207. this.next = err => {
  208. expect(err).to.not.equal(null)
  209. expect(err).to.be.instanceof(Error)
  210. return done()
  211. }
  212. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  213. })
  214. })
  215. describe('when deleteUser produces a known error', function() {
  216. beforeEach(function() {
  217. return (this.UserDeleter.deleteUser = sinon
  218. .stub()
  219. .yields(new Errors.SubscriptionAdminDeletionError()))
  220. })
  221. it('should return a json error', function(done) {
  222. this.UserController.tryDeleteUser(this.req, null, error => {
  223. expect(error).to.be.instanceof(HttpErrors.UnprocessableEntityError)
  224. expect(
  225. OError.hasCauseInstanceOf(
  226. error,
  227. Errors.SubscriptionAdminDeletionError
  228. )
  229. ).to.be.true
  230. done()
  231. })
  232. })
  233. })
  234. describe('when session.destroy produces an error', function() {
  235. beforeEach(function() {
  236. return (this.req.session.destroy = sinon
  237. .stub()
  238. .callsArgWith(0, new Error('woops')))
  239. })
  240. it('should call next with an error', function(done) {
  241. this.next = err => {
  242. expect(err).to.not.equal(null)
  243. expect(err).to.be.instanceof(Error)
  244. return done()
  245. }
  246. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  247. })
  248. })
  249. })
  250. describe('unsubscribe', function() {
  251. it('should send the user to unsubscribe', function(done) {
  252. this.res.send = code => {
  253. this.NewsLetterManager.unsubscribe
  254. .calledWith(this.user)
  255. .should.equal(true)
  256. return done()
  257. }
  258. return this.UserController.unsubscribe(this.req, this.res)
  259. })
  260. })
  261. describe('updateUserSettings', function() {
  262. beforeEach(function() {
  263. this.newEmail = 'hello@world.com'
  264. return (this.req.externalAuthenticationSystemUsed = sinon
  265. .stub()
  266. .returns(false))
  267. })
  268. it('should call save', function(done) {
  269. this.req.body = {}
  270. this.res.sendStatus = code => {
  271. this.user.save.called.should.equal(true)
  272. return done()
  273. }
  274. return this.UserController.updateUserSettings(this.req, this.res)
  275. })
  276. it('should set the first name', function(done) {
  277. this.req.body = { first_name: 'bobby ' }
  278. this.res.sendStatus = code => {
  279. this.user.first_name.should.equal('bobby')
  280. return done()
  281. }
  282. return this.UserController.updateUserSettings(this.req, this.res)
  283. })
  284. it('should set the role', function(done) {
  285. this.req.body = { role: 'student' }
  286. this.res.sendStatus = code => {
  287. this.user.role.should.equal('student')
  288. return done()
  289. }
  290. return this.UserController.updateUserSettings(this.req, this.res)
  291. })
  292. it('should set the institution', function(done) {
  293. this.req.body = { institution: 'MIT' }
  294. this.res.sendStatus = code => {
  295. this.user.institution.should.equal('MIT')
  296. return done()
  297. }
  298. return this.UserController.updateUserSettings(this.req, this.res)
  299. })
  300. it('should set some props on ace', function(done) {
  301. this.req.body = { editorTheme: 'something' }
  302. this.res.sendStatus = code => {
  303. this.user.ace.theme.should.equal('something')
  304. return done()
  305. }
  306. return this.UserController.updateUserSettings(this.req, this.res)
  307. })
  308. it('should set the overall theme', function(done) {
  309. this.req.body = { overallTheme: 'green-ish' }
  310. this.res.sendStatus = code => {
  311. this.user.ace.overallTheme.should.equal('green-ish')
  312. return done()
  313. }
  314. return this.UserController.updateUserSettings(this.req, this.res)
  315. })
  316. it('should send an error if the email is 0 len', function(done) {
  317. this.req.body.email = ''
  318. this.res.sendStatus = function(code) {
  319. code.should.equal(400)
  320. return done()
  321. }
  322. return this.UserController.updateUserSettings(this.req, this.res)
  323. })
  324. it('should send an error if the email does not contain an @', function(done) {
  325. this.req.body.email = 'bob at something dot com'
  326. this.res.sendStatus = function(code) {
  327. code.should.equal(400)
  328. return done()
  329. }
  330. return this.UserController.updateUserSettings(this.req, this.res)
  331. })
  332. it('should call the user updater with the new email and user _id', function(done) {
  333. this.req.body.email = this.newEmail.toUpperCase()
  334. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  335. this.res.sendStatus = code => {
  336. code.should.equal(200)
  337. this.UserUpdater.changeEmailAddress
  338. .calledWith(this.user_id, this.newEmail)
  339. .should.equal(true)
  340. return done()
  341. }
  342. return this.UserController.updateUserSettings(this.req, this.res)
  343. })
  344. it('should update the email on the session', function(done) {
  345. this.req.body.email = this.newEmail.toUpperCase()
  346. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  347. let callcount = 0
  348. this.User.findById = (id, cb) => {
  349. if (++callcount === 2) {
  350. this.user.email = this.newEmail
  351. }
  352. return cb(null, this.user)
  353. }
  354. this.res.sendStatus = code => {
  355. code.should.equal(200)
  356. this.AuthenticationController.setInSessionUser
  357. .calledWith(this.req, {
  358. email: this.newEmail,
  359. first_name: undefined,
  360. last_name: undefined
  361. })
  362. .should.equal(true)
  363. return done()
  364. }
  365. return this.UserController.updateUserSettings(this.req, this.res)
  366. })
  367. it('should call populateTeamInvites', function(done) {
  368. this.req.body.email = this.newEmail.toUpperCase()
  369. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  370. this.res.sendStatus = code => {
  371. code.should.equal(200)
  372. this.UserHandler.populateTeamInvites
  373. .calledWith(this.user)
  374. .should.equal(true)
  375. return done()
  376. }
  377. return this.UserController.updateUserSettings(this.req, this.res)
  378. })
  379. describe('when using an external auth source', function() {
  380. beforeEach(function() {
  381. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  382. this.newEmail = 'someone23@example.com'
  383. return (this.req.externalAuthenticationSystemUsed = sinon
  384. .stub()
  385. .returns(true))
  386. })
  387. it('should not set a new email', function(done) {
  388. this.req.body.email = this.newEmail
  389. this.res.sendStatus = code => {
  390. code.should.equal(200)
  391. this.UserUpdater.changeEmailAddress
  392. .calledWith(this.user_id, this.newEmail)
  393. .should.equal(false)
  394. return done()
  395. }
  396. return this.UserController.updateUserSettings(this.req, this.res)
  397. })
  398. })
  399. })
  400. describe('logout', function() {
  401. it('should destroy the session', function(done) {
  402. this.req.session.destroy = sinon.stub().callsArgWith(0)
  403. this.res.redirect = url => {
  404. url.should.equal('/login')
  405. this.req.session.destroy.called.should.equal(true)
  406. return done()
  407. }
  408. return this.UserController.logout(this.req, this.res)
  409. })
  410. it('should clear sudo-mode', function(done) {
  411. this.req.session.destroy = sinon.stub().callsArgWith(0)
  412. this.SudoModeHandler.clearSudoMode = sinon.stub()
  413. this.res.redirect = url => {
  414. url.should.equal('/login')
  415. this.SudoModeHandler.clearSudoMode.callCount.should.equal(1)
  416. this.SudoModeHandler.clearSudoMode
  417. .calledWith(this.user._id)
  418. .should.equal(true)
  419. return done()
  420. }
  421. return this.UserController.logout(this.req, this.res)
  422. })
  423. it('should redirect after logout', function(done) {
  424. this.req.body.redirect = '/institutional-login'
  425. this.req.session.destroy = sinon.stub().callsArgWith(0)
  426. this.SudoModeHandler.clearSudoMode = sinon.stub()
  427. this.res.redirect = url => {
  428. url.should.equal(this.req.body.redirect)
  429. return done()
  430. }
  431. return this.UserController.logout(this.req, this.res)
  432. })
  433. it('should redirect to login after logout when no redirect set', function(done) {
  434. this.req.session.destroy = sinon.stub().callsArgWith(0)
  435. this.SudoModeHandler.clearSudoMode = sinon.stub()
  436. this.res.redirect = url => {
  437. url.should.equal('/login')
  438. return done()
  439. }
  440. return this.UserController.logout(this.req, this.res)
  441. })
  442. })
  443. describe('register', function() {
  444. beforeEach(function() {
  445. this.UserRegistrationHandler.registerNewUserAndSendActivationEmail = sinon
  446. .stub()
  447. .callsArgWith(1, null, this.user, (this.url = 'mock/url'))
  448. this.req.body.email = this.user.email = this.email = 'email@example.com'
  449. return this.UserController.register(this.req, this.res)
  450. })
  451. it('should register the user and send them an email', function() {
  452. return this.UserRegistrationHandler.registerNewUserAndSendActivationEmail
  453. .calledWith(this.email)
  454. .should.equal(true)
  455. })
  456. it('should return the user and activation url', function() {
  457. return this.res.json
  458. .calledWith({
  459. email: this.email,
  460. setNewPasswordUrl: this.url
  461. })
  462. .should.equal(true)
  463. })
  464. })
  465. describe('clearSessions', function() {
  466. it('should call revokeAllUserSessions', function(done) {
  467. this.UserController.clearSessions(this.req, this.res)
  468. this.UserSessionsManager.revokeAllUserSessions.callCount.should.equal(1)
  469. return done()
  470. })
  471. it('send a 201 response', function(done) {
  472. this.res.sendStatus = status => {
  473. status.should.equal(201)
  474. return done()
  475. }
  476. return this.UserController.clearSessions(this.req, this.res)
  477. })
  478. describe('when revokeAllUserSessions produces an error', function() {
  479. it('should call next with an error', function(done) {
  480. this.UserSessionsManager.revokeAllUserSessions.callsArgWith(
  481. 2,
  482. new Error('woops')
  483. )
  484. const next = err => {
  485. expect(err).to.not.equal(null)
  486. expect(err).to.be.instanceof(Error)
  487. return done()
  488. }
  489. return this.UserController.clearSessions(this.req, this.res, next)
  490. })
  491. })
  492. })
  493. describe('changePassword', function() {
  494. it('should check the old password is the current one at the moment', function() {
  495. this.AuthenticationManager.authenticate.yields()
  496. this.req.body = { currentPassword: 'oldpasshere' }
  497. this.UserController.changePassword(this.req, this.res, this.callback)
  498. this.AuthenticationManager.authenticate.should.have.been.calledWith(
  499. { _id: this.user._id },
  500. 'oldpasshere'
  501. )
  502. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  503. })
  504. it('it should not set the new password if they do not match', function() {
  505. this.AuthenticationManager.authenticate.yields(null, {})
  506. this.req.body = {
  507. newPassword1: '1',
  508. newPassword2: '2'
  509. }
  510. this.UserController.changePassword(this.req, this.res, this.callback)
  511. this.res.status.should.have.been.calledWith(400)
  512. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  513. })
  514. it('should set the new password if they do match', function() {
  515. this.AuthenticationManager.authenticate.yields(null, this.user)
  516. this.AuthenticationManager.setUserPassword.yields()
  517. this.req.body = {
  518. newPassword1: 'newpass',
  519. newPassword2: 'newpass'
  520. }
  521. this.UserController.changePassword(this.req, this.res, this.callback)
  522. this.AuthenticationManager.setUserPassword.should.have.been.calledWith(
  523. this.user._id,
  524. 'newpass'
  525. )
  526. })
  527. it('it should not set the new password if it is invalid', function() {
  528. this.AuthenticationManager.validatePassword = sinon
  529. .stub()
  530. .returns({ message: 'validation-error' })
  531. this.AuthenticationManager.authenticate.yields(null, {})
  532. this.req.body = {
  533. newPassword1: 'newpass',
  534. newPassword2: 'newpass'
  535. }
  536. this.UserController.changePassword(this.req, this.res, this.callback)
  537. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  538. this.res.status.should.have.been.calledWith(400)
  539. this.res.json.should.have.been.calledWith({
  540. message: {
  541. type: 'error',
  542. text: 'validation-error'
  543. }
  544. })
  545. })
  546. })
  547. })