mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
coderabbit comments
This commit is contained in:
@@ -135,10 +135,7 @@ async function validateCSRFToken(request: NextRequest): Promise<boolean> {
|
|||||||
const tokenTime = parseInt(timestamp);
|
const tokenTime = parseInt(timestamp);
|
||||||
if (now - tokenTime > TOKEN_EXPIRY) return false;
|
if (now - tokenTime > TOKEN_EXPIRY) return false;
|
||||||
|
|
||||||
const expectedSignature = crypto
|
const expectedSignature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex");
|
||||||
.createHmac("sha256", env.BETTER_AUTH_SECRET || "fallback-secret")
|
|
||||||
.update(`${token}:${timestamp}`)
|
|
||||||
.digest("hex");
|
|
||||||
|
|
||||||
return signature === expectedSignature;
|
return signature === expectedSignature;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { env } from "@/env";
|
|||||||
|
|
||||||
const CSRF_TOKEN_NAME = "waitlist-csrf";
|
const CSRF_TOKEN_NAME = "waitlist-csrf";
|
||||||
const TOKEN_EXPIRY = 60 * 60 * 1000;
|
const TOKEN_EXPIRY = 60 * 60 * 1000;
|
||||||
|
const allowedHosts = env.NODE_ENV === "development" ? ["localhost:3000", "127.0.0.1:3000"] : ["opencut.app", "www.opencut.app"];
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
const referer = request.headers.get("referer");
|
const referer = request.headers.get("referer");
|
||||||
@@ -12,14 +13,11 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
if (referer) {
|
if (referer) {
|
||||||
const refererUrl = new URL(referer);
|
const refererUrl = new URL(referer);
|
||||||
const allowedHosts = env.NODE_ENV === "development" ? ["localhost:3000", "127.0.0.1:3000"] : ["opencut.app", "www.opencut.app"];
|
|
||||||
|
|
||||||
if (!allowedHosts.some((allowed) => refererUrl.host === allowed || refererUrl.host.endsWith(allowed))) {
|
if (!allowedHosts.some((allowed) => refererUrl.host === allowed || refererUrl.host.endsWith(allowed))) {
|
||||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||||
}
|
}
|
||||||
} else if (host) {
|
} else if (host) {
|
||||||
const allowedHosts = env.NODE_ENV === "development" ? ["localhost:3000", "127.0.0.1:3000"] : ["opencut.app", "www.opencut.app"];
|
|
||||||
|
|
||||||
if (!allowedHosts.some((allowed) => host === allowed || host.endsWith(allowed))) {
|
if (!allowedHosts.some((allowed) => host === allowed || host.endsWith(allowed))) {
|
||||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||||
}
|
}
|
||||||
@@ -27,12 +25,13 @@ export async function GET(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!env.BETTER_AUTH_SECRET) {
|
||||||
|
throw new Error("BETTER_AUTH_SECRET must be configured");
|
||||||
|
}
|
||||||
|
|
||||||
const token = crypto.randomBytes(32).toString("hex");
|
const token = crypto.randomBytes(32).toString("hex");
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const signature = crypto
|
const signature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex");
|
||||||
.createHmac("sha256", env.BETTER_AUTH_SECRET || "fallback-secret")
|
|
||||||
.update(`${token}:${timestamp}`)
|
|
||||||
.digest("hex");
|
|
||||||
|
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
cookieStore.set(CSRF_TOKEN_NAME, `${token}:${timestamp}:${signature}`, {
|
cookieStore.set(CSRF_TOKEN_NAME, `${token}:${timestamp}:${signature}`, {
|
||||||
|
|||||||
@@ -16,16 +16,24 @@ export function Hero() {
|
|||||||
const [csrfToken, setCsrfToken] = useState<string | null>(null);
|
const [csrfToken, setCsrfToken] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
fetch("/api/waitlist/token", {
|
fetch("/api/waitlist/token", {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.token) {
|
if (isMounted && data.token) {
|
||||||
setCsrfToken(data.token);
|
setCsrfToken(data.token);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => console.error("Failed to fetch CSRF token:", err));
|
.catch((err) => {
|
||||||
|
console.error("Failed to fetch CSRF token:", err);
|
||||||
|
if (isMounted) {
|
||||||
|
toast.error("Security initialization failed", {
|
||||||
|
description: "Please refresh the page to continue.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -70,6 +78,9 @@ export function Hero() {
|
|||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.token) setCsrfToken(data.token);
|
if (data.token) setCsrfToken(data.token);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Failed to refresh CSRF token:", err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.error("Oops!", {
|
toast.error("Oops!", {
|
||||||
|
|||||||
Reference in New Issue
Block a user