import { createAnonClient } from "../_shared/chatbot.ts"; import { getClientIp, getCorsHeaders, hashText, jsonResponse, preflightResponse, readJsonBody, requireRateLimit, } from "../_shared/security.ts"; const MAX_BODY_BYTES = 8 * 1024; const isValidEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim()); Deno.serve(async (request) => { if (request.method === "OPTIONS") { return preflightResponse(request, "public"); } if (request.method !== "POST") { return jsonResponse({ ok: false, error: "Method not allowed" }, 405); } const corsHeaders = getCorsHeaders(request, "public"); if (!corsHeaders) { return jsonResponse({ ok: false, error: "Origin not allowed" }, 403); } try { const { body } = await readJsonBody<{ email?: string }>(request, { maxBytes: MAX_BODY_BYTES, }); const email = String(body.email || "").trim().toLowerCase(); if (!email || !isValidEmail(email)) { return jsonResponse({ ok: false, error: "Valid email is required" }, 400, corsHeaders); } const supabase = createAnonClient(); const emailHash = await hashText(email); const ipHash = await hashText(getClientIp(request)); await requireRateLimit(supabase, { scope: "otp-request", key: `${ipHash}:${emailHash}`, maxCount: 3, windowSeconds: 600, blockSeconds: 1800, }); const { error } = await supabase.auth.signInWithOtp({ email, options: { shouldCreateUser: false, }, }); if (error) { return jsonResponse({ ok: false, error: error.message }, 400, corsHeaders); } return jsonResponse({ ok: true }, 200, corsHeaders); } catch (error) { if (error instanceof Error && "status" in error) { const httpError = error as { status: number; message: string }; return jsonResponse({ ok: false, error: httpError.message }, httpError.status, corsHeaders); } return jsonResponse( { ok: false, error: error instanceof Error ? error.message : "Unexpected error", }, 500, corsHeaders, ); } });