import rateLimit, { ipKeyGenerator } from 'express-rate-limit' import type { Request } from 'express' import { verifyAnyActorToken } from '../security/tokens' import { getSessionCookieName } from '../security/sessionCookies' const SESSION_COOKIE_NAMES = [ getSessionCookieName('admin'), getSessionCookieName('employee'), getSessionCookieName('renter'), ] function readCookie(cookieHeader: string | undefined, name: string): string | null { if (!cookieHeader) return null for (const chunk of cookieHeader.split(';')) { const [rawName, ...rawValue] = chunk.trim().split('=') if (rawName === name) return decodeURIComponent(rawValue.join('=')) } return null } function getBearerToken(req: Request): string | null { const authHeader = req.headers.authorization if (!authHeader?.startsWith('Bearer ')) return null const token = authHeader.slice(7).trim() return token || null } function getRequestToken(req: Request): string | null { const cookieHeader = req.headers.cookie for (const name of SESSION_COOKIE_NAMES) { const token = readCookie(cookieHeader, name) if (token) return token } return getBearerToken(req) } function getAuthenticatedActorKey(req: Request): string | null { const token = getRequestToken(req) if (!token) return null try { const payload = verifyAnyActorToken(token) return `${payload.type}:${payload.sub}` } catch { return null } } // req.ip is already the real client IP when app.set('trust proxy', 1) is configured const getClientIpKey = (req: Request) => ipKeyGenerator(req.ip ?? '') // Strict limiter for auth endpoints — prevents brute-force and credential stuffing. // Successful requests (e.g. GET /me profile reads) are skipped so only failed // attempts count toward the cap. export const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 20, standardHeaders: 'draft-7', legacyHeaders: false, skipSuccessfulRequests: true, keyGenerator: (req) => getClientIpKey(req), message: { error: 'too_many_requests', message: 'Too many attempts, please try again later', statusCode: 429 }, }) // Standard limiter for general authenticated API endpoints export const apiLimiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 120, standardHeaders: 'draft-7', legacyHeaders: false, keyGenerator: (req) => { const ip = getClientIpKey(req) const actorKey = getAuthenticatedActorKey(req) const companyId = (req as any).companyId ?? '' const renterId = (req as any).renterId ?? '' return `${ip}:${actorKey || companyId || renterId || 'anonymous'}` }, message: { error: 'too_many_requests', message: 'Rate limit exceeded', statusCode: 429 }, }) // Limiter for public marketplace and site endpoints (no auth) export const publicLimiter = rateLimit({ windowMs: 60 * 1000, max: 60, standardHeaders: 'draft-7', legacyHeaders: false, keyGenerator: (req) => getClientIpKey(req), message: { error: 'too_many_requests', message: 'Rate limit exceeded', statusCode: 429 }, }) // Tight limiter for admin endpoints export const adminLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, standardHeaders: 'draft-7', legacyHeaders: false, keyGenerator: (req) => { const ip = getClientIpKey(req) return `${ip}:${getAuthenticatedActorKey(req) || 'anonymous'}` }, message: { error: 'too_many_requests', message: 'Too many admin requests', statusCode: 429 }, }) // Applied after authentication so limits can include actor identity rather than // pretending every employee behind the same NAT is the same organism. export const actorLimiter = rateLimit({ windowMs: 60 * 1000, max: 240, standardHeaders: 'draft-7', legacyHeaders: false, keyGenerator: (req) => { const ip = getClientIpKey(req) const actorKey = getAuthenticatedActorKey(req) const companyId = (req as any).companyId ?? '' const employeeId = (req as any).employee?.id ?? '' const renterId = (req as any).renterId ?? '' const adminId = (req as any).admin?.id ?? '' return `${ip}:${companyId}:${actorKey || employeeId || renterId || adminId || 'anonymous'}` }, message: { error: 'too_many_requests', message: 'Authenticated rate limit exceeded', statusCode: 429 }, })