Csrf.test.mjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { beforeEach, describe, expect, it, vi } from 'vitest'
  2. import sinon from 'sinon'
  3. const modulePath = '../../../../app/src/infrastructure/Csrf.mjs'
  4. describe('Csrf', function () {
  5. beforeEach(async function (ctx) {
  6. ctx.csurf_csrf = sinon
  7. .stub()
  8. .callsArgWith(2, (ctx.err = { code: 'EBADCSRFTOKEN' }))
  9. vi.doMock('csurf', () => ({
  10. default: sinon.stub().returns(ctx.csurf_csrf),
  11. }))
  12. const module = await import(modulePath)
  13. ctx.Csrf = module.default
  14. ctx.CsrfClass = module.Csrf
  15. ctx.csrf = new ctx.CsrfClass()
  16. ctx.next = sinon.stub()
  17. ctx.path = '/foo/bar'
  18. ctx.req = {
  19. path: ctx.path,
  20. method: 'POST',
  21. }
  22. ctx.res = {}
  23. })
  24. describe('the middleware', function () {
  25. describe('when there are no excluded routes', function () {
  26. it('passes the csrf error on', function (ctx) {
  27. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  28. return expect(ctx.next.calledWith(ctx.err)).to.equal(true)
  29. })
  30. })
  31. describe('when the route is excluded', function () {
  32. it('does not pass the csrf error on', function (ctx) {
  33. ctx.csrf.disableDefaultCsrfProtection(ctx.path, 'POST')
  34. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  35. return expect(ctx.next.calledWith(ctx.err)).to.equal(false)
  36. })
  37. })
  38. describe('when there is a partial route match', function () {
  39. it('passes the csrf error on when the match is too short', function (ctx) {
  40. ctx.csrf.disableDefaultCsrfProtection('/foo', 'POST')
  41. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  42. return expect(ctx.next.calledWith(ctx.err)).to.equal(true)
  43. })
  44. it('passes the csrf error on when the match is too long', function (ctx) {
  45. ctx.csrf.disableDefaultCsrfProtection('/foo/bar/baz', 'POST')
  46. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  47. return expect(ctx.next.calledWith(ctx.err)).to.equal(true)
  48. })
  49. })
  50. describe('when there are multiple exclusions', function () {
  51. it('does not pass the csrf error on when the match is present', function (ctx) {
  52. ctx.csrf.disableDefaultCsrfProtection(ctx.path, 'POST')
  53. ctx.csrf.disableDefaultCsrfProtection('/test', 'POST')
  54. ctx.csrf.disableDefaultCsrfProtection('/a/b/c', 'POST')
  55. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  56. return expect(ctx.next.calledWith(ctx.err)).to.equal(false)
  57. })
  58. it('passes the csrf error on when the match is not present', function (ctx) {
  59. ctx.csrf.disableDefaultCsrfProtection('/url', 'POST')
  60. ctx.csrf.disableDefaultCsrfProtection('/test', 'POST')
  61. ctx.csrf.disableDefaultCsrfProtection('/a/b/c', 'POST')
  62. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  63. return expect(ctx.next.calledWith(ctx.err)).to.equal(true)
  64. })
  65. })
  66. describe('when the method does not match', function () {
  67. it('passes the csrf error on', function (ctx) {
  68. ctx.csrf.disableDefaultCsrfProtection(ctx.path, 'POST')
  69. ctx.req.method = 'GET'
  70. ctx.csrf.middleware(ctx.req, ctx.res, ctx.next)
  71. return expect(ctx.next.calledWith(ctx.err)).to.equal(true)
  72. })
  73. })
  74. describe('when the route is excluded, but the error is not a bad-csrf-token error', function () {
  75. it('passes the error on', async function (ctx) {
  76. const err = { code: 'EOTHER' }
  77. ctx.csurf_csrf.callsArgWith(2, err)
  78. const csrf = new ctx.CsrfClass()
  79. csrf.disableDefaultCsrfProtection(ctx.path, 'POST')
  80. csrf.middleware(ctx.req, ctx.res, ctx.next)
  81. expect(ctx.next.calledWith(err)).to.equal(true)
  82. expect(ctx.next.calledWith(ctx.err)).to.equal(false)
  83. })
  84. })
  85. })
  86. describe('validateRequest', function () {
  87. describe('when the request is invalid', function () {
  88. it('rejects the promise', async function (ctx) {
  89. await expect(
  90. ctx.Csrf.promises.validateRequest(ctx.req)
  91. ).to.be.rejectedWith(ctx.err)
  92. })
  93. })
  94. describe('when the request is valid', function () {
  95. it('resolves the promise', async function (ctx) {
  96. vi.resetModules()
  97. vi.doMock('csurf', () => ({
  98. default: sinon.stub().returns(sinon.stub().callsArg(2)),
  99. }))
  100. ctx.Csrf = (await import(modulePath)).default
  101. await expect(ctx.Csrf.promises.validateRequest(ctx.req)).to.eventually
  102. .be.fulfilled
  103. })
  104. })
  105. })
  106. describe('validateToken', function () {
  107. describe('when the request is invalid', function () {
  108. it('rejects the promise', async function (ctx) {
  109. await expect(
  110. ctx.Csrf.promises.validateToken('token', {})
  111. ).to.be.rejectedWith(ctx.err)
  112. })
  113. })
  114. describe('when the request is valid', function () {
  115. it('resolves the promise', async function (ctx) {
  116. vi.resetModules()
  117. vi.doMock('csurf', () => ({
  118. default: sinon.stub().returns(sinon.stub().callsArg(2)),
  119. }))
  120. ctx.Csrf = (await import(modulePath)).default
  121. await expect(ctx.Csrf.promises.validateToken('goodtoken', {})).to
  122. .eventually.be.fulfilled
  123. })
  124. })
  125. describe('when there is no token', function () {
  126. it('throws an error', async function (ctx) {
  127. vi.doMock('csurf', () => ({
  128. default: (ctx.csurf = sinon
  129. .stub()
  130. .returns((ctx.csurf_csrf = sinon.stub().callsArg(2)))),
  131. }))
  132. ctx.Csrf = (await import(modulePath)).default
  133. await expect(
  134. ctx.Csrf.promises.validateToken(null, {})
  135. ).to.be.rejectedWith('missing token')
  136. })
  137. })
  138. })
  139. })