mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 13:55:23 +02:00
291 lines
8.9 KiB
JavaScript
291 lines
8.9 KiB
JavaScript
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_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];
|
|
|
|
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.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' });
|
|
}
|
|
}
|