import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; interface VerifyOTPRequest { user_id: string; otp_code: string; } serve(async (req: Request) => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } try { const { user_id, otp_code }: VerifyOTPRequest = await req.json(); // Validate required fields if (!user_id || !otp_code) { return new Response( JSON.stringify({ success: false, message: "Missing required fields: user_id, otp_code" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Validate OTP format (6 digits) if (!/^\d{6}$/.test(otp_code)) { return new Response( JSON.stringify({ success: false, message: "Invalid OTP format. Must be 6 digits." }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Initialize Supabase client with service role const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabase = createClient(supabaseUrl, supabaseServiceKey); console.log(`Verifying OTP for user ${user_id}`); // Find valid OTP (not expired, not used) const { data: otpRecord, error: otpError } = await supabase .from('auth_otps') .select('*') .eq('user_id', user_id) .eq('otp_code', otp_code) .is('used_at', null) .gt('expires_at', new Date().toISOString()) .order('created_at', { ascending: false }) .limit(1) .maybeSingle(); if (otpError) { console.error('Error fetching OTP:', otpError); throw new Error(`Failed to verify OTP: ${otpError.message}`); } if (!otpRecord) { console.log('Invalid or expired OTP'); // Clean up expired OTPs await supabase.rpc('cleanup_expired_otps'); return new Response( JSON.stringify({ success: false, message: "Invalid or expired OTP code" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Mark OTP as used const { error: updateError } = await supabase .from('auth_otps') .update({ used_at: new Date().toISOString() }) .eq('id', otpRecord.id); if (updateError) { console.error('Error marking OTP as used:', updateError); throw new Error(`Failed to mark OTP as used: ${updateError.message}`); } // Confirm email in Supabase Auth const { error: confirmError } = await supabase.auth.admin.updateUserById( user_id, { email_confirm: true } ); if (confirmError) { console.error('Error confirming email:', confirmError); throw new Error(`Failed to confirm email: ${confirmError.message}`); } console.log(`Email confirmed successfully for user ${user_id}`); // Clean up expired OTPs await supabase.rpc('cleanup_expired_otps'); return new Response( JSON.stringify({ success: true, message: "Email verified successfully" }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } catch (error: any) { console.error("Error verifying OTP:", error); return new Response( JSON.stringify({ success: false, message: error.message || "Failed to verify OTP" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } });