Spaces:
Sleeping
Sleeping
File size: 4,011 Bytes
51648f8 eef451f 51648f8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 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<boolean> {
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<void> => {
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;
|