AuthenticationTests.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. const { expect } = require('chai')
  2. const { ObjectId } = require('mongodb-legacy')
  3. const Settings = require('@overleaf/settings')
  4. const User = require('./helpers/User').promises
  5. describe('Authentication', function () {
  6. let user
  7. beforeEach('init vars', function () {
  8. user = new User()
  9. })
  10. describe('CSRF regeneration on login', function () {
  11. it('should prevent use of csrf token from before login', function (done) {
  12. user.logout(err => {
  13. if (err) {
  14. return done(err)
  15. }
  16. user.getCsrfToken(err => {
  17. if (err) {
  18. return done(err)
  19. }
  20. const oldToken = user.csrfToken
  21. user.login(err => {
  22. if (err) {
  23. return done(err)
  24. }
  25. expect(oldToken === user.csrfToken).to.equal(false)
  26. user.request.post(
  27. {
  28. headers: {
  29. 'x-csrf-token': oldToken,
  30. },
  31. url: '/project/new',
  32. json: { projectName: 'test' },
  33. },
  34. (err, response, body) => {
  35. expect(err).to.not.exist
  36. expect(response.statusCode).to.equal(403)
  37. expect(body).to.equal('Forbidden')
  38. done()
  39. }
  40. )
  41. })
  42. })
  43. })
  44. })
  45. })
  46. describe('login', function () {
  47. beforeEach('doLogin', async function () {
  48. await user.login()
  49. })
  50. it('should log the user in', async function () {
  51. const {
  52. response: { statusCode },
  53. } = await user.doRequest('GET', '/project')
  54. expect(statusCode).to.equal(200)
  55. })
  56. it('should emit an user auditLog entry for the login', async function () {
  57. const auditLog = await user.getAuditLog()
  58. const auditLogEntry = auditLog[0]
  59. expect(auditLogEntry).to.exist
  60. expect(auditLogEntry.timestamp).to.exist
  61. expect(auditLogEntry.initiatorId).to.deep.equal(new ObjectId(user.id))
  62. expect(auditLogEntry.userId).to.deep.equal(new ObjectId(user.id))
  63. expect(auditLogEntry.operation).to.equal('login')
  64. expect(auditLogEntry.info).to.deep.equal({
  65. method: 'Password login',
  66. captcha: 'solved',
  67. fromKnownDevice: false,
  68. })
  69. expect(auditLogEntry.ipAddress).to.equal('127.0.0.1')
  70. })
  71. })
  72. describe('failed login', function () {
  73. beforeEach('fetchCsrfToken', async function () {
  74. await user.login()
  75. await user.logout()
  76. await user.getCsrfToken()
  77. })
  78. it('should return a 401, and add an entry to the audit log', async function () {
  79. const {
  80. response: { statusCode },
  81. } = await user.doRequest('POST', {
  82. url: Settings.enableLegacyLogin ? '/login/legacy' : '/login',
  83. json: {
  84. email: user.email,
  85. password: 'foo-bar-baz',
  86. 'g-recaptcha-response': 'valid',
  87. },
  88. })
  89. expect(statusCode).to.equal(401)
  90. const auditLog = await user.getAuditLog()
  91. const auditLogEntry = auditLog.pop()
  92. expect(auditLogEntry).to.exist
  93. expect(auditLogEntry.timestamp).to.exist
  94. expect(auditLogEntry.initiatorId).to.deep.equal(new ObjectId(user.id))
  95. expect(auditLogEntry.userId).to.deep.equal(new ObjectId(user.id))
  96. expect(auditLogEntry.operation).to.equal('failed-password-match')
  97. expect(auditLogEntry.info).to.deep.equal({
  98. method: 'Password login',
  99. fromKnownDevice: true,
  100. })
  101. expect(auditLogEntry.ipAddress).to.equal('127.0.0.1')
  102. })
  103. })
  104. describe('rate-limit', function () {
  105. beforeEach('fetchCsrfToken', async function () {
  106. await user.login()
  107. await user.logout()
  108. await user.getCsrfToken()
  109. })
  110. const tryLogin = async (i = 0) => {
  111. const {
  112. response: { statusCode },
  113. } = await user.doRequest('POST', {
  114. url: Settings.enableLegacyLogin ? '/login/legacy' : '/login',
  115. json: {
  116. email: `${user.email}${' '.repeat(i)}`,
  117. password: 'wrong-password',
  118. 'g-recaptcha-response': 'valid',
  119. },
  120. })
  121. return statusCode
  122. }
  123. it('should return 429 after 10 unsuccessful login attempts', async function () {
  124. for (let i = 0; i < 10; i++) {
  125. const statusCode = await tryLogin()
  126. expect(statusCode).to.equal(401)
  127. }
  128. for (let i = 0; i < 10; i++) {
  129. const statusCode = await tryLogin()
  130. expect(statusCode).to.equal(429)
  131. }
  132. })
  133. it('ignore extra spaces in email address', async function () {
  134. for (let i = 0; i < 10; i++) {
  135. const statusCode = await tryLogin(i)
  136. expect(statusCode).to.equal(401)
  137. }
  138. for (let i = 0; i < 10; i++) {
  139. const statusCode = await tryLogin(i)
  140. expect(statusCode).to.equal(429)
  141. }
  142. })
  143. })
  144. })