New Captcha Package added
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { captchaHandlers } from "../../lib/captcha.ts";
|
||||
|
||||
async function handle(ctx: Context): Promise<Response> {
|
||||
return (await captchaHandlers.handle(ctx.req, ctx)) ?? new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
export const GET = handle;
|
||||
export const HEAD = handle;
|
||||
export const POST = handle;
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { captchaGuard } from "@wrnexus/captcha/server";
|
||||
import { parseBody } from "@wrnexus/validation";
|
||||
import contactSchema from "../schemas/contact.ts";
|
||||
import { captchaEngine } from "../lib/captcha.ts";
|
||||
|
||||
const protectContact = captchaGuard({
|
||||
action: "contact-submit",
|
||||
engine: captchaEngine,
|
||||
responseField: "wrn-captcha-response",
|
||||
bindHostname: true,
|
||||
bindSession: true,
|
||||
});
|
||||
|
||||
interface ContactInput {
|
||||
name: string;
|
||||
email: string;
|
||||
topic: "general" | "security" | "billing" | "integration";
|
||||
message: string;
|
||||
consent: boolean;
|
||||
}
|
||||
|
||||
export async function POST(ctx: Context): Promise<Response> {
|
||||
const validation = await parseBody<ContactInput>(contactSchema, ctx.req.clone());
|
||||
if (!validation.ok) return validation.response;
|
||||
|
||||
return protectContact(ctx, async () =>
|
||||
Response.json({
|
||||
ok: true,
|
||||
message: "Validated CAPTCHA form accepted.",
|
||||
submission: {
|
||||
name: validation.value.name,
|
||||
email: validation.value.email,
|
||||
topic: validation.value.topic,
|
||||
messageLength: validation.value.message.length,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Context } from "@wrnexus/core";
|
||||
|
||||
function safeReturnPath(value: string | null, baseUrl: URL): string {
|
||||
if (!value) return "/protected";
|
||||
|
||||
try {
|
||||
const destination = new URL(value, baseUrl);
|
||||
if (destination.origin !== baseUrl.origin) return "/protected";
|
||||
if (!destination.pathname.startsWith("/") || destination.pathname.startsWith("//")) {
|
||||
return "/protected";
|
||||
}
|
||||
return `${destination.pathname}${destination.search}`;
|
||||
} catch {
|
||||
return "/protected";
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(ctx: Context): Promise<Response> {
|
||||
const contentType = ctx.req.headers.get("content-type") ?? "";
|
||||
let submittedReturnTo: string | null = null;
|
||||
|
||||
if (contentType.includes("application/json")) {
|
||||
const body = (await ctx.req.json().catch(() => ({}))) as Record<string, unknown>;
|
||||
submittedReturnTo = typeof body.returnTo === "string" ? body.returnTo : null;
|
||||
} else {
|
||||
const form = await ctx.req.formData();
|
||||
const value = form.get("returnTo");
|
||||
submittedReturnTo = typeof value === "string" ? value : null;
|
||||
}
|
||||
|
||||
const returnTo = submittedReturnTo ?? ctx.url.searchParams.get("returnTo");
|
||||
const safePath = safeReturnPath(returnTo, ctx.url);
|
||||
return Response.redirect(new URL(safePath, ctx.url), 303);
|
||||
}
|
||||
Reference in New Issue
Block a user