diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts
index 7c4f101..3ab2617 100644
--- a/src/app/api/contact/route.ts
+++ b/src/app/api/contact/route.ts
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
-import { rateLimited, sendEmail } from "@/lib/server-utils";
+import { rateLimited, sendEmail, validateTurnstile } from "@/lib/server-utils";
import { trimTooLong } from "@/lib/strings";
import validator from "validator";
@@ -14,31 +14,24 @@ const validateInput = (data: any) => {
typeof data.name !== "string" ||
typeof data.email !== "string"
) ||
- typeof data.message !== "string"
+ typeof data.message !== "string" ||
+ typeof data["cf-turnstile-response"] !== "string"
) ||
!data.anon &&
(
!data.name.trim() ||
!data.email.trim() ||
!validator.isEmail(data.email) ||
- data.email.length > 30
+ data.email.length > 50
) ||
- !data.message.trim()
+ !data.message.trim(),
+ !data["cf-turnstile-response"].trim()
)
}
export async function POST(req: NextRequest) {
const agent = req.headers.get("x-forwarded-for") ?? "damn";
-
- const isRateLimited = await rateLimited(agent);
- if (isRateLimited) {
- return NextResponse.json(
- { error: "Too many requests" },
- { status: 429 }
- );
- }
-
let data;
try {
data = await req.json();
@@ -49,6 +42,33 @@ export async function POST(req: NextRequest) {
);
}
+ const cf_token = data["cf-turnstile-response"];
+
+ if (typeof cf_token !== 'string' || !cf_token.trim()) {
+ return NextResponse.json(
+ { error: "Captcha Failed" },
+ { status: 403 }
+ );
+ }
+
+ const captchaVerified = await validateTurnstile(cf_token, agent);
+
+ if (!captchaVerified) {
+ return NextResponse.json(
+ { error: "Captcha Failed" },
+ { status: 403 }
+ );
+ }
+
+ const isRateLimited = await rateLimited(agent);
+
+ if (isRateLimited) {
+ return NextResponse.json(
+ { error: "Too many requests" },
+ { status: 429 }
+ );
+ }
+
if (validateInput(data)) {
return NextResponse.json(
{ error: "Invalid input" },
diff --git a/src/app/globals.css b/src/app/globals.css
index 3f4af8d..d3b13de 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -5,6 +5,7 @@
:root {
--background: #0F0A1F;
--primary: #F48120;
+ --primary-darker: #cc6d1f;
}
@keyframes click-bounce {
diff --git a/src/components/form/contact-form.tsx b/src/components/form/contact-form.tsx
index 8a4c3b5..698d08e 100644
--- a/src/components/form/contact-form.tsx
+++ b/src/components/form/contact-form.tsx
@@ -5,6 +5,7 @@ import axios, { AxiosError } from "axios";
import { CheckboxInput, Input, Submit } from "./form";
import { FloatingLabel } from "../floating-label";
+import { CloudflareTurnstile } from "../turnstile";
export default function ContactForm() {
const [anon, setAnon] = useState(false);
@@ -32,7 +33,8 @@ export default function ContactForm() {
anon,
name: formData.get('name')?.toString(),
email: formData.get('email')?.toString(),
- message: formData.get('message')!.toString()
+ message: formData.get('message')!.toString(),
+ "cf-turnstile-response": formData.get('cf-turnstile-response')?.toString()
}
if ((!data.name || !data.email) && !data.anon) {
@@ -44,6 +46,11 @@ export default function ContactForm() {
setErrorMsg("What do you want to tell me???");
return;
}
+
+ if (!data["cf-turnstile-response"]) {
+ setErrorMsg("Please fill in the captcha");
+ return;
+ }
setStatus('loading');
@@ -59,6 +66,7 @@ export default function ContactForm() {
if (err instanceof AxiosError) {
if (err.status == 429) setErrorMsg("Limit reached, please try again later...");
+ if (err.status == 403) setErrorMsg("Captcha failed or expired, please try again later");
}
}
}
@@ -100,6 +108,7 @@ export default function ContactForm() {
placeholder="Tell me something cool, or ask question"
/>
+