import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version", }; Deno.serve(async (req) => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } try { const GOOGLE_CLIENT_ID = Deno.env.get("GOOGLE_CLIENT_ID"); const GOOGLE_CLIENT_SECRET = Deno.env.get("GOOGLE_CLIENT_SECRET"); const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; const SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) { return new Response(JSON.stringify({ error: "Google OAuth credentials not configured" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const body = await req.json(); const { action, code, redirect_uri } = body; const serviceClient = createClient(SUPABASE_URL, SERVICE_ROLE_KEY); // Authenticate user const authHeader = req.headers.get("Authorization"); if (!authHeader?.startsWith("Bearer ")) { // For check_status, return not-connected instead of 401 if (action === "check_status") { return new Response(JSON.stringify({ connected: false }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const token = authHeader.replace("Bearer ", ""); let userId: string | null = null; try { const payload = token.split(".")[1]; if (!payload) throw new Error("Missing JWT payload"); const normalized = payload.replace(/-/g, "+").replace(/_/g, "/"); const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="); const decoded = JSON.parse(atob(padded)); userId = typeof decoded?.sub === "string" ? decoded.sub : null; } catch (error) { console.error("google-drive-auth token decode failed", error); } if (!userId) { if (action === "check_status") { return new Response(JSON.stringify({ connected: false }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } // Check staff role (Google Drive is staff-only) const { data: roles } = await serviceClient .from("user_roles") .select("role") .eq("user_id", userId); const STAFF_ROLES = ["admin", "manager", "employee", "staff"]; const isStaff = roles?.some((r: any) => STAFF_ROLES.includes(r.role)); if (!isStaff) { if (action === "check_status") { return new Response(JSON.stringify({ connected: false }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ error: "Only staff can connect Google Drive" }), { status: 403, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (action === "get_auth_url") { const scopes = [ "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/calendar.readonly", ].join(" "); const params = new URLSearchParams({ client_id: GOOGLE_CLIENT_ID, redirect_uri: redirect_uri, response_type: "code", scope: scopes, access_type: "offline", prompt: "consent", state: "drive_connect", }); return new Response(JSON.stringify({ url: `https://accounts.google.com/o/oauth2/v2/auth?${params}` }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (action === "exchange_code") { if (!code || !redirect_uri) { return new Response(JSON.stringify({ error: "Missing code or redirect_uri" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const tokenRes = await fetch("https://oauth2.googleapis.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: GOOGLE_CLIENT_ID, client_secret: GOOGLE_CLIENT_SECRET, code, grant_type: "authorization_code", redirect_uri, }), }); const tokenData = await tokenRes.json(); if (tokenData.error) { return new Response(JSON.stringify({ error: tokenData.error_description || tokenData.error }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const expiresAt = new Date(Date.now() + tokenData.expires_in * 1000).toISOString(); const { error: upsertError } = await serviceClient .from("google_drive_tokens") .upsert({ user_id: userId, access_token: tokenData.access_token, refresh_token: tokenData.refresh_token, token_expires_at: expiresAt, }, { onConflict: "user_id" }); if (upsertError) { return new Response(JSON.stringify({ error: "Failed to store tokens" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (action === "check_status") { const { data: tokenRow } = await serviceClient .from("google_drive_tokens") .select("id, token_expires_at") .eq("user_id", userId) .maybeSingle(); return new Response(JSON.stringify({ connected: !!tokenRow }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (action === "disconnect") { await serviceClient .from("google_drive_tokens") .delete() .eq("user_id", userId); return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } catch (err) { console.error("google-drive-auth error:", err); return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } });