feat(security): add Redis-backed rate limiting and CSP/security headers
Public go-live hardening: Redis-backed rate limiting on the previously invite-gated public surface (explore browsing, post/comment creation, invite-cookie routes) plus CSP, HSTS, and standard security headers on every response, now that the invite gate no longer filters traffic.
This commit is contained in:
1 parent
ec9573f33a
commit
f43541a4cc
11 files changed
+162
-8
No files matched your search
@@ -0,0 +1,10 @@
|
||||
import "server-only";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export function getClientIp(request: NextRequest): string {
|
||||
return (
|
||||
request.headers.get("cf-connecting-ip") ??
|
||||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
|
||||
"unknown"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import "server-only";
|
||||
import { redis } from "@/lib/redis";
|
||||
|
||||
interface RateLimitOptions {
|
||||
/** Unique key identifying the caller + the action being limited, e.g. "posts.create:user_123". */
|
||||
key: string;
|
||||
/** Max requests allowed within the window. */
|
||||
limit: number;
|
||||
windowSeconds: number;
|
||||
}
|
||||
|
||||
interface RateLimitResult {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed-window rate limiter backed by Redis INCR/EXPIRE.
|
||||
* Fails open (allows the request) if Redis is unreachable — a rate-limit
|
||||
* outage must not take the whole app down with it.
|
||||
*/
|
||||
export async function checkRateLimit({ key, limit, windowSeconds }: RateLimitOptions): Promise<RateLimitResult> {
|
||||
try {
|
||||
const redisKey = `ratelimit:${key}`;
|
||||
const count = await redis.incr(redisKey);
|
||||
if (count === 1) {
|
||||
await redis.expire(redisKey, windowSeconds);
|
||||
}
|
||||
return { success: count <= limit };
|
||||
} catch {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user