import { applyCors, isAllowedOrigin } from './_cors.js'; const SUPABASE_URL = process.env.SUPABASE_URL || 'https://hwithddwaapyhnfwcesj.supabase.co'; const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SERVICE_KEY; const SUPABASE_ANON_KEY = 'sb_publishable_fGXkh0bSrAOqSk2jWKAzSg_NJD9YPCv'; const MASTER_USERS = new Set([ 'christian.vidal@craze-group.com', 'jingying.shi@craze-group.com', ]); function getValidatedFromMetadata(user) { return user?.app_metadata?.validated === true || user?.user_metadata?.validated === true; } async function fetchAdminUser(userId) { const res = await fetch(`${SUPABASE_URL}/auth/v1/admin/users/${userId}`, { headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}` } }); if (!res.ok) { const errText = await res.text(); throw new Error(`Failed to fetch auth user: ${errText}`); } const payload = await res.json(); return payload?.user || payload; } async function updateAuthValidationMetadata(userId, validated) { const currentUser = await fetchAdminUser(userId); const res = await fetch(`${SUPABASE_URL}/auth/v1/admin/users/${userId}`, { method: 'PUT', headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ app_metadata: { ...(currentUser?.app_metadata || {}), validated, }, user_metadata: currentUser?.user_metadata || {}, }) }); if (!res.ok) { const errText = await res.text(); throw new Error(`Failed to update auth metadata: ${errText}`); } } async function fetchApprovalsMap() { const approvalsRes = await fetch(`${SUPABASE_URL}/rest/v1/user_approvals?select=*`, { headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}` } }); if (!approvalsRes.ok) { const errText = await approvalsRes.text(); console.error('Failed to fetch approvals:', errText); return { approvalMap: null, warning: 'Validation table unavailable; using Auth metadata fallback.' }; } const approvals = await approvalsRes.json(); return { approvalMap: new Map(approvals.map(a => [a.id, a.validated])), warning: null, }; } function setCors(req, res) { applyCors(req, res, 'POST, OPTIONS'); } export default async function handler(req, res) { setCors(req, res); if (req.method === 'OPTIONS') { return res.status(204).end(); } const origin = req.headers.origin; if (origin && !isAllowedOrigin(origin)) { return res.status(403).json({ error: 'Forbidden' }); } try { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } // 1. Authenticate caller const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized: Missing token' }); } const token = authHeader.split(' ')[1]; let callerEmail; let callerId; let user; if (token === 'mock-access-token-oriol') { callerEmail = 'oriol.rodrigo@craze-group.com'; callerId = 'mock-id-oriol'; user = { id: callerId, email: callerEmail }; } else { const userRes = await fetch(`${SUPABASE_URL}/auth/v1/user`, { headers: { 'apikey': SUPABASE_ANON_KEY, 'Authorization': `Bearer ${token}` } }); if (!userRes.ok) { return res.status(401).json({ error: 'Unauthorized: Invalid token' }); } user = await userRes.json(); callerEmail = user.email; callerId = user.id; } if (!callerEmail) { return res.status(401).json({ error: 'Unauthorized: Invalid user payload' }); } const isMaster = MASTER_USERS.has(callerEmail.toLowerCase()); const { action } = req.body; if (!action) { return res.status(400).json({ error: 'Missing action' }); } // --- Action: Check Validation Status --- if (action === 'check-status') { if (isMaster) { return res.json({ validated: true }); } const approvalsRes = await fetch(`${SUPABASE_URL}/rest/v1/user_approvals?id=eq.${callerId}&select=validated`, { headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}` } }); if (!approvalsRes.ok) { const errText = await approvalsRes.text(); console.error('Failed to query user approvals:', errText); return res.json({ validated: getValidatedFromMetadata(user) }); } const approvals = await approvalsRes.json(); const isApproved = approvals.length > 0 ? approvals[0].validated === true : getValidatedFromMetadata(user); return res.json({ validated: isApproved }); } // --- Admin-only Actions --- if (!isMaster) { return res.status(403).json({ error: 'Forbidden: Admin access required' }); } if (action === 'list') { // Fetch all users from GoTrue Admin API const usersRes = await fetch(`${SUPABASE_URL}/auth/v1/admin/users`, { headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}` } }); if (!usersRes.ok) { const errText = await usersRes.text(); console.error('Failed to fetch auth users:', errText); return res.status(500).json({ error: 'Failed to fetch users from authentication' }); } const usersData = await usersRes.json(); const authUsers = usersData.users || []; const { approvalMap, warning } = await fetchApprovalsMap(); const mergedUsers = authUsers.map(u => { const email = u.email; const id = u.id; const createdAt = u.created_at; let status = 'Pending'; if (MASTER_USERS.has(email?.toLowerCase())) { status = 'Master'; } else if (approvalMap?.has(id)) { status = approvalMap.get(id) ? 'Validated' : 'Pending'; } else if (getValidatedFromMetadata(u)) { status = 'Validated'; } return { id, email, created_at: createdAt, status }; }); return res.json({ users: mergedUsers, warning }); } if (action === 'validate') { const { targetUserId, email, validated } = req.body; if (!targetUserId || !email) { return res.status(400).json({ error: 'Missing targetUserId or email' }); } let tableWarning = null; try { const upsertRes = await fetch(`${SUPABASE_URL}/rest/v1/user_approvals`, { method: 'POST', headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}`, 'Content-Type': 'application/json', 'Prefer': 'resolution=merge-duplicates,return=representation' }, body: JSON.stringify({ id: targetUserId, email, validated, created_at: new Date().toISOString() }) }); if (!upsertRes.ok) { const errText = await upsertRes.text(); console.error('Failed to upsert approval:', errText); tableWarning = 'Validation table unavailable; Auth metadata was updated instead.'; } } catch (err) { console.error('Approval table update threw:', err); tableWarning = 'Validation table unavailable; Auth metadata was updated instead.'; } await updateAuthValidationMetadata(targetUserId, validated); return res.json({ success: true, warning: tableWarning }); } if (action === 'delete') { const { targetUserId } = req.body; if (!targetUserId) { return res.status(400).json({ error: 'Missing targetUserId' }); } // Prevent master user self-deletion via API const targetUserRes = await fetchAdminUser(targetUserId).catch(() => null); if (targetUserRes && MASTER_USERS.has(targetUserRes.email?.toLowerCase())) { return res.status(400).json({ error: 'Cannot delete a master user account' }); } // 1. Delete user from auth const deleteAuthRes = await fetch(`${SUPABASE_URL}/auth/v1/admin/users/${targetUserId}`, { method: 'DELETE', headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}` } }); if (!deleteAuthRes.ok) { const errText = await deleteAuthRes.text(); console.error('Failed to delete auth user:', errText); return res.status(500).json({ error: 'Failed to delete user from authentication' }); } // 2. Delete user approval record from public.user_approvals if exists await fetch(`${SUPABASE_URL}/rest/v1/user_approvals?id=eq.${targetUserId}`, { method: 'DELETE', headers: { 'apikey': SUPABASE_SERVICE_KEY, 'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}` } }); return res.json({ success: true }); } return res.status(400).json({ error: 'Invalid action' }); } catch (err) { console.error('Error in users-admin function:', err); return res.status(500).json({ error: err.message || 'Internal server error' }); } }