PasswordResetHandlerTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /* eslint-disable
  2. handle-callback-err,
  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 SandboxedModule = require('sandboxed-module')
  15. const assert = require('assert')
  16. const path = require('path')
  17. const sinon = require('sinon')
  18. const sinonChai = require('sinon-chai')
  19. const chai = require('chai')
  20. const modulePath = path.join(
  21. __dirname,
  22. '../../../../app/src/Features/PasswordReset/PasswordResetHandler'
  23. )
  24. const should = require('chai').should()
  25. chai.use(sinonChai)
  26. const { expect } = chai
  27. describe('PasswordResetHandler', function() {
  28. beforeEach(function() {
  29. this.settings = { siteUrl: 'www.sharelatex.com' }
  30. this.OneTimeTokenHandler = {
  31. getNewToken: sinon.stub(),
  32. getValueFromTokenAndExpire: sinon.stub()
  33. }
  34. this.UserGetter = {
  35. getUserByMainEmail: sinon.stub(),
  36. getUser: sinon.stub(),
  37. getUserByAnyEmail: sinon.stub()
  38. }
  39. this.EmailHandler = { sendEmail: sinon.stub() }
  40. this.AuthenticationManager = {
  41. setUserPassword: sinon.stub(),
  42. setUserPasswordInV1: sinon.stub(),
  43. setUserPasswordInV2: sinon.stub()
  44. }
  45. this.PasswordResetHandler = SandboxedModule.require(modulePath, {
  46. globals: { console: console },
  47. requires: {
  48. '../User/UserGetter': this.UserGetter,
  49. '../Security/OneTimeTokenHandler': this.OneTimeTokenHandler,
  50. '../Email/EmailHandler': this.EmailHandler,
  51. '../Authentication/AuthenticationManager': this.AuthenticationManager,
  52. 'settings-sharelatex': this.settings,
  53. 'logger-sharelatex': {
  54. log() {},
  55. err() {}
  56. }
  57. }
  58. })
  59. this.token = '12312321i'
  60. this.user_id = 'user_id_here'
  61. this.user = { email: (this.email = 'bob@bob.com'), _id: 'user-id' }
  62. this.password = 'my great secret password'
  63. this.callback = sinon.stub()
  64. // this should not have any effect now
  65. this.settings.overleaf = true
  66. })
  67. afterEach(function() {
  68. this.settings.overleaf = false
  69. })
  70. describe('generateAndEmailResetToken', function() {
  71. it('should check the user exists', function() {
  72. this.UserGetter.getUserByAnyEmail.yields()
  73. this.PasswordResetHandler.generateAndEmailResetToken(
  74. this.user.email,
  75. this.callback
  76. )
  77. this.UserGetter.getUserByAnyEmail.should.have.been.calledWith(
  78. this.user.email
  79. )
  80. })
  81. it('should send the email with the token', function(done) {
  82. this.UserGetter.getUserByAnyEmail.yields(null, this.user)
  83. this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
  84. this.EmailHandler.sendEmail.yields()
  85. this.PasswordResetHandler.generateAndEmailResetToken(
  86. this.user.email,
  87. (err, status) => {
  88. this.EmailHandler.sendEmail.called.should.equal(true)
  89. status.should.equal('primary')
  90. const args = this.EmailHandler.sendEmail.args[0]
  91. args[0].should.equal('passwordResetRequested')
  92. args[1].setNewPasswordUrl.should.equal(
  93. `${this.settings.siteUrl}/user/password/set?passwordResetToken=${
  94. this.token
  95. }&email=${encodeURIComponent(this.user.email)}`
  96. )
  97. done()
  98. }
  99. )
  100. })
  101. describe('when the email exists', function() {
  102. beforeEach(function() {
  103. this.UserGetter.getUserByAnyEmail.yields(null, this.user)
  104. this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
  105. this.EmailHandler.sendEmail.yields()
  106. this.PasswordResetHandler.generateAndEmailResetToken(
  107. this.email,
  108. this.callback
  109. )
  110. })
  111. it('should set the password token data to the user id and email', function() {
  112. this.OneTimeTokenHandler.getNewToken.should.have.been.calledWith(
  113. 'password',
  114. {
  115. email: this.email,
  116. user_id: this.user._id
  117. }
  118. )
  119. })
  120. it('should send an email with the token', function() {
  121. this.EmailHandler.sendEmail.called.should.equal(true)
  122. const args = this.EmailHandler.sendEmail.args[0]
  123. args[0].should.equal('passwordResetRequested')
  124. args[1].setNewPasswordUrl.should.equal(
  125. `${this.settings.siteUrl}/user/password/set?passwordResetToken=${
  126. this.token
  127. }&email=${encodeURIComponent(this.user.email)}`
  128. )
  129. })
  130. it('should return status == true', function() {
  131. this.callback.calledWith(null, 'primary').should.equal(true)
  132. })
  133. })
  134. describe("when the email doesn't exist", function() {
  135. beforeEach(function() {
  136. this.UserGetter.getUserByAnyEmail.yields(null, null)
  137. this.PasswordResetHandler.generateAndEmailResetToken(
  138. this.email,
  139. this.callback
  140. )
  141. })
  142. it('should not set the password token data', function() {
  143. this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
  144. })
  145. it('should send an email with the token', function() {
  146. this.EmailHandler.sendEmail.called.should.equal(false)
  147. })
  148. it('should return status == null', function() {
  149. this.callback.calledWith(null, null).should.equal(true)
  150. })
  151. })
  152. describe('when the email is a secondary email', function() {
  153. beforeEach(function() {
  154. this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.user)
  155. this.PasswordResetHandler.generateAndEmailResetToken(
  156. 'secondary@email.com',
  157. this.callback
  158. )
  159. })
  160. it('should not set the password token data', function() {
  161. this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
  162. })
  163. it('should not send an email with the token', function() {
  164. this.EmailHandler.sendEmail.called.should.equal(false)
  165. })
  166. it('should return status == secondary', function() {
  167. this.callback.calledWith(null, 'secondary').should.equal(true)
  168. })
  169. })
  170. })
  171. describe('setNewUserPassword', function() {
  172. describe('when no data is found', function() {
  173. beforeEach(function() {
  174. this.OneTimeTokenHandler.getValueFromTokenAndExpire.yields(null, null)
  175. this.PasswordResetHandler.setNewUserPassword(
  176. this.token,
  177. this.password,
  178. this.callback
  179. )
  180. })
  181. it('should return exists == false', function() {
  182. this.callback.calledWith(null, false).should.equal(true)
  183. })
  184. })
  185. describe('when the token has a user_id and email', function() {
  186. beforeEach(function() {
  187. this.OneTimeTokenHandler.getValueFromTokenAndExpire
  188. .withArgs('password', this.token)
  189. .yields(null, {
  190. user_id: this.user._id,
  191. email: this.email
  192. })
  193. this.AuthenticationManager.setUserPassword
  194. .withArgs(this.user._id, this.password)
  195. .yields(null, true, this.user._id)
  196. })
  197. describe('when no user is found with this email', function() {
  198. beforeEach(function() {
  199. this.UserGetter.getUserByMainEmail
  200. .withArgs(this.email)
  201. .yields(null, null)
  202. })
  203. it('should return found == false', function(done) {
  204. this.PasswordResetHandler.setNewUserPassword(
  205. this.token,
  206. this.password,
  207. (err, found) => {
  208. if (err != null) {
  209. return done(err)
  210. }
  211. expect(found).to.be.false
  212. done()
  213. }
  214. )
  215. })
  216. })
  217. describe("when the email and user don't match", function() {
  218. beforeEach(function() {
  219. this.UserGetter.getUserByMainEmail
  220. .withArgs(this.email)
  221. .yields(null, { _id: 'not-the-same', email: this.email })
  222. })
  223. it('should return found == false', function(done) {
  224. this.PasswordResetHandler.setNewUserPassword(
  225. this.token,
  226. this.password,
  227. (err, found) => {
  228. if (err != null) {
  229. return done(err)
  230. }
  231. expect(found).to.be.false
  232. done()
  233. }
  234. )
  235. })
  236. })
  237. describe('when the email and user match', function() {
  238. beforeEach(function() {
  239. this.PasswordResetHandler.getUserForPasswordResetToken = sinon
  240. .stub()
  241. .withArgs(this.token)
  242. .yields(null, this.user)
  243. })
  244. it('should return found == true and the user id', function(done) {
  245. this.PasswordResetHandler.setNewUserPassword(
  246. this.token,
  247. this.password,
  248. (err, found, userId) => {
  249. if (err != null) {
  250. return done(err)
  251. }
  252. expect(found).to.be.true
  253. expect(userId).to.equal(this.user._id)
  254. done()
  255. }
  256. )
  257. })
  258. })
  259. })
  260. describe('when the token has a v1_user_id and email', function() {
  261. beforeEach(function() {
  262. this.user.overleaf = { id: 184 }
  263. this.OneTimeTokenHandler.getValueFromTokenAndExpire
  264. .withArgs('password', this.token)
  265. .yields(null, {
  266. v1_user_id: this.user.overleaf.id,
  267. email: this.email
  268. })
  269. this.AuthenticationManager.setUserPassword
  270. .withArgs(this.user._id, this.password)
  271. .yields(null, true)
  272. })
  273. describe('when no user is found with this email', function() {
  274. beforeEach(function() {
  275. this.UserGetter.getUserByMainEmail
  276. .withArgs(this.email)
  277. .yields(null, null)
  278. })
  279. it('should return found == false', function(done) {
  280. this.PasswordResetHandler.setNewUserPassword(
  281. this.token,
  282. this.password,
  283. (err, found) => {
  284. if (err != null) {
  285. return done(err)
  286. }
  287. expect(found).to.be.false
  288. done()
  289. }
  290. )
  291. })
  292. })
  293. describe("when the email and user don't match", function() {
  294. beforeEach(function() {
  295. this.UserGetter.getUserByMainEmail.withArgs(this.email).yields(null, {
  296. _id: this.user._id,
  297. email: this.email,
  298. overleaf: { id: 'not-the-same' }
  299. })
  300. })
  301. it('should return found == false', function(done) {
  302. this.PasswordResetHandler.setNewUserPassword(
  303. this.token,
  304. this.password,
  305. (err, found) => {
  306. if (err != null) {
  307. return done(err)
  308. }
  309. expect(found).to.be.false
  310. done()
  311. }
  312. )
  313. })
  314. })
  315. describe('when the email and user match', function() {
  316. beforeEach(function() {
  317. this.UserGetter.getUserByMainEmail
  318. .withArgs(this.email)
  319. .yields(null, this.user)
  320. })
  321. it('should return found == true and the user id', function(done) {
  322. this.PasswordResetHandler.setNewUserPassword(
  323. this.token,
  324. this.password,
  325. (err, found, userId) => {
  326. if (err != null) {
  327. return done(err)
  328. }
  329. expect(found).to.be.true
  330. expect(userId).to.equal(this.user._id)
  331. done()
  332. }
  333. )
  334. })
  335. })
  336. })
  337. })
  338. })