PasswordResetTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. const { expect } = require('chai')
  2. const UserHelper = require('./helpers/UserHelper')
  3. const { db } = require('../../../app/src/infrastructure/mongodb')
  4. describe('PasswordReset', function () {
  5. let email, response, user, userHelper, token, emailQuery
  6. beforeEach(async function () {
  7. userHelper = new UserHelper()
  8. email = userHelper.getDefaultEmail()
  9. emailQuery = `?email=${encodeURIComponent(email)}`
  10. userHelper = await UserHelper.createUser({ email })
  11. user = userHelper.user
  12. // generate the token
  13. await userHelper.getCsrfToken()
  14. response = await userHelper.request.post('/user/password/reset', {
  15. form: {
  16. email,
  17. },
  18. })
  19. token = (
  20. await db.tokens.findOne({
  21. 'data.user_id': user._id.toString(),
  22. })
  23. ).token
  24. })
  25. describe('with a valid token', function () {
  26. describe('when logged in', function () {
  27. beforeEach(async function () {
  28. userHelper = await UserHelper.loginUser({
  29. email,
  30. password: userHelper.getDefaultPassword(),
  31. })
  32. response = await userHelper.request.get(
  33. `/user/password/set?passwordResetToken=${token}&email=${email}`,
  34. { simple: false }
  35. )
  36. expect(response.statusCode).to.equal(302)
  37. expect(response.headers.location).to.equal(
  38. `/user/password/set${emailQuery}`
  39. )
  40. // send reset request
  41. response = await userHelper.request.post('/user/password/set', {
  42. form: {
  43. passwordResetToken: token,
  44. password: 'a-password',
  45. },
  46. })
  47. userHelper = await UserHelper.getUser({ email })
  48. user = userHelper.user
  49. })
  50. it('update the password', async function () {
  51. expect(user.hashedPassword).to.exist
  52. expect(user.password).to.not.exist
  53. })
  54. it('log the change with initiatorId', async function () {
  55. const auditLog = userHelper.getAuditLogWithoutNoise()
  56. expect(auditLog).to.exist
  57. expect(auditLog[0]).to.exist
  58. expect(typeof auditLog[0].initiatorId).to.equal('object')
  59. expect(auditLog[0].initiatorId).to.deep.equal(user._id)
  60. expect(auditLog[0].operation).to.equal('reset-password')
  61. expect(auditLog[0].ipAddress).to.equal('127.0.0.1')
  62. expect(auditLog[0].timestamp).to.exist
  63. })
  64. })
  65. describe('when logged in as another user', function () {
  66. let otherUser, otherUserEmail
  67. beforeEach(async function () {
  68. otherUserEmail = userHelper.getDefaultEmail()
  69. userHelper = await UserHelper.createUser({ email: otherUserEmail })
  70. otherUser = userHelper.user
  71. userHelper = await UserHelper.loginUser({
  72. email: otherUserEmail,
  73. password: userHelper.getDefaultPassword(),
  74. })
  75. response = await userHelper.request.get(
  76. `/user/password/set?passwordResetToken=${token}&email=${email}`,
  77. { simple: false }
  78. )
  79. expect(response.statusCode).to.equal(302)
  80. expect(response.headers.location).to.equal(
  81. `/user/password/set${emailQuery}`
  82. )
  83. // send reset request
  84. response = await userHelper.request.post('/user/password/set', {
  85. form: {
  86. passwordResetToken: token,
  87. password: 'a-password',
  88. },
  89. })
  90. userHelper = await UserHelper.getUser({ email })
  91. user = userHelper.user
  92. })
  93. it('update the password', async function () {
  94. expect(user.hashedPassword).to.exist
  95. expect(user.password).to.not.exist
  96. })
  97. it('log the change with the logged in user as the initiatorId', async function () {
  98. const auditLog = userHelper.getAuditLogWithoutNoise()
  99. expect(auditLog).to.exist
  100. expect(auditLog[0]).to.exist
  101. expect(typeof auditLog[0].initiatorId).to.equal('object')
  102. expect(auditLog[0].initiatorId).to.deep.equal(otherUser._id)
  103. expect(auditLog[0].operation).to.equal('reset-password')
  104. expect(auditLog[0].ipAddress).to.equal('127.0.0.1')
  105. expect(auditLog[0].timestamp).to.exist
  106. })
  107. })
  108. describe('when not logged in', function () {
  109. beforeEach(async function () {
  110. response = await userHelper.request.get(
  111. `/user/password/set?passwordResetToken=${token}&email=${email}`,
  112. { simple: false }
  113. )
  114. expect(response.statusCode).to.equal(302)
  115. expect(response.headers.location).to.equal(
  116. `/user/password/set${emailQuery}`
  117. )
  118. // send reset request
  119. response = await userHelper.request.post('/user/password/set', {
  120. form: {
  121. passwordResetToken: token,
  122. password: 'a-password',
  123. },
  124. })
  125. userHelper = await UserHelper.getUser({ email })
  126. user = userHelper.user
  127. })
  128. it('updates the password', function () {
  129. expect(user.hashedPassword).to.exist
  130. expect(user.password).to.not.exist
  131. })
  132. it('log the change', async function () {
  133. const auditLog = userHelper.getAuditLogWithoutNoise()
  134. expect(auditLog).to.exist
  135. expect(auditLog[0]).to.exist
  136. expect(auditLog[0].initiatorId).to.equal(null)
  137. expect(auditLog[0].operation).to.equal('reset-password')
  138. expect(auditLog[0].ipAddress).to.equal('127.0.0.1')
  139. expect(auditLog[0].timestamp).to.exist
  140. })
  141. })
  142. describe('password checks', function () {
  143. beforeEach(async function () {
  144. response = await userHelper.request.get(
  145. `/user/password/set?passwordResetToken=${token}&email=${email}`,
  146. { simple: false }
  147. )
  148. expect(response.statusCode).to.equal(302)
  149. expect(response.headers.location).to.equal(
  150. `/user/password/set${emailQuery}`
  151. )
  152. })
  153. it('without a password should return 400 and not log the change', async function () {
  154. // send reset request
  155. response = await userHelper.request.post('/user/password/set', {
  156. form: {
  157. passwordResetToken: token,
  158. },
  159. simple: false,
  160. })
  161. expect(response.statusCode).to.equal(400)
  162. userHelper = await UserHelper.getUser({ email })
  163. const auditLog = userHelper.getAuditLogWithoutNoise()
  164. expect(auditLog).to.deep.equal([])
  165. })
  166. it('without a valid password should return 400 and not log the change', async function () {
  167. // send reset request
  168. response = await userHelper.request.post('/user/password/set', {
  169. form: {
  170. passwordResetToken: token,
  171. password: 'short',
  172. },
  173. simple: false,
  174. })
  175. expect(response.statusCode).to.equal(400)
  176. userHelper = await UserHelper.getUser({ email })
  177. const auditLog = userHelper.getAuditLogWithoutNoise()
  178. expect(auditLog).to.deep.equal([])
  179. })
  180. it('should flag email in password', async function () {
  181. const localPart = email.split('@').shift()
  182. // send bad password
  183. response = await userHelper.request.post('/user/password/set', {
  184. form: {
  185. passwordResetToken: token,
  186. password: localPart,
  187. email,
  188. },
  189. json: true,
  190. simple: false,
  191. })
  192. expect(response.statusCode).to.equal(400)
  193. expect(response.body).to.deep.equal({
  194. message: { text: 'password contains part of email address' },
  195. })
  196. })
  197. it('should be able to retry after providing an invalid password', async function () {
  198. // send bad password
  199. response = await userHelper.request.post('/user/password/set', {
  200. form: {
  201. passwordResetToken: token,
  202. password: 'short',
  203. },
  204. simple: false,
  205. })
  206. expect(response.statusCode).to.equal(400)
  207. // send good password
  208. response = await userHelper.request.post('/user/password/set', {
  209. form: {
  210. passwordResetToken: token,
  211. password: 'SomeThingVeryStrong!11',
  212. },
  213. simple: false,
  214. })
  215. expect(response.statusCode).to.equal(200)
  216. userHelper = await UserHelper.getUser({ email })
  217. const auditLog = userHelper.getAuditLogWithoutNoise()
  218. expect(auditLog.length).to.equal(1)
  219. })
  220. it('when the password is the same as current, should return 400 and log the change', async function () {
  221. // send reset request
  222. response = await userHelper.request.post('/user/password/set', {
  223. form: {
  224. passwordResetToken: token,
  225. password: userHelper.getDefaultPassword(),
  226. },
  227. simple: false,
  228. })
  229. expect(response.statusCode).to.equal(400)
  230. expect(JSON.parse(response.body).message.key).to.equal(
  231. 'password-must-be-different'
  232. )
  233. userHelper = await UserHelper.getUser({ email })
  234. const auditLog = userHelper.getAuditLogWithoutNoise()
  235. expect(auditLog.length).to.equal(1)
  236. })
  237. })
  238. })
  239. describe('multiple attempts to set the password, reaching attempt limit', async function () {
  240. beforeEach(async function () {
  241. response = await userHelper.request.get(
  242. `/user/password/set?passwordResetToken=${token}&email=${email}`,
  243. { simple: false }
  244. )
  245. expect(response.statusCode).to.equal(302)
  246. expect(response.headers.location).to.equal(
  247. `/user/password/set${emailQuery}`
  248. )
  249. })
  250. it('should allow multiple attempts with same-password error, then deny further attempts', async function () {
  251. const sendSamePasswordRequest = async function () {
  252. return userHelper.request.post('/user/password/set', {
  253. form: {
  254. passwordResetToken: token,
  255. password: userHelper.getDefaultPassword(),
  256. },
  257. simple: false,
  258. })
  259. }
  260. // Three attempts at setting the password, all rejected for being the same as
  261. // the current password
  262. const response1 = await sendSamePasswordRequest()
  263. expect(response1.statusCode).to.equal(400)
  264. expect(JSON.parse(response1.body).message.key).to.equal(
  265. 'password-must-be-different'
  266. )
  267. const response2 = await sendSamePasswordRequest()
  268. expect(response2.statusCode).to.equal(400)
  269. expect(JSON.parse(response2.body).message.key).to.equal(
  270. 'password-must-be-different'
  271. )
  272. const response3 = await sendSamePasswordRequest()
  273. expect(response3.statusCode).to.equal(400)
  274. expect(JSON.parse(response3.body).message.key).to.equal(
  275. 'password-must-be-different'
  276. )
  277. // Fourth attempt is rejected because the token has been used too many times
  278. const response4 = await sendSamePasswordRequest()
  279. expect(response4.statusCode).to.equal(404)
  280. expect(JSON.parse(response4.body).message.key).to.equal('token-expired')
  281. })
  282. it('should allow multiple attempts with same-password error, then set the password', async function () {
  283. const sendSamePasswordRequest = async function () {
  284. return userHelper.request.post('/user/password/set', {
  285. form: {
  286. passwordResetToken: token,
  287. password: userHelper.getDefaultPassword(),
  288. },
  289. simple: false,
  290. })
  291. }
  292. // Two attempts at setting the password, all rejected for being the same as
  293. // the current password
  294. const response1 = await sendSamePasswordRequest()
  295. expect(response1.statusCode).to.equal(400)
  296. expect(JSON.parse(response1.body).message.key).to.equal(
  297. 'password-must-be-different'
  298. )
  299. const response2 = await sendSamePasswordRequest()
  300. expect(response2.statusCode).to.equal(400)
  301. expect(JSON.parse(response2.body).message.key).to.equal(
  302. 'password-must-be-different'
  303. )
  304. // Third attempt is succeeds
  305. const response3 = await userHelper.request.post('/user/password/set', {
  306. form: {
  307. passwordResetToken: token,
  308. password: 'some-new-password',
  309. },
  310. simple: false,
  311. })
  312. expect(response3.statusCode).to.equal(200)
  313. // Check the user and audit log
  314. userHelper = await UserHelper.getUser({ email })
  315. user = userHelper.user
  316. expect(user.hashedPassword).to.exist
  317. expect(user.password).to.not.exist
  318. const auditLog = userHelper.getAuditLogWithoutNoise()
  319. expect(auditLog).to.exist
  320. expect(auditLog[0]).to.exist
  321. expect(auditLog[0].initiatorId).to.equal(null)
  322. expect(auditLog[0].operation).to.equal('reset-password')
  323. expect(auditLog[0].ipAddress).to.equal('127.0.0.1')
  324. expect(auditLog[0].timestamp).to.exist
  325. })
  326. })
  327. describe('without a valid token', function () {
  328. it('no token should redirect to page to re-request reset token', async function () {
  329. response = await userHelper.request.get(
  330. `/user/password/set?&email=${email}`,
  331. { simple: false }
  332. )
  333. expect(response.statusCode).to.equal(302)
  334. expect(response.headers.location).to.equal('/user/password/reset')
  335. })
  336. it('should show error for invalid tokens and return 404 if used', async function () {
  337. const invalidToken = 'not-real-token'
  338. response = await userHelper.request.get(
  339. `/user/password/set?&passwordResetToken=${invalidToken}&email=${email}`,
  340. { simple: false }
  341. )
  342. expect(response.statusCode).to.equal(302)
  343. expect(response.headers.location).to.equal(
  344. `/user/password/reset?error=token_expired`
  345. )
  346. // send reset request
  347. response = await userHelper.request.post('/user/password/set', {
  348. form: {
  349. passwordResetToken: invalidToken,
  350. password: 'a-password',
  351. },
  352. simple: false,
  353. })
  354. expect(response.statusCode).to.equal(404)
  355. })
  356. })
  357. describe('password reset', function () {
  358. it('should return 200 if email field is valid', async function () {
  359. response = await userHelper.request.post(`/user/password/reset`, {
  360. form: {
  361. email,
  362. },
  363. })
  364. expect(response.statusCode).to.equal(200)
  365. })
  366. it('should return 400 if email field is missing', async function () {
  367. response = await userHelper.request.post(`/user/password/reset`, {
  368. form: {
  369. mail: email,
  370. },
  371. simple: false,
  372. })
  373. expect(response.statusCode).to.equal(400)
  374. })
  375. })
  376. describe('password set', function () {
  377. it('should return 200 if password and passwordResetToken fields are valid', async function () {
  378. response = await userHelper.request.post(`/user/password/set`, {
  379. form: {
  380. password: 'new-password',
  381. passwordResetToken: token,
  382. },
  383. })
  384. expect(response.statusCode).to.equal(200)
  385. })
  386. it('should return 400 if password field is missing', async function () {
  387. response = await userHelper.request.post(`/user/password/set`, {
  388. form: {
  389. passwordResetToken: token,
  390. },
  391. simple: false,
  392. })
  393. expect(response.statusCode).to.equal(400)
  394. })
  395. it('should return 400 if passwordResetToken field is missing', async function () {
  396. response = await userHelper.request.post(`/user/password/set`, {
  397. form: {
  398. password: 'new-password',
  399. },
  400. simple: false,
  401. })
  402. expect(response.statusCode).to.equal(400)
  403. })
  404. })
  405. })