import { describe, it, expect, beforeEach } from 'vitest' import { createMockEvent } from '../helpers/createMockEvent.js' // Import the handler — defineEventHandler is a global passthrough, // so the default export is the raw handler function. import csrfMiddleware from '../../../server/middleware/01.csrf.js' describe('CSRF middleware', () => { describe('safe methods bypass', () => { it.each(['GET', 'HEAD', 'OPTIONS'])('%s requests pass through', (method) => { const event = createMockEvent({ method, path: '/api/test' }) expect(() => csrfMiddleware(event)).not.toThrow() }) }) describe('non-API paths bypass', () => { it('POST to non-/api/ path passes through', () => { const event = createMockEvent({ method: 'POST', path: '/some/page' }) expect(() => csrfMiddleware(event)).not.toThrow() }) }) describe('exempt routes bypass', () => { it.each([ '/api/helcim/webhook', '/api/slack/webhook', '/api/auth/verify' ])('POST to %s passes through', (path) => { const event = createMockEvent({ method: 'POST', path }) expect(() => csrfMiddleware(event)).not.toThrow() }) }) describe('CSRF enforcement on state-changing API requests', () => { it('throws 403 when POST to /api/* has no x-csrf-token header', () => { const event = createMockEvent({ method: 'POST', path: '/api/members/profile', cookies: { 'csrf-token': 'abc123' } }) expect(() => csrfMiddleware(event)).toThrowError( expect.objectContaining({ statusCode: 403, statusMessage: 'CSRF token missing or invalid' }) ) }) it('throws 403 when header and cookie tokens do not match', () => { const event = createMockEvent({ method: 'POST', path: '/api/members/profile', cookies: { 'csrf-token': 'cookie-token' }, headers: { 'x-csrf-token': 'different-token' } }) expect(() => csrfMiddleware(event)).toThrowError( expect.objectContaining({ statusCode: 403, statusMessage: 'CSRF token missing or invalid' }) ) }) it('passes when header and cookie tokens match', () => { const token = 'matching-token-value' const event = createMockEvent({ method: 'POST', path: '/api/members/profile', cookies: { 'csrf-token': token }, headers: { 'x-csrf-token': token } }) expect(() => csrfMiddleware(event)).not.toThrow() }) it('enforces CSRF on DELETE requests', () => { const event = createMockEvent({ method: 'DELETE', path: '/api/admin/events/123', cookies: { 'csrf-token': 'abc' } }) expect(() => csrfMiddleware(event)).toThrowError( expect.objectContaining({ statusCode: 403 }) ) }) it('enforces CSRF on PATCH requests', () => { const event = createMockEvent({ method: 'PATCH', path: '/api/members/profile', cookies: { 'csrf-token': 'abc' } }) expect(() => csrfMiddleware(event)).toThrowError( expect.objectContaining({ statusCode: 403 }) ) }) }) describe('cookie provisioning', () => { it('sets csrf-token cookie when none exists', () => { const event = createMockEvent({ method: 'GET', path: '/api/test' }) csrfMiddleware(event) // The middleware calls setCookie which sets a Set-Cookie header const setCookieHeader = event._testSetHeaders['set-cookie'] expect(setCookieHeader).toBeDefined() expect(setCookieHeader).toContain('csrf-token=') }) it('does not set a new cookie when one already exists', () => { const event = createMockEvent({ method: 'GET', path: '/api/test', cookies: { 'csrf-token': 'existing-token' } }) csrfMiddleware(event) const setCookieHeader = event._testSetHeaders['set-cookie'] // Should not have set a new cookie expect(setCookieHeader).toBeUndefined() }) }) })