mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 15:15:24 +02:00
feat: implement manual user validation and user deletion flow
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://hwithddwaapyhnfwcesj.supabase.co';
|
||||
const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
|
||||
const SUPABASE_ANON_KEY = 'sb_publishable_fGXkh0bSrAOqSk2jWKAzSg_NJD9YPCv';
|
||||
const ALLOWED_ORIGIN = 'https://craze-data-check.vercel.app';
|
||||
|
||||
const MASTER_USERS = new Set([
|
||||
'christian.vidal@craze-group.com',
|
||||
'jingying.shi@craze-group.com',
|
||||
]);
|
||||
|
||||
function setCors(req, res) {
|
||||
const origin = req.headers.origin;
|
||||
if (origin === ALLOWED_ORIGIN || (origin && (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')))) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, apikey');
|
||||
res.setHeader('Access-Control-Max-Age', '86400');
|
||||
res.setHeader('Vary', 'Origin');
|
||||
}
|
||||
|
||||
export default async function handler(req, res) {
|
||||
setCors(req, res);
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
return res.status(204).end();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
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' });
|
||||
}
|
||||
|
||||
const user = await userRes.json();
|
||||
const callerEmail = user.email;
|
||||
const 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.status(500).json({ error: 'Failed to query database' });
|
||||
}
|
||||
|
||||
const approvals = await approvalsRes.json();
|
||||
const isApproved = approvals.length > 0 && approvals[0].validated === true;
|
||||
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 || [];
|
||||
|
||||
// Fetch validation mappings from public.user_approvals
|
||||
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 res.status(500).json({ error: 'Failed to fetch user approvals' });
|
||||
}
|
||||
|
||||
const approvals = await approvalsRes.json();
|
||||
const approvalMap = new Map(approvals.map(a => [a.id, a.validated]));
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
return { id, email, created_at: createdAt, status };
|
||||
});
|
||||
|
||||
return res.json({ users: mergedUsers });
|
||||
}
|
||||
|
||||
if (action === 'validate') {
|
||||
const { targetUserId, email, validated } = req.body;
|
||||
if (!targetUserId || !email) {
|
||||
return res.status(400).json({ error: 'Missing targetUserId or email' });
|
||||
}
|
||||
|
||||
// Upsert into user_approvals table
|
||||
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);
|
||||
return res.status(500).json({ error: 'Failed to update approval status' });
|
||||
}
|
||||
|
||||
return res.json({ success: true });
|
||||
}
|
||||
|
||||
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 { data: targetUserRes } = await fetch(`${SUPABASE_URL}/auth/v1/admin/users/${targetUserId}`, {
|
||||
headers: {
|
||||
'apikey': SUPABASE_SERVICE_KEY,
|
||||
'Authorization': `Bearer ${SUPABASE_SERVICE_KEY}`
|
||||
}
|
||||
}).then(r => r.json().catch(() => ({})));
|
||||
|
||||
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' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user