RateLimiterMiddleware.test.mjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import { vi } from 'vitest'
  2. import sinon from 'sinon'
  3. const modulePath =
  4. '../../../../app/src/Features/Security/RateLimiterMiddleware.mjs'
  5. describe('RateLimiterMiddleware', function () {
  6. beforeEach(async function (ctx) {
  7. ctx.SessionManager = {
  8. getLoggedInUserId: () => ctx.req.session?.user?._id,
  9. }
  10. vi.doMock('@overleaf/settings', () => ({
  11. default: (ctx.settings = {}),
  12. }))
  13. vi.doMock('../../../../app/src/Features/Security/LoginRateLimiter', () => ({
  14. default: {},
  15. }))
  16. vi.doMock(
  17. '../../../../app/src/Features/Authentication/SessionManager',
  18. () => ({
  19. default: ctx.SessionManager,
  20. })
  21. )
  22. ctx.RateLimiterMiddleware = (await import(modulePath)).default
  23. ctx.req = { params: {} }
  24. ctx.res = {
  25. status: sinon.stub(),
  26. write: sinon.stub(),
  27. end: sinon.stub(),
  28. }
  29. ctx.next = sinon.stub()
  30. })
  31. describe('rateLimit', function () {
  32. beforeEach(function (ctx) {
  33. ctx.projectId = 'project-id'
  34. ctx.docId = 'doc-id'
  35. ctx.rateLimiter = {
  36. consume: sinon.stub().resolves({ remainingPoints: 2 }),
  37. }
  38. ctx.middleware = ctx.RateLimiterMiddleware.rateLimit(ctx.rateLimiter, {
  39. params: ['projectId', 'docId'],
  40. })
  41. ctx.req.params = { projectId: ctx.projectId, docId: ctx.docId }
  42. })
  43. describe('when there is no session', function () {
  44. beforeEach(async function (ctx) {
  45. await new Promise(resolve => {
  46. ctx.req.ip = ctx.ip = '1.2.3.4'
  47. ctx.middleware(ctx.req, ctx.res, () => {
  48. resolve()
  49. })
  50. })
  51. })
  52. it('should call the rate limiter with the ip address', function (ctx) {
  53. ctx.rateLimiter.consume.should.have.been.calledWith(
  54. `${ctx.projectId}:${ctx.docId}:${ctx.ip}`
  55. )
  56. })
  57. })
  58. describe('when smoke test user', function () {
  59. beforeEach(async function (ctx) {
  60. await new Promise(resolve => {
  61. ctx.userId = 'smoke-test-user-id'
  62. ctx.req.session = {
  63. user: { _id: ctx.userId },
  64. }
  65. ctx.settings.smokeTest = { userId: ctx.userId }
  66. ctx.middleware(ctx.req, ctx.res, () => {
  67. resolve()
  68. })
  69. })
  70. })
  71. it('should not call the rate limiter', function (ctx) {
  72. ctx.rateLimiter.consume.should.not.have.been.called
  73. })
  74. })
  75. describe('when under the rate limit with logged in user', function () {
  76. beforeEach(async function (ctx) {
  77. await new Promise(resolve => {
  78. ctx.userId = 'user-id'
  79. ctx.req.session = {
  80. user: { _id: ctx.userId },
  81. }
  82. ctx.middleware(ctx.req, ctx.res, () => {
  83. resolve()
  84. })
  85. })
  86. })
  87. it('should call the rate limiter backend with the userId', function (ctx) {
  88. ctx.rateLimiter.consume.should.have.been.calledWith(
  89. `${ctx.projectId}:${ctx.docId}:${ctx.userId}`
  90. )
  91. })
  92. })
  93. describe('when under the rate limit with anonymous user', function () {
  94. beforeEach(async function (ctx) {
  95. await new Promise(resolve => {
  96. ctx.req.ip = '1.2.3.4'
  97. ctx.middleware(ctx.req, ctx.res, () => {
  98. resolve()
  99. })
  100. })
  101. })
  102. it('should call the rate limiter backend with the ip address', function (ctx) {
  103. ctx.rateLimiter.consume.should.have.been.calledWith(
  104. `${ctx.projectId}:${ctx.docId}:${ctx.req.ip}`
  105. )
  106. })
  107. })
  108. describe('when over the rate limit', function () {
  109. beforeEach(async function (ctx) {
  110. await new Promise(resolve => {
  111. ctx.userId = 'user-id'
  112. ctx.req.session = {
  113. user: { _id: ctx.userId },
  114. }
  115. ctx.res.end.callsFake(() => {
  116. resolve()
  117. })
  118. ctx.rateLimiter.consume.rejects({ remainingPoints: 0 })
  119. ctx.middleware(ctx.req, ctx.res, ctx.next)
  120. })
  121. })
  122. it('should return a 429', function (ctx) {
  123. ctx.res.status.should.have.been.calledWith(429)
  124. })
  125. it('should not continue', function (ctx) {
  126. ctx.next.should.not.have.been.called
  127. })
  128. })
  129. })
  130. })