import { Router, Response } from 'express'; import { supabase } from '../../db/client'; import { logger } from '../../utils/logger'; import { authenticateUser, AuthenticatedRequest } from '../middleware/auth'; const router = Router(); const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const ALLOWED_ROLES = new Set(['user', 'admin']); async function isAdmin(userId: string): Promise { const { data, error } = await supabase .from('user_profiles') .select('role') .eq('id', userId) .single(); if (error) { logger.warn('Failed to verify admin for user management', { userId, error: error.message, }); return false; } return data?.role === 'admin'; } router.post( '/api/admin/users', authenticateUser, async (req, res: Response): Promise => { const authReq = req as AuthenticatedRequest; try { if (!(await isAdmin(authReq.userId))) { res.status(403).json({ error: 'Admin access required' }); return; } const email = String(authReq.body.email || '').trim().toLowerCase(); const password = String(authReq.body.password || ''); const displayName = String(authReq.body.displayName || '').trim(); const role = String(authReq.body.role || 'user').trim().toLowerCase(); const ticketBalance = Number(authReq.body.ticketBalance ?? 0); if (!EMAIL_PATTERN.test(email)) { res.status(400).json({ error: 'A valid email address is required' }); return; } if (password.length < 8 || password.length > 128) { res.status(400).json({ error: 'Password must contain 8 to 128 characters' }); return; } if (displayName.length > 80) { res.status(400).json({ error: 'Display name must not exceed 80 characters' }); return; } if (!ALLOWED_ROLES.has(role)) { res.status(400).json({ error: 'Role must be user or admin' }); return; } if (!Number.isInteger(ticketBalance) || ticketBalance < 0 || ticketBalance > 1_000_000) { res.status(400).json({ error: 'Ticket balance must be an integer from 0 to 1,000,000' }); return; } const { data: created, error: createError } = await supabase.auth.admin.createUser({ email, password, email_confirm: true, app_metadata: { dashboard_provisioned: true, }, user_metadata: { display_name: displayName || email.split('@')[0], }, }); if (createError || !created.user) { const message = createError?.message || 'Supabase did not return the created user'; const status = /already|registered|exists/i.test(message) ? 409 : 400; res.status(status).json({ error: message }); return; } const { data: profile, error: profileError } = await supabase .from('user_profiles') .update({ display_name: displayName || email.split('@')[0], role, ticket_balance: ticketBalance, updated_at: new Date().toISOString(), }) .eq('id', created.user.id) .select('id, email, display_name, role, ticket_balance, created_at') .single(); if (profileError || !profile) { await supabase.auth.admin.deleteUser(created.user.id).catch(() => undefined); throw profileError || new Error('User profile was not created'); } logger.info('Dashboard account created by admin', { adminUserId: authReq.userId, createdUserId: created.user.id, role, ticketBalance, }); res.status(201).json({ user: profile }); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); logger.error('Admin user creation failed', { adminUserId: authReq.userId, error: message, }); res.status(500).json({ error: 'Failed to create dashboard account' }); } }, ); export default router;