Add anti-bot registration protection
- Custom /api/register route: honeypot, disposable email blocking, Cloudflare Turnstile verification - Register form: honeypot hidden field, Turnstile widget (loads when NEXT_PUBLIC_TURNSTILE_SITE_KEY set) - Install disposable-email-domains package - Add NEXT_PUBLIC_TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY env vars to .env.example Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const disposableDomains = require("disposable-email-domains") as string[];
|
||||
|
||||
async function verifyTurnstile(token: string): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
secret: process.env.TURNSTILE_SECRET_KEY,
|
||||
response: token,
|
||||
}),
|
||||
});
|
||||
const data = await res.json() as { success: boolean };
|
||||
return data.success;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonError(message: string, status: number) {
|
||||
return new Response(JSON.stringify({ message }), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
let body: Record<string, string>;
|
||||
try {
|
||||
body = await request.json() as Record<string, string>;
|
||||
} catch {
|
||||
return jsonError("Неверный формат запроса", 400);
|
||||
}
|
||||
|
||||
const { name, email, password, website, cfTurnstileResponse } = body;
|
||||
|
||||
// Honeypot — bots fill this field, humans don't see it
|
||||
if (website) {
|
||||
return jsonError("Registration failed", 400);
|
||||
}
|
||||
|
||||
// Disposable email check
|
||||
const domain = email?.split("@")[1]?.toLowerCase();
|
||||
if (domain && disposableDomains.includes(domain)) {
|
||||
return jsonError("Пожалуйста, используйте постоянный email-адрес", 400);
|
||||
}
|
||||
|
||||
// Turnstile check
|
||||
if (process.env.TURNSTILE_SECRET_KEY) {
|
||||
if (!cfTurnstileResponse) {
|
||||
return jsonError("Пожалуйста, подтвердите, что вы не робот", 400);
|
||||
}
|
||||
if (!(await verifyTurnstile(cfTurnstileResponse))) {
|
||||
return jsonError("Проверка капчи не пройдена. Попробуйте ещё раз", 400);
|
||||
}
|
||||
}
|
||||
|
||||
const baseUrl =
|
||||
process.env.BETTER_AUTH_URL ??
|
||||
`https://${request.headers.get("host")}`;
|
||||
|
||||
return auth.handler(
|
||||
new Request(`${baseUrl}/api/auth/sign-up/email`, {
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
"Content-Type": "application/json",
|
||||
Origin: baseUrl,
|
||||
}),
|
||||
body: JSON.stringify({ name, email, password }),
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user