- ST-601/602: loading skeletons en error boundary - ST-603: Sonner toasts op alle CRUD-operaties - ST-604: DemoTooltip op uitgeschakelde knoppen - ST-605: KeyboardSensor dnd-kit, Escape sluit modals - ST-606: min-width banner < 1024px - ST-607: WCAG AA aria-labels en skip link - ST-608: rate limiting login (10/min) en registratie (5/uur) - ST-609: security integratietests cross-user toegang (7 tests) - ST-610: GitHub Actions CI/CD workflow - ST-611: README met quickstart, deployment en API-docs - ST-612: Lars-flow acceptatiechecklist - fix: settings toont gebruikersnaam i.p.v. interne id - fix: seed idempotent, testdata altijd gekoppeld aan demo-gebruiker Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
36 lines
999 B
TypeScript
36 lines
999 B
TypeScript
// Simple in-memory rate limiter.
|
|
// Note: resets on server restart and does not share state across multiple processes.
|
|
// Suitable for MVP; replace with Redis for production scale-out.
|
|
|
|
interface RateLimitConfig {
|
|
windowMs: number
|
|
max: number
|
|
}
|
|
|
|
const CONFIGS: Record<string, RateLimitConfig> = {
|
|
login: { windowMs: 60_000, max: 10 }, // 10 attempts per minute
|
|
register: { windowMs: 3_600_000, max: 5 }, // 5 attempts per hour
|
|
}
|
|
|
|
const DEFAULT_CONFIG: RateLimitConfig = { windowMs: 60_000, max: 10 }
|
|
|
|
const store = new Map<string, { count: number; resetAt: number }>()
|
|
|
|
export function checkRateLimit(key: string): boolean {
|
|
const prefix = key.split(':')[0]
|
|
const config = CONFIGS[prefix] ?? DEFAULT_CONFIG
|
|
const now = Date.now()
|
|
const entry = store.get(key)
|
|
|
|
if (!entry || now > entry.resetAt) {
|
|
store.set(key, { count: 1, resetAt: now + config.windowMs })
|
|
return true
|
|
}
|
|
|
|
if (entry.count >= config.max) {
|
|
return false
|
|
}
|
|
|
|
entry.count++
|
|
return true
|
|
}
|