CaptchaTests.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. const auditLog = await user.getAuditLog()
  69. expect(auditLog[0].info).to.deep.equal({
  70. captcha: 'solved',
  71. method: 'Password login',
  72. })
  73. })
  74. describe('deviceHistory', function () {
  75. beforeEach('login', async function () {
  76. const { response, body } = await loginWithCaptcha('valid')
  77. expectSuccessfulLogin(response, body)
  78. })
  79. it('should be able to skip captcha with the same email', async function () {
  80. expect(await canSkipCaptcha(user.email)).to.equal(true)
  81. })
  82. it('should be able to omit captcha with the same email', async function () {
  83. const { response, body } = await loginWithCaptcha('')
  84. expectSuccessfulLogin(response, body)
  85. })
  86. it('should note the skipped captcha in audit log', async function () {
  87. const { response, body } = await loginWithCaptcha('')
  88. expectSuccessfulLogin(response, body)
  89. const auditLog = await user.getAuditLog()
  90. expect(auditLog[1].info).to.deep.equal({
  91. captcha: 'skipped',
  92. method: 'Password login',
  93. })
  94. })
  95. it('should request a captcha for another email', async function () {
  96. expect(await canSkipCaptcha('a@bc.de')).to.equal(false)
  97. })
  98. it('should flag missing captcha for another email', async function () {
  99. const { response, body } = await loginWithEmailAndCaptcha('a@bc.de', '')
  100. expectBadCaptchaResponse(response, body)
  101. })
  102. describe('login failure', function () {
  103. beforeEach(async function () {
  104. const { response, body } = await login(
  105. user.email,
  106. 'bad password',
  107. 'valid'
  108. )
  109. expectBadLogin(response, body)
  110. })
  111. it('should be able to skip captcha per device history', async function () {
  112. expect(await canSkipCaptcha(user.email)).to.equal(true)
  113. })
  114. it('should request a captcha despite device history entry', async function () {
  115. const { response, body } = await loginWithCaptcha('')
  116. expectBadCaptchaResponse(response, body)
  117. })
  118. it('should accept the login with captcha', async function () {
  119. const { response, body } = await loginWithCaptcha('valid')
  120. expectSuccessfulLogin(response, body)
  121. })
  122. describe('when the login failure happened a long time ago', function () {
  123. beforeEach(async function () {
  124. db.users.updateOne(
  125. { email: user.email },
  126. {
  127. $set: {
  128. lastFailedLogin: new Date(
  129. Date.now() - 90 * 24 * 60 * 60 * 1000
  130. ),
  131. },
  132. }
  133. )
  134. })
  135. it('should be able to skip captcha per device history', async function () {
  136. expect(await canSkipCaptcha(user.email)).to.equal(true)
  137. })
  138. it('should accept the login without captcha', async function () {
  139. const { response, body } = await loginWithCaptcha('')
  140. expectSuccessfulLogin(response, body)
  141. })
  142. it('should accept the login with captcha', async function () {
  143. const { response, body } = await loginWithCaptcha('valid')
  144. expectSuccessfulLogin(response, body)
  145. })
  146. })
  147. })
  148. describe('cycle history', function () {
  149. beforeEach('create and login with 10 other users', async function () {
  150. for (let i = 0; i < 10; i++) {
  151. const otherUser = new User()
  152. otherUser.password = user.password
  153. await otherUser.ensureUserExists()
  154. const { response, body } = await loginWithEmailAndCaptcha(
  155. otherUser.email,
  156. 'valid'
  157. )
  158. expectSuccessfulLogin(response, body)
  159. }
  160. })
  161. it('should have rolled out the initial users email', async function () {
  162. const { response, body } = await loginWithCaptcha('')
  163. expectBadCaptchaResponse(response, body)
  164. })
  165. })
  166. })
  167. })