Files
brainwave/backend/src/middleware/auth.test.ts
T
LaurensBartelds ecbf842b3b
Test / backend (push) Successful in 15s
Test / frontend (push) Successful in 27s
Add detailed implementation plan and tests for 2026 security audit fixes
2026-08-22 00:16:13 +02:00

128 lines
4.0 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest'
import type { Request, Response, NextFunction } from 'express'
import * as userRepo from '../repositories/userRepo.js'
import { signToken } from '../utils/tokens.js'
import { requireAuth } from './auth.js'
function mockRes(): Response {
const res = {} as Response
res.status = vi.fn(() => res) as unknown as Response['status']
res.json = vi.fn(() => res) as unknown as Response['json']
return res
}
function makeReq(token?: string): Request {
const headers: Record<string, string> = {}
if (token) headers.authorization = `Bearer ${token}`
return { headers } as unknown as Request
}
function createUser(role: 'user' | 'admin' = 'user'): { id: number; email: string; name: string } {
const email = `user-${Math.random().toString(36).slice(2)}@example.com`
const { id } = userRepo.createUser({
email,
passwordHash: 'x',
displayName: 'Test User',
role,
emailVerified: 1,
})
return { id, email, name: 'Test User' }
}
describe('requireAuth', () => {
it('accepts a valid token and loads fresh user data from the DB', () => {
const u = createUser()
const token = signToken({ userId: u.id, email: u.email, displayName: u.name, role: 'user', tv: 0 })
const req = makeReq(token)
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).toHaveBeenCalledOnce()
expect(res.status).not.toHaveBeenCalled()
expect(req.user?.userId).toBe(u.id)
expect(req.user?.role).toBe('user')
expect(req.user?.tv).toBe(0)
})
it('serves the current DB role, not the role claimed by an old JWT', () => {
const u = createUser()
// Token signed while the user was still an admin
const staleAdminToken = signToken({ userId: u.id, email: u.email, displayName: u.name, role: 'admin', tv: 0 })
userRepo.updateUser(u.id, { role: 'user' })
const req = makeReq(staleAdminToken)
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).toHaveBeenCalledOnce()
expect(req.user?.role).toBe('user')
})
it('rejects tokens for deleted accounts', () => {
const u = createUser()
const token = signToken({ userId: u.id, email: u.email, displayName: u.name, role: 'user', tv: 0 })
userRepo.deleteUser(u.id)
const req = makeReq(token)
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).not.toHaveBeenCalled()
expect(res.status).toHaveBeenCalledWith(401)
})
it('rejects tokens whose token version is stale (e.g. after password change)', () => {
const u = createUser()
const oldToken = signToken({ userId: u.id, email: u.email, displayName: u.name, role: 'user', tv: 0 })
userRepo.updateUser(u.id, { tokenVersion: 1 })
const req = makeReq(oldToken)
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).not.toHaveBeenCalled()
expect(res.status).toHaveBeenCalledWith(401)
})
it('accepts a token signed with the current token version after rotation', () => {
const u = createUser()
userRepo.updateUser(u.id, { tokenVersion: 2 })
const freshToken = signToken({ userId: u.id, email: u.email, displayName: u.name, role: 'user', tv: 2 })
const req = makeReq(freshToken)
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).toHaveBeenCalledOnce()
expect(req.user?.tv).toBe(2)
})
it('rejects requests without a Bearer header', () => {
const req = makeReq()
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).not.toHaveBeenCalled()
expect(res.status).toHaveBeenCalledWith(401)
})
it('rejects garbage tokens', () => {
const req = makeReq('not.a.jwt')
const res = mockRes()
const next = vi.fn() as NextFunction
requireAuth(req, res, next)
expect(next).not.toHaveBeenCalled()
expect(res.status).toHaveBeenCalledWith(401)
})
})