UserControllerTests.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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('../../../../app/src/Features/Errors/HttpErrors')
  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. '../Errors/HttpErrors': HttpErrors
  109. }
  110. })
  111. this.res = {
  112. send: sinon.stub(),
  113. status: sinon.stub(),
  114. sendStatus: sinon.stub(),
  115. json: sinon.stub()
  116. }
  117. this.res.status.returns(this.res)
  118. this.next = sinon.stub()
  119. this.callback = sinon.stub()
  120. })
  121. describe('tryDeleteUser', function() {
  122. beforeEach(function() {
  123. this.req.body.password = 'wat'
  124. this.req.logout = sinon.stub()
  125. this.req.session.destroy = sinon.stub().callsArgWith(0, null)
  126. this.AuthenticationController.getLoggedInUserId = sinon
  127. .stub()
  128. .returns(this.user._id)
  129. this.AuthenticationManager.authenticate = sinon
  130. .stub()
  131. .callsArgWith(2, null, this.user)
  132. })
  133. it('should send 200', function(done) {
  134. this.res.sendStatus = code => {
  135. code.should.equal(200)
  136. return done()
  137. }
  138. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  139. })
  140. it('should try to authenticate user', function(done) {
  141. this.res.sendStatus = code => {
  142. this.AuthenticationManager.authenticate.callCount.should.equal(1)
  143. this.AuthenticationManager.authenticate
  144. .calledWith({ _id: this.user._id }, this.req.body.password)
  145. .should.equal(true)
  146. return done()
  147. }
  148. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  149. })
  150. it('should delete the user', function(done) {
  151. this.res.sendStatus = code => {
  152. this.UserDeleter.deleteUser.callCount.should.equal(1)
  153. this.UserDeleter.deleteUser.calledWith(this.user._id).should.equal(true)
  154. return done()
  155. }
  156. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  157. })
  158. describe('when no password is supplied', function() {
  159. beforeEach(function() {
  160. return (this.req.body.password = '')
  161. })
  162. it('should return 403', function(done) {
  163. this.res.sendStatus = code => {
  164. code.should.equal(403)
  165. return done()
  166. }
  167. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  168. })
  169. })
  170. describe('when authenticate produces an error', function() {
  171. beforeEach(function() {
  172. return (this.AuthenticationManager.authenticate = sinon
  173. .stub()
  174. .callsArgWith(2, new Error('woops')))
  175. })
  176. it('should call next with an error', function(done) {
  177. this.next = err => {
  178. expect(err).to.not.equal(null)
  179. expect(err).to.be.instanceof(Error)
  180. return done()
  181. }
  182. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  183. })
  184. })
  185. describe('when authenticate does not produce a user', function() {
  186. beforeEach(function() {
  187. return (this.AuthenticationManager.authenticate = sinon
  188. .stub()
  189. .callsArgWith(2, null, null))
  190. })
  191. it('should return 403', function(done) {
  192. this.res.sendStatus = code => {
  193. code.should.equal(403)
  194. return done()
  195. }
  196. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  197. })
  198. })
  199. describe('when deleteUser produces an error', function() {
  200. beforeEach(function() {
  201. return (this.UserDeleter.deleteUser = sinon
  202. .stub()
  203. .yields(new Error('woops')))
  204. })
  205. it('should call next with an error', function(done) {
  206. this.next = err => {
  207. expect(err).to.not.equal(null)
  208. expect(err).to.be.instanceof(Error)
  209. return done()
  210. }
  211. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  212. })
  213. })
  214. describe('when deleteUser produces a known error', function() {
  215. beforeEach(function() {
  216. return (this.UserDeleter.deleteUser = sinon
  217. .stub()
  218. .yields(new Errors.SubscriptionAdminDeletionError()))
  219. })
  220. it('should return a json error', function(done) {
  221. this.UserController.tryDeleteUser(this.req, null, error => {
  222. expect(error).to.be.instanceof(HttpErrors.UnprocessableEntityError)
  223. expect(
  224. OError.hasCauseInstanceOf(
  225. error,
  226. Errors.SubscriptionAdminDeletionError
  227. )
  228. ).to.be.true
  229. done()
  230. })
  231. })
  232. })
  233. describe('when session.destroy produces an error', function() {
  234. beforeEach(function() {
  235. return (this.req.session.destroy = sinon
  236. .stub()
  237. .callsArgWith(0, new Error('woops')))
  238. })
  239. it('should call next with an error', function(done) {
  240. this.next = err => {
  241. expect(err).to.not.equal(null)
  242. expect(err).to.be.instanceof(Error)
  243. return done()
  244. }
  245. return this.UserController.tryDeleteUser(this.req, this.res, this.next)
  246. })
  247. })
  248. })
  249. describe('unsubscribe', () =>
  250. it('should send the user to unsubscribe', function(done) {
  251. this.res.send = code => {
  252. this.NewsLetterManager.unsubscribe
  253. .calledWith(this.user)
  254. .should.equal(true)
  255. return done()
  256. }
  257. return this.UserController.unsubscribe(this.req, this.res)
  258. }))
  259. describe('updateUserSettings', function() {
  260. beforeEach(function() {
  261. this.newEmail = 'hello@world.com'
  262. return (this.req.externalAuthenticationSystemUsed = sinon
  263. .stub()
  264. .returns(false))
  265. })
  266. it('should call save', function(done) {
  267. this.req.body = {}
  268. this.res.sendStatus = code => {
  269. this.user.save.called.should.equal(true)
  270. return done()
  271. }
  272. return this.UserController.updateUserSettings(this.req, this.res)
  273. })
  274. it('should set the first name', function(done) {
  275. this.req.body = { first_name: 'bobby ' }
  276. this.res.sendStatus = code => {
  277. this.user.first_name.should.equal('bobby')
  278. return done()
  279. }
  280. return this.UserController.updateUserSettings(this.req, this.res)
  281. })
  282. it('should set the role', function(done) {
  283. this.req.body = { role: 'student' }
  284. this.res.sendStatus = code => {
  285. this.user.role.should.equal('student')
  286. return done()
  287. }
  288. return this.UserController.updateUserSettings(this.req, this.res)
  289. })
  290. it('should set the institution', function(done) {
  291. this.req.body = { institution: 'MIT' }
  292. this.res.sendStatus = code => {
  293. this.user.institution.should.equal('MIT')
  294. return done()
  295. }
  296. return this.UserController.updateUserSettings(this.req, this.res)
  297. })
  298. it('should set some props on ace', function(done) {
  299. this.req.body = { editorTheme: 'something' }
  300. this.res.sendStatus = code => {
  301. this.user.ace.theme.should.equal('something')
  302. return done()
  303. }
  304. return this.UserController.updateUserSettings(this.req, this.res)
  305. })
  306. it('should set the overall theme', function(done) {
  307. this.req.body = { overallTheme: 'green-ish' }
  308. this.res.sendStatus = code => {
  309. this.user.ace.overallTheme.should.equal('green-ish')
  310. return done()
  311. }
  312. return this.UserController.updateUserSettings(this.req, this.res)
  313. })
  314. it('should send an error if the email is 0 len', function(done) {
  315. this.req.body.email = ''
  316. this.res.sendStatus = function(code) {
  317. code.should.equal(400)
  318. return done()
  319. }
  320. return this.UserController.updateUserSettings(this.req, this.res)
  321. })
  322. it('should send an error if the email does not contain an @', function(done) {
  323. this.req.body.email = 'bob at something dot com'
  324. this.res.sendStatus = function(code) {
  325. code.should.equal(400)
  326. return done()
  327. }
  328. return this.UserController.updateUserSettings(this.req, this.res)
  329. })
  330. it('should call the user updater with the new email and user _id', function(done) {
  331. this.req.body.email = this.newEmail.toUpperCase()
  332. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  333. this.res.sendStatus = code => {
  334. code.should.equal(200)
  335. this.UserUpdater.changeEmailAddress
  336. .calledWith(this.user_id, this.newEmail)
  337. .should.equal(true)
  338. return done()
  339. }
  340. return this.UserController.updateUserSettings(this.req, this.res)
  341. })
  342. it('should update the email on the session', function(done) {
  343. this.req.body.email = this.newEmail.toUpperCase()
  344. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  345. let callcount = 0
  346. this.User.findById = (id, cb) => {
  347. if (++callcount === 2) {
  348. this.user.email = this.newEmail
  349. }
  350. return cb(null, this.user)
  351. }
  352. this.res.sendStatus = code => {
  353. code.should.equal(200)
  354. this.AuthenticationController.setInSessionUser
  355. .calledWith(this.req, {
  356. email: this.newEmail,
  357. first_name: undefined,
  358. last_name: undefined
  359. })
  360. .should.equal(true)
  361. return done()
  362. }
  363. return this.UserController.updateUserSettings(this.req, this.res)
  364. })
  365. it('should call populateTeamInvites', function(done) {
  366. this.req.body.email = this.newEmail.toUpperCase()
  367. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  368. this.res.sendStatus = code => {
  369. code.should.equal(200)
  370. this.UserHandler.populateTeamInvites
  371. .calledWith(this.user)
  372. .should.equal(true)
  373. return done()
  374. }
  375. return this.UserController.updateUserSettings(this.req, this.res)
  376. })
  377. describe('when using an external auth source', function() {
  378. beforeEach(function() {
  379. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  380. this.newEmail = 'someone23@example.com'
  381. return (this.req.externalAuthenticationSystemUsed = sinon
  382. .stub()
  383. .returns(true))
  384. })
  385. it('should not set a new email', function(done) {
  386. this.req.body.email = this.newEmail
  387. this.res.sendStatus = code => {
  388. code.should.equal(200)
  389. this.UserUpdater.changeEmailAddress
  390. .calledWith(this.user_id, this.newEmail)
  391. .should.equal(false)
  392. return done()
  393. }
  394. return this.UserController.updateUserSettings(this.req, this.res)
  395. })
  396. })
  397. })
  398. describe('logout', function() {
  399. it('should destroy the session', function(done) {
  400. this.req.session.destroy = sinon.stub().callsArgWith(0)
  401. this.res.redirect = url => {
  402. url.should.equal('/login')
  403. this.req.session.destroy.called.should.equal(true)
  404. return done()
  405. }
  406. return this.UserController.logout(this.req, this.res)
  407. })
  408. it('should clear sudo-mode', function(done) {
  409. this.req.session.destroy = sinon.stub().callsArgWith(0)
  410. this.SudoModeHandler.clearSudoMode = sinon.stub()
  411. this.res.redirect = url => {
  412. url.should.equal('/login')
  413. this.SudoModeHandler.clearSudoMode.callCount.should.equal(1)
  414. this.SudoModeHandler.clearSudoMode
  415. .calledWith(this.user._id)
  416. .should.equal(true)
  417. return done()
  418. }
  419. return this.UserController.logout(this.req, this.res)
  420. })
  421. })
  422. describe('register', function() {
  423. beforeEach(function() {
  424. this.UserRegistrationHandler.registerNewUserAndSendActivationEmail = sinon
  425. .stub()
  426. .callsArgWith(1, null, this.user, (this.url = 'mock/url'))
  427. this.req.body.email = this.user.email = this.email = 'email@example.com'
  428. return this.UserController.register(this.req, this.res)
  429. })
  430. it('should register the user and send them an email', function() {
  431. return this.UserRegistrationHandler.registerNewUserAndSendActivationEmail
  432. .calledWith(this.email)
  433. .should.equal(true)
  434. })
  435. it('should return the user and activation url', function() {
  436. return this.res.json
  437. .calledWith({
  438. email: this.email,
  439. setNewPasswordUrl: this.url
  440. })
  441. .should.equal(true)
  442. })
  443. })
  444. describe('clearSessions', function() {
  445. it('should call revokeAllUserSessions', function(done) {
  446. this.UserController.clearSessions(this.req, this.res)
  447. this.UserSessionsManager.revokeAllUserSessions.callCount.should.equal(1)
  448. return done()
  449. })
  450. it('send a 201 response', function(done) {
  451. this.res.sendStatus = status => {
  452. status.should.equal(201)
  453. return done()
  454. }
  455. return this.UserController.clearSessions(this.req, this.res)
  456. })
  457. describe('when revokeAllUserSessions produces an error', () =>
  458. it('should call next with an error', function(done) {
  459. this.UserSessionsManager.revokeAllUserSessions.callsArgWith(
  460. 2,
  461. new Error('woops')
  462. )
  463. const next = err => {
  464. expect(err).to.not.equal(null)
  465. expect(err).to.be.instanceof(Error)
  466. return done()
  467. }
  468. return this.UserController.clearSessions(this.req, this.res, next)
  469. }))
  470. })
  471. describe('changePassword', function() {
  472. it('should check the old password is the current one at the moment', function() {
  473. this.AuthenticationManager.authenticate.yields()
  474. this.req.body = { currentPassword: 'oldpasshere' }
  475. this.UserController.changePassword(this.req, this.res, this.callback)
  476. this.AuthenticationManager.authenticate.should.have.been.calledWith(
  477. { _id: this.user._id },
  478. 'oldpasshere'
  479. )
  480. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  481. })
  482. it('it should not set the new password if they do not match', function() {
  483. this.AuthenticationManager.authenticate.yields(null, {})
  484. this.req.body = {
  485. newPassword1: '1',
  486. newPassword2: '2'
  487. }
  488. this.UserController.changePassword(this.req, this.res, this.callback)
  489. this.res.status.should.have.been.calledWith(400)
  490. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  491. })
  492. it('should set the new password if they do match', function() {
  493. this.AuthenticationManager.authenticate.yields(null, this.user)
  494. this.AuthenticationManager.setUserPassword.yields()
  495. this.req.body = {
  496. newPassword1: 'newpass',
  497. newPassword2: 'newpass'
  498. }
  499. this.UserController.changePassword(this.req, this.res, this.callback)
  500. this.AuthenticationManager.setUserPassword.should.have.been.calledWith(
  501. this.user._id,
  502. 'newpass'
  503. )
  504. })
  505. it('it should not set the new password if it is invalid', function() {
  506. this.AuthenticationManager.validatePassword = sinon
  507. .stub()
  508. .returns({ message: 'validation-error' })
  509. this.AuthenticationManager.authenticate.yields(null, {})
  510. this.req.body = {
  511. newPassword1: 'newpass',
  512. newPassword2: 'newpass'
  513. }
  514. this.UserController.changePassword(this.req, this.res, this.callback)
  515. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  516. this.res.status.should.have.been.calledWith(400)
  517. this.res.json.should.have.been.calledWith({
  518. message: {
  519. type: 'error',
  520. text: 'validation-error'
  521. }
  522. })
  523. })
  524. })
  525. })