81 lines
2.1 KiB
TypeScript
81 lines
2.1 KiB
TypeScript
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,
|
|
);
|
|
}
|
|
});
|