PasswordResetControllerTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /* eslint-disable
  2. camelcase,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const should = require('chai').should()
  15. let { expect } = require('chai')
  16. const SandboxedModule = require('sandboxed-module')
  17. const assert = require('assert')
  18. const path = require('path')
  19. const sinon = require('sinon')
  20. const modulePath = path.join(
  21. __dirname,
  22. '../../../../app/src/Features/PasswordReset/PasswordResetController'
  23. )
  24. ;({ expect } = require('chai'))
  25. describe('PasswordResetController', function() {
  26. beforeEach(function() {
  27. this.settings = {}
  28. this.PasswordResetHandler = {
  29. generateAndEmailResetToken: sinon.stub(),
  30. setNewUserPassword: sinon.stub()
  31. }
  32. this.RateLimiter = { addCount: sinon.stub() }
  33. this.UserSessionsManager = {
  34. revokeAllUserSessions: sinon.stub().callsArgWith(2, null)
  35. }
  36. this.AuthenticationManager = { validatePassword: sinon.stub() }
  37. this.UserUpdater = {
  38. removeReconfirmFlag: sinon.stub().callsArgWith(1, null)
  39. }
  40. this.PasswordResetController = SandboxedModule.require(modulePath, {
  41. globals: {
  42. console: console
  43. },
  44. requires: {
  45. 'settings-sharelatex': this.settings,
  46. './PasswordResetHandler': this.PasswordResetHandler,
  47. 'logger-sharelatex': {
  48. log() {}
  49. },
  50. '../../infrastructure/RateLimiter': this.RateLimiter,
  51. '../Authentication/AuthenticationController': (this.AuthenticationController = {}),
  52. '../Authentication/AuthenticationManager': this.AuthenticationManager,
  53. '../User/UserGetter': (this.UserGetter = {}),
  54. '../User/UserSessionsManager': this.UserSessionsManager,
  55. '../User/UserUpdater': this.UserUpdater
  56. }
  57. })
  58. this.email = 'bob@bob.com '
  59. this.user_id = 'mock-user-id'
  60. this.token = 'my security token that was emailed to me'
  61. this.password = 'my new password'
  62. this.req = {
  63. body: {
  64. email: this.email,
  65. passwordResetToken: this.token,
  66. password: this.password
  67. },
  68. i18n: {
  69. translate() {}
  70. },
  71. session: {},
  72. query: {}
  73. }
  74. return (this.res = {})
  75. })
  76. describe('requestReset', function() {
  77. it('should error if the rate limit is hit', function(done) {
  78. this.PasswordResetHandler.generateAndEmailResetToken.callsArgWith(
  79. 1,
  80. null,
  81. 'primary'
  82. )
  83. this.RateLimiter.addCount.callsArgWith(1, null, false)
  84. this.res.send = code => {
  85. code.should.equal(429)
  86. this.PasswordResetHandler.generateAndEmailResetToken
  87. .calledWith(this.email.trim())
  88. .should.equal(false)
  89. return done()
  90. }
  91. return this.PasswordResetController.requestReset(this.req, this.res)
  92. })
  93. it('should tell the handler to process that email', function(done) {
  94. this.RateLimiter.addCount.callsArgWith(1, null, true)
  95. this.PasswordResetHandler.generateAndEmailResetToken.callsArgWith(
  96. 1,
  97. null,
  98. 'primary'
  99. )
  100. this.res.send = code => {
  101. code.should.equal(200)
  102. this.PasswordResetHandler.generateAndEmailResetToken
  103. .calledWith(this.email.trim())
  104. .should.equal(true)
  105. return done()
  106. }
  107. return this.PasswordResetController.requestReset(this.req, this.res)
  108. })
  109. it('should send a 500 if there is an error', function(done) {
  110. this.RateLimiter.addCount.callsArgWith(1, null, true)
  111. this.PasswordResetHandler.generateAndEmailResetToken.callsArgWith(
  112. 1,
  113. 'error'
  114. )
  115. this.res.send = code => {
  116. code.should.equal(500)
  117. return done()
  118. }
  119. return this.PasswordResetController.requestReset(this.req, this.res)
  120. })
  121. it("should send a 404 if the email doesn't exist", function(done) {
  122. this.RateLimiter.addCount.callsArgWith(1, null, true)
  123. this.PasswordResetHandler.generateAndEmailResetToken.callsArgWith(
  124. 1,
  125. null,
  126. null
  127. )
  128. this.res.send = code => {
  129. code.should.equal(404)
  130. return done()
  131. }
  132. return this.PasswordResetController.requestReset(this.req, this.res)
  133. })
  134. it('should send a 404 if the email is registered as a secondard email', function(done) {
  135. this.RateLimiter.addCount.callsArgWith(1, null, true)
  136. this.PasswordResetHandler.generateAndEmailResetToken.callsArgWith(
  137. 1,
  138. null,
  139. 'secondary'
  140. )
  141. this.res.send = code => {
  142. code.should.equal(404)
  143. return done()
  144. }
  145. return this.PasswordResetController.requestReset(this.req, this.res)
  146. })
  147. it('should lowercase the email address', function(done) {
  148. this.email = 'UPerCaseEMAIL@example.Com'
  149. this.req.body.email = this.email
  150. this.RateLimiter.addCount.callsArgWith(1, null, true)
  151. this.PasswordResetHandler.generateAndEmailResetToken.callsArgWith(
  152. 1,
  153. null,
  154. 'primary'
  155. )
  156. this.res.send = code => {
  157. code.should.equal(200)
  158. this.PasswordResetHandler.generateAndEmailResetToken
  159. .calledWith(this.email.toLowerCase())
  160. .should.equal(true)
  161. return done()
  162. }
  163. return this.PasswordResetController.requestReset(this.req, this.res)
  164. })
  165. })
  166. describe('setNewUserPassword', function() {
  167. beforeEach(function() {
  168. return (this.req.session.resetToken = this.token)
  169. })
  170. it('should tell the user handler to reset the password', function(done) {
  171. this.PasswordResetHandler.setNewUserPassword.callsArgWith(
  172. 2,
  173. null,
  174. true,
  175. this.user_id
  176. )
  177. this.res.sendStatus = code => {
  178. code.should.equal(200)
  179. this.PasswordResetHandler.setNewUserPassword
  180. .calledWith(this.token, this.password)
  181. .should.equal(true)
  182. return done()
  183. }
  184. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  185. })
  186. it("should send 404 if the token didn't work", function(done) {
  187. this.PasswordResetHandler.setNewUserPassword.callsArgWith(
  188. 2,
  189. null,
  190. false,
  191. this.user_id
  192. )
  193. this.res.sendStatus = code => {
  194. code.should.equal(404)
  195. return done()
  196. }
  197. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  198. })
  199. it('should return 400 (Bad Request) if there is no password', function(done) {
  200. this.req.body.password = ''
  201. this.PasswordResetHandler.setNewUserPassword.callsArgWith(2)
  202. this.res.sendStatus = code => {
  203. code.should.equal(400)
  204. this.PasswordResetHandler.setNewUserPassword.called.should.equal(false)
  205. return done()
  206. }
  207. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  208. })
  209. it('should return 400 (Bad Request) if there is no passwordResetToken', function(done) {
  210. this.req.body.passwordResetToken = ''
  211. this.PasswordResetHandler.setNewUserPassword.callsArgWith(2)
  212. this.res.sendStatus = code => {
  213. code.should.equal(400)
  214. this.PasswordResetHandler.setNewUserPassword.called.should.equal(false)
  215. return done()
  216. }
  217. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  218. })
  219. it('should return 400 (Bad Request) if the password is invalid', function(done) {
  220. this.req.body.password = 'correct horse battery staple'
  221. this.AuthenticationManager.validatePassword = sinon
  222. .stub()
  223. .returns({ message: 'password contains invalid characters' })
  224. this.PasswordResetHandler.setNewUserPassword.callsArgWith(2)
  225. this.res.sendStatus = code => {
  226. code.should.equal(400)
  227. this.PasswordResetHandler.setNewUserPassword.called.should.equal(false)
  228. return done()
  229. }
  230. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  231. })
  232. it('should clear the session.resetToken', function(done) {
  233. this.PasswordResetHandler.setNewUserPassword.callsArgWith(
  234. 2,
  235. null,
  236. true,
  237. this.user_id
  238. )
  239. this.res.sendStatus = code => {
  240. code.should.equal(200)
  241. this.req.session.should.not.have.property('resetToken')
  242. return done()
  243. }
  244. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  245. })
  246. it('should clear sessions', function(done) {
  247. this.PasswordResetHandler.setNewUserPassword.callsArgWith(
  248. 2,
  249. null,
  250. true,
  251. this.user_id
  252. )
  253. this.res.sendStatus = code => {
  254. this.UserSessionsManager.revokeAllUserSessions.callCount.should.equal(1)
  255. return done()
  256. }
  257. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  258. })
  259. it('should call removeReconfirmFlag', function(done) {
  260. this.PasswordResetHandler.setNewUserPassword.callsArgWith(
  261. 2,
  262. null,
  263. true,
  264. this.user_id
  265. )
  266. this.res.sendStatus = code => {
  267. this.UserUpdater.removeReconfirmFlag.callCount.should.equal(1)
  268. return done()
  269. }
  270. return this.PasswordResetController.setNewUserPassword(this.req, this.res)
  271. })
  272. describe('when login_after is set', function() {
  273. beforeEach(function() {
  274. this.UserGetter.getUser = sinon
  275. .stub()
  276. .callsArgWith(2, null, { email: 'joe@example.com' })
  277. this.PasswordResetHandler.setNewUserPassword.callsArgWith(
  278. 2,
  279. null,
  280. true,
  281. (this.user_id = 'user-id-123')
  282. )
  283. this.req.body.login_after = 'true'
  284. this.res.json = sinon.stub()
  285. this.AuthenticationController.afterLoginSessionSetup = sinon
  286. .stub()
  287. .callsArgWith(2, null)
  288. return (this.AuthenticationController._getRedirectFromSession = sinon
  289. .stub()
  290. .returns('/some/path'))
  291. })
  292. it('should login user if login_after is set', function(done) {
  293. this.PasswordResetController.setNewUserPassword(this.req, this.res)
  294. this.AuthenticationController.afterLoginSessionSetup.callCount.should.equal(
  295. 1
  296. )
  297. this.AuthenticationController.afterLoginSessionSetup
  298. .calledWith(this.req, { email: 'joe@example.com' })
  299. .should.equal(true)
  300. this.AuthenticationController._getRedirectFromSession.callCount.should.equal(
  301. 1
  302. )
  303. this.res.json.callCount.should.equal(1)
  304. this.res.json.calledWith({ redir: '/some/path' }).should.equal(true)
  305. return done()
  306. })
  307. })
  308. })
  309. describe('renderSetPasswordForm', function() {
  310. describe('with token in query-string', function() {
  311. beforeEach(function() {
  312. return (this.req.query.passwordResetToken = this.token)
  313. })
  314. it('should set session.resetToken and redirect', function(done) {
  315. this.req.session.should.not.have.property('resetToken')
  316. this.res.redirect = path => {
  317. path.should.equal('/user/password/set')
  318. this.req.session.resetToken.should.equal(this.token)
  319. return done()
  320. }
  321. return this.PasswordResetController.renderSetPasswordForm(
  322. this.req,
  323. this.res
  324. )
  325. })
  326. })
  327. describe('without a token in query-string', function() {
  328. describe('with token in session', function() {
  329. beforeEach(function() {
  330. return (this.req.session.resetToken = this.token)
  331. })
  332. it('should render the page, passing the reset token', function(done) {
  333. this.res.render = (template_path, options) => {
  334. options.passwordResetToken.should.equal(this.req.session.resetToken)
  335. return done()
  336. }
  337. return this.PasswordResetController.renderSetPasswordForm(
  338. this.req,
  339. this.res
  340. )
  341. })
  342. })
  343. describe('without a token in session', () =>
  344. it('should redirect to the reset request page', function(done) {
  345. this.res.redirect = path => {
  346. path.should.equal('/user/password/reset')
  347. this.req.session.should.not.have.property('resetToken')
  348. return done()
  349. }
  350. return this.PasswordResetController.renderSetPasswordForm(
  351. this.req,
  352. this.res
  353. )
  354. }))
  355. })
  356. })
  357. })