import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { createClient } from "npm:@supabase/supabase-js@2"; const cors = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", "Access-Control-Allow-Methods": "POST, OPTIONS", "Content-Type": "application/json", }; const reply = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: cors }); type PrepData = { status?: string; user_id?: string | null; email?: string; display_name?: string; role?: string; }; Deno.serve(async (req: Request) => { if (req.method === "OPTIONS") return new Response("ok", { headers: cors }); if (req.method !== "POST") return reply({ error: "Method not allowed" }, 405); try { const auth = req.headers.get("Authorization") || ""; const url = Deno.env.get("SUPABASE_URL") || ""; const anon = Deno.env.get("SUPABASE_ANON_KEY") || ""; const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || ""; if (!url || !anon || !serviceKey) return reply({ error: "Server auth configuration is incomplete" }, 500); const caller = createClient(url, anon, { global: { headers: { Authorization: auth } }, auth: { persistSession: false, autoRefreshToken: false }, }); const service = createClient(url, serviceKey, { auth: { persistSession: false, autoRefreshToken: false } }); const body = await req.json(); const workspaceId = String(body.workspace_id || ""); const email = String(body.email || "").trim().toLowerCase(); const displayName = String(body.display_name || "").trim(); const role = String(body.role || "manager"); const prepare = async () => { const result = await caller.rpc("sun_employee_prepare_v28", { p_workspace: workspaceId, p_email: email, p_display_name: displayName, p_role: role, }); if (result.error) throw new Error(result.error.message); return (result.data || {}) as PrepData; }; const finalize = async (userId: string, created: boolean, temporaryPassword: string | null = null) => { const fin = await caller.rpc("sun_employee_finalize_v28", { p_workspace: workspaceId, p_user_id: userId, p_display_name: displayName, p_role: role, }); if (fin.error) throw new Error(fin.error.message); return reply({ ...fin.data, created, temporary_password: temporaryPassword, must_change_password: created, }); }; const prep = await prepare(); const prepStatus = String(prep.status || ""); if (prepStatus === "already_member") { return reply({ status: "already_member", user_id: prep.user_id, email: prep.email || email, display_name: prep.display_name || displayName, role: prep.role || role, created: false, temporary_password: null, must_change_password: false, }); } if (prepStatus === "existing") { const existingUserId = String(prep.user_id || ""); if (!existingUserId) return reply({ error: "Existing employee account has no user id" }, 409); return await finalize(existingUserId, false, null); } if (prepStatus !== "new") { return reply({ error: `Unexpected employee prepare status: ${prepStatus || "empty"}` }, 409); } const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; const bytes = crypto.getRandomValues(new Uint8Array(12)); let password = ""; for (const b of bytes) password += alphabet[b % alphabet.length]; password = password.slice(0, 6) + "-" + password.slice(6); const created = await service.auth.admin.createUser({ email, password, email_confirm: true, user_metadata: { name: displayName || email.split("@")[0], must_change_password: true, registration_source: "caterium_employee_admin", }, }); if (created.error || !created.data.user) { // A concurrent request or a previous partial failure may have created Auth already. // Re-read server state and finalize the existing account instead of leaving the workspace half-configured. const retryPrep = await prepare(); const retryStatus = String(retryPrep.status || ""); if (retryStatus === "already_member") { return reply({ status: "already_member", user_id: retryPrep.user_id, email: retryPrep.email || email, display_name: retryPrep.display_name || displayName, role: retryPrep.role || role, created: false, temporary_password: null, must_change_password: false, }); } if (retryStatus === "existing" && retryPrep.user_id) { return await finalize(String(retryPrep.user_id), false, null); } return reply({ error: created.error?.message || "Create failed" }, 400); } return await finalize(created.data.user.id, true, password); } catch (e) { return reply({ error: e instanceof Error ? e.message : String(e) }, 500); } });