/** * Management Portal API Routes * Completely separate authentication system for security isolation */ const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); // Fixed: use bcryptjs instead of bcrypt const { Op } = require('sequelize'); // Add Sequelize operators const { Tenant, User } = require('../models'); // Management-specific authentication middleware - NO shared auth with tenants const requireManagementAuth = (req, res, next) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ success: false, message: 'Management authentication required' }); } try { // Use separate JWT secret for management const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production'; const decoded = jwt.verify(token, MANAGEMENT_SECRET); // Verify this is a management token with proper role if (!decoded.isManagement || !['super_admin', 'platform_admin'].includes(decoded.role)) { return res.status(403).json({ success: false, message: 'Insufficient management privileges' }); } req.managementUser = { id: decoded.userId, username: decoded.username, role: decoded.role, isManagement: true }; next(); } catch (error) { return res.status(403).json({ success: false, message: 'Invalid management token', error: error.message }); } }; // Management login endpoint - separate from tenant auth router.post('/auth/login', async (req, res) => { try { const { username, password } = req.body; // Hardcoded management users for now (should be in separate DB table) const MANAGEMENT_USERS = { 'admin': { password: await bcrypt.hash('admin123', 10), // Change this! role: 'super_admin' }, 'platform_admin': { password: await bcrypt.hash('platform123', 10), // Change this! role: 'platform_admin' } }; const managementUser = MANAGEMENT_USERS[username]; if (!managementUser || !await bcrypt.compare(password, managementUser.password)) { return res.status(401).json({ success: false, message: 'Invalid management credentials' }); } const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production'; const token = jwt.sign({ userId: username, username: username, role: managementUser.role, isManagement: true }, MANAGEMENT_SECRET, { expiresIn: '8h' }); res.json({ success: true, token, user: { username, role: managementUser.role } }); } catch (error) { res.status(500).json({ success: false, message: 'Management authentication error', error: error.message }); } }); // Apply management authentication to all other routes router.use(requireManagementAuth); // Security audit logging for all management operations router.use((req, res, next) => { const auditLog = { timestamp: new Date().toISOString(), user: req.managementUser.username, role: req.managementUser.role, method: req.method, path: req.path, ip: req.ip, userAgent: req.headers['user-agent'] }; console.log('[MANAGEMENT AUDIT]', JSON.stringify(auditLog)); next(); }); /** * GET /api/management/system-info - Platform system information */ router.get('/system-info', async (req, res) => { try { const tenantCount = await Tenant.count(); const userCount = await User.count(); res.json({ success: true, data: { platform: { name: 'UAMILS Platform', version: '1.0.0', environment: process.env.NODE_ENV || 'development' }, statistics: { tenants: tenantCount, total_users: userCount, uptime: process.uptime() }, security: { management_access_level: req.managementUser.role, last_backup: process.env.LAST_BACKUP_DATE || 'Not configured' } } }); } catch (error) { console.error('Management: Error fetching system info:', error); res.status(500).json({ success: false, message: 'Failed to fetch system information', error: error.message }); } }); /** * GET /api/management/tenants - List all tenants with admin details */ router.get('/tenants', async (req, res) => { try { const { limit = 50, offset = 0, search, auth_provider } = req.query; const whereClause = {}; if (search) { whereClause[Op.or] = [ { name: { [Op.iLike]: `%${search}%` } }, { slug: { [Op.iLike]: `%${search}%` } }, { domain: { [Op.iLike]: `%${search}%` } } ]; } if (auth_provider) { whereClause.auth_provider = auth_provider; } const tenants = await Tenant.findAndCountAll({ where: whereClause, include: [{ model: User, as: 'users', attributes: ['id', 'username', 'email', 'role', 'last_login', 'created_at'], limit: 10 }], limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: tenants.rows, pagination: { total: tenants.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(tenants.count / parseInt(limit)) } }); } catch (error) { console.error('Management: Error fetching tenants:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenants' }); } }); /** * POST /api/management/tenants - Create new tenant */ router.post('/tenants', async (req, res) => { try { const tenantData = req.body; // Enhanced validation for management portal if (!tenantData.name || !tenantData.slug) { return res.status(400).json({ success: false, message: 'Name and slug are required' }); } // Check for unique slug const existingTenant = await Tenant.findOne({ where: { slug: tenantData.slug } }); if (existingTenant) { return res.status(409).json({ success: false, message: 'Tenant slug already exists' }); } // Log management action console.log(`Management: Admin ${req.managementUser.username} creating tenant: ${tenantData.name}`); const tenant = await Tenant.create(tenantData); res.status(201).json({ success: true, data: tenant, message: 'Tenant created successfully' }); } catch (error) { console.error('Management: Error creating tenant:', error); res.status(500).json({ success: false, message: 'Failed to create tenant' }); } }); /** * GET /api/management/tenants/:id - Get tenant details */ router.get('/tenants/:id', async (req, res) => { try { const tenant = await Tenant.findByPk(req.params.id, { include: [{ model: User, as: 'users', attributes: ['id', 'username', 'email', 'role', 'last_login', 'created_at'] }] }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } res.json({ success: true, data: tenant }); } catch (error) { console.error('Management: Error fetching tenant:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenant' }); } }); /** * PUT /api/management/tenants/:id - Update tenant */ router.put('/tenants/:id', async (req, res) => { try { const tenant = await Tenant.findByPk(req.params.id); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Log management action console.log(`Management: Admin ${req.managementUser.username} updating tenant: ${tenant.name}`); await tenant.update(req.body); res.json({ success: true, data: tenant, message: 'Tenant updated successfully' }); } catch (error) { console.error('Management: Error updating tenant:', error); res.status(500).json({ success: false, message: 'Failed to update tenant' }); } }); /** * DELETE /api/management/tenants/:id - Delete tenant */ router.delete('/tenants/:id', async (req, res) => { try { const tenant = await Tenant.findByPk(req.params.id); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Prevent deletion of default tenant if (tenant.slug === 'default') { return res.status(403).json({ success: false, message: 'Cannot delete default tenant' }); } // Log management action console.log(`Management: Admin ${req.managementUser.username} deleting tenant: ${tenant.name}`); await tenant.destroy(); res.json({ success: true, message: 'Tenant deleted successfully' }); } catch (error) { console.error('Management: Error deleting tenant:', error); res.status(500).json({ success: false, message: 'Failed to delete tenant' }); } }); /** * GET /api/management/users - List all users across tenants */ router.get('/users', async (req, res) => { try { const { limit = 50, offset = 0, search, role, tenant_id } = req.query; const whereClause = {}; if (search) { whereClause[Op.or] = [ { username: { [Op.iLike]: `%${search}%` } }, { email: { [Op.iLike]: `%${search}%` } } ]; } if (role) { whereClause.role = role; } if (tenant_id) { whereClause.tenant_id = tenant_id; } const users = await User.findAndCountAll({ where: whereClause, include: [{ model: Tenant, as: 'tenant', attributes: ['id', 'name', 'slug'] }], attributes: { exclude: ['password'] }, // Never expose passwords limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: users.rows, pagination: { total: users.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(users.count / parseInt(limit)) } }); } catch (error) { console.error('Management: Error fetching users:', error); res.status(500).json({ success: false, message: 'Failed to fetch users' }); } }); /** * GET /api/management/system/info - System information */ router.get('/system/info', async (req, res) => { try { // Gather system statistics const tenantCount = await Tenant.count(); const userCount = await User.count(); const activeTenantsCount = await Tenant.count({ where: { is_active: true } }); const systemInfo = { version: process.env.APP_VERSION || '1.0.0', environment: process.env.NODE_ENV || 'production', uptime: process.uptime(), database: { status: 'connected', version: 'PostgreSQL 15', connections: 5, // You can get this from pg pool maxConnections: 100 }, statistics: { tenants: tenantCount, users: userCount, activeTenants: activeTenantsCount }, memory: { used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + 'MB', total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + 'MB', percentage: Math.round((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100) }, lastBackup: new Date(Date.now() - 24*60*60*1000).toISOString(), // Mock ssl: { status: 'valid', expiresAt: new Date(Date.now() + 90*24*60*60*1000).toISOString() // Mock 90 days } }; res.json({ success: true, data: systemInfo }); } catch (error) { console.error('Management: Error fetching system info:', error); res.status(500).json({ success: false, message: 'Failed to fetch system information' }); } }); module.exports = router;