86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
import NextAuth from "next-auth";
|
|
|
|
import { authOptions } from "~/server/auth";
|
|
import { env } from "~/env";
|
|
import { getRedis } from "~/server/redis";
|
|
import { logger } from "~/server/logger/log";
|
|
|
|
const handler = NextAuth(authOptions);
|
|
|
|
export { handler as GET };
|
|
|
|
function getClientIp(req: Request): string | null {
|
|
const h = req.headers;
|
|
const direct =
|
|
h.get("x-forwarded-for") ??
|
|
h.get("x-real-ip") ??
|
|
h.get("cf-connecting-ip") ??
|
|
h.get("x-client-ip") ??
|
|
h.get("true-client-ip") ??
|
|
h.get("fastly-client-ip") ??
|
|
h.get("x-cluster-client-ip") ??
|
|
null;
|
|
|
|
let ip = direct?.split(",")[0]?.trim() ?? "";
|
|
|
|
if (!ip) {
|
|
const fwd = h.get("forwarded");
|
|
if (fwd) {
|
|
const first = fwd.split(",")[0];
|
|
const match = first?.match(/for=([^;]+)/i);
|
|
if (match && match[1]) {
|
|
const raw = match[1].trim().replace(/^"|"$/g, "");
|
|
if (raw.startsWith("[")) {
|
|
const end = raw.indexOf("]");
|
|
ip = end !== -1 ? raw.slice(1, end) : raw;
|
|
} else {
|
|
const parts = raw.split(":");
|
|
if (parts.length > 0 && parts[0]) {
|
|
ip =
|
|
parts.length === 2 && /^\d+(?:\.\d+){3}$/.test(parts[0])
|
|
? parts[0]
|
|
: raw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ip || null;
|
|
}
|
|
|
|
export async function POST(req: Request, ctx: any) {
|
|
if (env.AUTH_EMAIL_RATE_LIMIT > 0) {
|
|
const url = new URL(req.url);
|
|
if (url.pathname.endsWith("/signin/email")) {
|
|
try {
|
|
const ip = getClientIp(req);
|
|
if (!ip) {
|
|
logger.warn("Auth email rate limit skipped: missing client IP");
|
|
return handler(req, ctx);
|
|
}
|
|
const redis = getRedis();
|
|
const key = `auth-rl:${ip}`;
|
|
const ttl = 60;
|
|
const count = await redis.incr(key);
|
|
if (count === 1) await redis.expire(key, ttl);
|
|
if (count > env.AUTH_EMAIL_RATE_LIMIT) {
|
|
logger.warn({ ip }, "Auth email rate limit exceeded");
|
|
return Response.json(
|
|
{
|
|
error: {
|
|
code: "RATE_LIMITED",
|
|
message: "Too many requests",
|
|
},
|
|
},
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
} catch (error) {
|
|
logger.error({ err: error }, "Auth email rate limit failed");
|
|
}
|
|
}
|
|
}
|
|
return handler(req, ctx);
|
|
}
|