(req: NextRequest)
| 147 | // Middleware |
| 148 | // --------------------------------------------------------------------------- |
| 149 | export function middleware(req: NextRequest) { |
| 150 | const { pathname } = req.nextUrl; |
| 151 | const method = req.method; |
| 152 | |
| 153 | // --- Admin auth check --- |
| 154 | if (requiresAuth(pathname, method)) { |
| 155 | if (!isAuthorized(req)) { |
| 156 | const res = NextResponse.json({ error: "Unauthorized" }, { status: 401 }); |
| 157 | applySecurityHeaders(res); |
| 158 | return res; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | // --- Rate limiting (API routes only, exempt health check) --- |
| 163 | if (pathname.startsWith("/api/") && pathname !== "/api/health") { |
| 164 | const ip = getClientIP(req); |
| 165 | const tier = getTier(pathname); |
| 166 | const rl = checkRateLimit(ip, tier); |
| 167 | |
| 168 | if (!rl.allowed) { |
| 169 | const res = NextResponse.json( |
| 170 | { error: "Too many requests" }, |
| 171 | { status: 429 } |
| 172 | ); |
| 173 | res.headers.set("Retry-After", String(rl.retryAfter)); |
| 174 | res.headers.set("X-RateLimit-Limit", String(rl.limit)); |
| 175 | res.headers.set("X-RateLimit-Remaining", "0"); |
| 176 | res.headers.set("X-RateLimit-Reset", String(Math.ceil(rl.resetAt / 1000))); |
| 177 | applySecurityHeaders(res); |
| 178 | return res; |
| 179 | } |
| 180 | |
| 181 | // Continue with rate limit headers on success |
| 182 | const res = NextResponse.next(); |
| 183 | res.headers.set("X-RateLimit-Limit", String(rl.limit)); |
| 184 | res.headers.set("X-RateLimit-Remaining", String(rl.remaining)); |
| 185 | res.headers.set("X-RateLimit-Reset", String(Math.ceil(rl.resetAt / 1000))); |
| 186 | applySecurityHeaders(res); |
| 187 | return res; |
| 188 | } |
| 189 | |
| 190 | // --- Non-API routes: just add security headers --- |
| 191 | const res = NextResponse.next(); |
| 192 | applySecurityHeaders(res); |
| 193 | return res; |
| 194 | } |
| 195 | |
| 196 | export const config = { |
| 197 | matcher: [ |
nothing calls this directly
no test coverage detected