Skip to content

Commit 1483cbc

Browse files
committed
feat: implement API rate limiting middleware for enhanced security
1 parent 6949366 commit 1483cbc

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Most recruiting software holds your candidate data hostage behind per-seat prici
4242
- **Multi-tenant organizations** — Isolated data per organization with role-based membership
4343
- **Recruiter dashboard** — At-a-glance stats, pipeline breakdown, recent applications, and top active jobs
4444
- **Server-proxied documents** — Resumes are never exposed via public URLs; all access is authenticated and streamed
45+
- **API rate limiting** — Global per-IP limits on all `/api` endpoints with stricter auth/write thresholds
4546

4647
## Quick Start
4748

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { createRateLimiter } from '../utils/rateLimit'
2+
3+
const SAFE_METHODS = new Set(['GET', 'HEAD'])
4+
const SKIP_METHODS = new Set(['OPTIONS'])
5+
6+
// Baseline global API limits (per IP)
7+
const globalReadLimiter = createRateLimiter({
8+
windowMs: 60 * 1000,
9+
maxRequests: 300,
10+
message: 'Too many API requests. Please try again shortly.',
11+
})
12+
13+
const globalWriteLimiter = createRateLimiter({
14+
windowMs: 60 * 1000,
15+
maxRequests: 80,
16+
message: 'Too many write requests. Please try again shortly.',
17+
})
18+
19+
// Auth endpoints get their own buckets to reduce brute-force risk without
20+
// starving the rest of the API traffic from the same IP.
21+
const authReadLimiter = createRateLimiter({
22+
windowMs: 5 * 60 * 1000,
23+
maxRequests: 600,
24+
message: 'Too many auth requests. Please try again shortly.',
25+
})
26+
27+
const authWriteLimiter = createRateLimiter({
28+
windowMs: 5 * 60 * 1000,
29+
maxRequests: 40,
30+
message: 'Too many sign-in attempts. Please wait before trying again.',
31+
})
32+
33+
export default defineEventHandler(async (event) => {
34+
const path = getRequestURL(event).pathname
35+
if (!path.startsWith('/api/')) return
36+
37+
const method = event.method.toUpperCase()
38+
if (SKIP_METHODS.has(method)) return
39+
40+
if (path.startsWith('/api/auth/')) {
41+
if (SAFE_METHODS.has(method)) {
42+
await authReadLimiter(event)
43+
return
44+
}
45+
46+
await authWriteLimiter(event)
47+
return
48+
}
49+
50+
if (SAFE_METHODS.has(method)) {
51+
await globalReadLimiter(event)
52+
return
53+
}
54+
55+
await globalWriteLimiter(event)
56+
})

0 commit comments

Comments
 (0)