Files
route-commerce/src/lib/rate-limit.ts
T
tyler 7dfaba6e3d feat: complete launch & marketing layer
- Add marketing pages: blog, changelog, roadmap, waitlist, security, maintenance
- Add GDPR cookie consent banner with preference modal
- Add referral system with API routes for code generation/tracking
- Add waitlist API with referral support
- Add PWA support: manifest, service worker, install prompt
- Add onboarding flow with 6-step guided tour
- Add in-app notification center with bell dropdown
- Add admin launch checklist (32 items across 8 categories)
- Update landing page with trust badges
- Add Open Graph image and favicon
- Configure ESLint for PWA install patterns
- Add LAUNCH_CHECKLIST.md with go-to-market guide

Supabase migrations for:
- blog_posts and blog_categories tables
- waitlist_signups table
- roadmap_items table
- launch_checklist_items table
2026-06-02 06:19:56 +00:00

91 lines
2.8 KiB
TypeScript

// Rate Limiting Configuration
import { NextResponse } from "next/server";
// Rate limit configuration
type Duration = `${number}${"s" | "m" | "h"}`;
interface RateLimitConfig {
windowMs: number;
max: number;
}
// Rate limit definitions
const RATE_LIMITS: Record<string, RateLimitConfig> = {
api: { windowMs: 60000, max: 100 }, // 100 requests per minute
checkout: { windowMs: 300000, max: 20 }, // 20 checkouts per 5 minutes
email: { windowMs: 600000, max: 50 }, // 50 emails per 10 minutes
auth: { windowMs: 900000, max: 10 }, // 10 auth attempts per 15 minutes
};
// Simple in-memory store for rate limiting (use Redis in production)
const rateLimitStore = new Map<string, { count: number; reset: number }>();
// Clean up old entries periodically
setInterval(() => {
const now = Date.now();
for (const [key, value] of rateLimitStore.entries()) {
if (value.reset < now) {
rateLimitStore.delete(key);
}
}
}, 60000);
export async function checkRateLimit(
limiter: { windowMs: number; max: number },
identifier: string
): Promise<{ success: boolean; remaining: number; reset: number }> {
const key = `rate_limit_${identifier}`;
const now = Date.now();
const windowStart = now - limiter.windowMs;
const current = rateLimitStore.get(key);
if (!current || current.reset < now) {
// New window
rateLimitStore.set(key, { count: 1, reset: now + limiter.windowMs });
return { success: true, remaining: limiter.max - 1, reset: now + limiter.windowMs };
}
if (current.count >= limiter.max) {
// Rate limit exceeded
return { success: false, remaining: 0, reset: current.reset };
}
// Increment count
current.count++;
rateLimitStore.set(key, current);
return { success: true, remaining: limiter.max - current.count, reset: current.reset };
}
export function rateLimitExceeded(seconds: number) {
return NextResponse.json(
{ error: "Rate limit exceeded. Please try again later.", retry_after: seconds },
{ status: 429, headers: { "Retry-After": String(seconds) } }
);
}
export function securityHeaders() {
return {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"Content-Security-Policy": "default-src 'self'",
} as const;
}
export function rateLimitHeaders(rateCheck: { remaining: number; reset: number }) {
return {
"X-RateLimit-Limit": String(100),
"X-RateLimit-Remaining": String(rateCheck.remaining),
"X-RateLimit-Reset": String(Math.ceil(rateCheck.reset / 1000)),
} as const;
}
// Pre-configured limiters
export const apiLimiter = RATE_LIMITS.api;
export const checkoutLimiter = RATE_LIMITS.checkout;
export const emailLimiter = RATE_LIMITS.email;
export const authLimiter = RATE_LIMITS.auth;