CaptchaTests.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. const { db } = require('../../../app/src/infrastructure/mongodb')
  2. const { expect } = require('chai')
  3. const User = require('./helpers/User').promises
  4. describe('Captcha', function () {
  5. let user
  6. beforeEach('create user', async function () {
  7. user = new User()
  8. await user.ensureUserExists()
  9. })
  10. async function login(email, password, captchaResponse) {
  11. await user.getCsrfToken()
  12. return user.doRequest('POST', {
  13. url: '/login',
  14. json: {
  15. email,
  16. password,
  17. 'g-recaptcha-response': captchaResponse,
  18. },
  19. })
  20. }
  21. async function loginWithCaptcha(captchaResponse) {
  22. return login(user.email, user.password, captchaResponse)
  23. }
  24. async function loginWithEmailAndCaptcha(email, captchaResponse) {
  25. return login(email, user.password, captchaResponse)
  26. }
  27. async function canSkipCaptcha(email) {
  28. await user.getCsrfToken()
  29. const { response, body } = await user.doRequest('POST', {
  30. url: '/login/can-skip-captcha',
  31. json: { email },
  32. })
  33. expect(response.statusCode).to.equal(200)
  34. return body
  35. }
  36. function expectBadCaptchaResponse(response, body) {
  37. expect(response.statusCode).to.equal(400)
  38. expect(body.errorReason).to.equal('cannot_verify_user_not_robot')
  39. }
  40. function expectSuccessfulLogin(response, body) {
  41. expect(response.statusCode).to.equal(200)
  42. expect(body).to.deep.equal({ redir: '/project' })
  43. }
  44. function expectBadLogin(response, body) {
  45. expect(response.statusCode).to.equal(401)
  46. expect(body).to.deep.equal({
  47. message: {
  48. text: 'Your email or password is incorrect. Please try again.',
  49. type: 'error',
  50. },
  51. })
  52. }
  53. it('should reject a login without captcha response', async function () {
  54. const { response, body } = await loginWithCaptcha('')
  55. expectBadCaptchaResponse(response, body)
  56. })
  57. it('should reject a login with an invalid captcha response', async function () {
  58. const { response, body } = await loginWithCaptcha('invalid')
  59. expectBadCaptchaResponse(response, body)
  60. })
  61. it('should accept a login with a valid captcha response', async function () {
  62. const { response, body } = await loginWithCaptcha('valid')
  63. expectSuccessfulLogin(response, body)
  64. })
  65. it('should note the solved captcha in audit log', async function () {
  66. const { response, body } = await loginWithCaptcha('valid')
  67. expectSuccessfulLogin(response, body)
  68. expect((await user.get()).auditLog.pop().info).to.deep.equal({
  69. captcha: 'solved',
  70. method: 'Password login',
  71. })
  72. })
  73. describe('deviceHistory', function () {
  74. beforeEach('login', async function () {
  75. const { response, body } = await loginWithCaptcha('valid')
  76. expectSuccessfulLogin(response, body)
  77. })
  78. it('should be able to skip captcha with the same email', async function () {
  79. expect(await canSkipCaptcha(user.email)).to.equal(true)
  80. })
  81. it('should be able to omit captcha with the same email', async function () {
  82. const { response, body } = await loginWithCaptcha('')
  83. expectSuccessfulLogin(response, body)
  84. })
  85. it('should note the skipped captcha in audit log', async function () {
  86. const { response, body } = await loginWithCaptcha('')
  87. expectSuccessfulLogin(response, body)
  88. expect((await user.get()).auditLog.pop().info).to.deep.equal({
  89. captcha: 'skipped',
  90. method: 'Password login',
  91. })
  92. })
  93. it('should request a captcha for another email', async function () {
  94. expect(await canSkipCaptcha('a@bc.de')).to.equal(false)
  95. })
  96. it('should flag missing captcha for another email', async function () {
  97. const { response, body } = await loginWithEmailAndCaptcha('a@bc.de', '')
  98. expectBadCaptchaResponse(response, body)
  99. })
  100. describe('login failure', function () {
  101. beforeEach(async function () {
  102. const { response, body } = await login(
  103. user.email,
  104. 'bad password',
  105. 'valid'
  106. )
  107. expectBadLogin(response, body)
  108. })
  109. it('should be able to skip captcha per device history', async function () {
  110. expect(await canSkipCaptcha(user.email)).to.equal(true)
  111. })
  112. it('should request a captcha despite device history entry', async function () {
  113. const { response, body } = await loginWithCaptcha('')
  114. expectBadCaptchaResponse(response, body)
  115. })
  116. it('should accept the login with captcha', async function () {
  117. const { response, body } = await loginWithCaptcha('valid')
  118. expectSuccessfulLogin(response, body)
  119. })
  120. describe('when the login failure happened a long time ago', function () {
  121. beforeEach(async function () {
  122. db.users.updateOne(
  123. { email: user.email },
  124. {
  125. $set: {
  126. lastFailedLogin: new Date(
  127. Date.now() - 90 * 24 * 60 * 60 * 1000
  128. ),
  129. },
  130. }
  131. )
  132. })
  133. it('should be able to skip captcha per device history', async function () {
  134. expect(await canSkipCaptcha(user.email)).to.equal(true)
  135. })
  136. it('should accept the login without captcha', async function () {
  137. const { response, body } = await loginWithCaptcha('')
  138. expectSuccessfulLogin(response, body)
  139. })
  140. it('should accept the login with captcha', async function () {
  141. const { response, body } = await loginWithCaptcha('valid')
  142. expectSuccessfulLogin(response, body)
  143. })
  144. })
  145. })
  146. describe('cycle history', function () {
  147. beforeEach('create and login with 10 other users', async function () {
  148. for (let i = 0; i < 10; i++) {
  149. const otherUser = new User()
  150. otherUser.password = user.password
  151. await otherUser.ensureUserExists()
  152. const { response, body } = await loginWithEmailAndCaptcha(
  153. otherUser.email,
  154. 'valid'
  155. )
  156. expectSuccessfulLogin(response, body)
  157. }
  158. })
  159. it('should have rolled out the initial users email', async function () {
  160. const { response, body } = await loginWithCaptcha('')
  161. expectBadCaptchaResponse(response, body)
  162. })
  163. })
  164. })
  165. })