/** * Tenant Self-Management Routes * Allows tenant admins to manage their own tenant settings */ const express = require('express'); const router = express.Router(); const { Tenant, User } = require('../models'); const { authenticateToken } = require('../middleware/auth'); const { requirePermissions, requireAnyPermission, hasPermission } = require('../middleware/rbac'); const MultiTenantAuth = require('../middleware/multi-tenant-auth'); // Initialize multi-tenant auth const multiAuth = new MultiTenantAuth(); /** * GET /tenant/info * Get current tenant information */ router.get('/info', authenticateToken, requirePermissions(['tenant.view']), async (req, res) => { try { // Determine tenant from request const tenantId = await multiAuth.determineTenant(req); if (!tenantId) { return res.status(400).json({ success: false, message: 'Unable to determine tenant' }); } const tenant = await Tenant.findOne({ where: { slug: tenantId } }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Return tenant info (excluding sensitive data) const tenantInfo = { id: tenant.id, name: tenant.name, slug: tenant.slug, domain: tenant.domain, subscription_type: tenant.subscription_type, is_active: tenant.is_active, auth_provider: tenant.auth_provider, branding: tenant.branding, features: tenant.features, admin_email: tenant.admin_email, admin_phone: tenant.admin_phone, billing_email: tenant.billing_email, created_at: tenant.created_at, // IP restriction info (for security admins only) ip_restriction_enabled: hasPermission(req.user.role, 'security.view') ? tenant.ip_restriction_enabled : undefined, ip_whitelist: hasPermission(req.user.role, 'security.view') ? tenant.ip_whitelist : undefined, ip_restriction_message: hasPermission(req.user.role, 'security.view') ? tenant.ip_restriction_message : undefined }; res.json({ success: true, data: tenantInfo }); } catch (error) { console.error('Error fetching tenant info:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenant information' }); } }); /** * PUT /tenant/branding * Update tenant branding (branding admin or higher) */ router.put('/branding', authenticateToken, requirePermissions(['branding.edit']), async (req, res) => { try { // Determine tenant from request const tenantId = await multiAuth.determineTenant(req); if (!tenantId) { return res.status(400).json({ success: false, message: 'Unable to determine tenant' }); } const tenant = await Tenant.findOne({ where: { slug: tenantId } }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } const { logo_url, primary_color, secondary_color, company_name } = req.body; // Validate colors const colorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; if (primary_color && !colorRegex.test(primary_color)) { return res.status(400).json({ success: false, message: 'Invalid primary color format' }); } if (secondary_color && !colorRegex.test(secondary_color)) { return res.status(400).json({ success: false, message: 'Invalid secondary color format' }); } // Update branding const updatedBranding = { ...tenant.branding, logo_url: logo_url || tenant.branding?.logo_url || '', primary_color: primary_color || tenant.branding?.primary_color || '#3B82F6', secondary_color: secondary_color || tenant.branding?.secondary_color || '#1F2937', company_name: company_name || tenant.branding?.company_name || '' }; await tenant.update({ branding: updatedBranding }); console.log(`✅ Tenant "${tenantId}" branding updated by user "${req.user.username}"`); res.json({ success: true, message: 'Branding updated successfully', data: { branding: updatedBranding } }); } catch (error) { console.error('Error updating tenant branding:', error); res.status(500).json({ success: false, message: 'Failed to update branding' }); } }); /** * PUT /tenant/security * Update tenant security settings (security admin or higher) */ router.put('/security', authenticateToken, requirePermissions(['security.edit']), async (req, res) => { try { // Determine tenant from request const tenantId = await multiAuth.determineTenant(req); if (!tenantId) { return res.status(400).json({ success: false, message: 'Unable to determine tenant' }); } const tenant = await Tenant.findOne({ where: { slug: tenantId } }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } const { ip_restriction_enabled, ip_whitelist, ip_restriction_message } = req.body; // Validate IP whitelist if provided if (ip_whitelist && Array.isArray(ip_whitelist)) { for (const ip of ip_whitelist) { // Basic IP validation (could be enhanced) if (typeof ip !== 'string' || ip.trim().length === 0) { return res.status(400).json({ success: false, message: 'Invalid IP address in whitelist' }); } } } // Update security settings const updates = {}; if (typeof ip_restriction_enabled === 'boolean') { updates.ip_restriction_enabled = ip_restriction_enabled; } if (ip_whitelist) { updates.ip_whitelist = ip_whitelist; } if (ip_restriction_message) { updates.ip_restriction_message = ip_restriction_message; } await tenant.update(updates); console.log(`✅ Tenant "${tenantId}" security settings updated by user "${req.user.username}"`); res.json({ success: true, message: 'Security settings updated successfully', data: { ip_restriction_enabled: tenant.ip_restriction_enabled, ip_whitelist: tenant.ip_whitelist, ip_restriction_message: tenant.ip_restriction_message } }); } catch (error) { console.error('Error updating tenant security:', error); res.status(500).json({ success: false, message: 'Failed to update security settings' }); } }); /** * GET /tenant/users * Get users in current tenant (user admin or higher) */ router.get('/users', authenticateToken, requirePermissions(['users.view']), async (req, res) => { try { // Determine tenant from request const tenantId = await multiAuth.determineTenant(req); if (!tenantId) { return res.status(400).json({ success: false, message: 'Unable to determine tenant' }); } const tenant = await Tenant.findOne({ where: { slug: tenantId } }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Get users in this tenant const users = await User.findAll({ where: { tenant_id: tenant.id }, attributes: ['id', 'username', 'email', 'role', 'is_active', 'last_login', 'created_at'], order: [['created_at', 'DESC']] }); res.json({ success: true, data: users }); } catch (error) { console.error('Error fetching tenant users:', error); res.status(500).json({ success: false, message: 'Failed to fetch users' }); } }); /** * POST /tenant/users * Create a new user in current tenant (user admin or higher, local auth only) */ router.post('/users', authenticateToken, requirePermissions(['users.create']), async (req, res) => { try { // Determine tenant from request const tenantId = await multiAuth.determineTenant(req); if (!tenantId) { return res.status(400).json({ success: false, message: 'Unable to determine tenant' }); } const tenant = await Tenant.findOne({ where: { slug: tenantId } }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Check if tenant uses local authentication if (tenant.auth_provider !== 'local') { return res.status(400).json({ success: false, message: `User creation is only available for local authentication. This tenant uses ${tenant.auth_provider}.` }); } const { username, email, password, role = 'viewer', is_active = true } = req.body; // Validate required fields if (!username || !email || !password) { return res.status(400).json({ success: false, message: 'Username, email, and password are required' }); } // Validate role const validRoles = ['admin', 'operator', 'viewer']; if (!validRoles.includes(role)) { return res.status(400).json({ success: false, message: 'Invalid role. Must be admin, operator, or viewer' }); } // Check if username or email already exists in this tenant const existingUser = await User.findOne({ where: { tenant_id: tenant.id, [require('sequelize').Op.or]: [ { username }, { email } ] } }); if (existingUser) { return res.status(400).json({ success: false, message: 'Username or email already exists in this tenant' }); } // Create user const user = await User.create({ username, email, password_hash: password, // Will be hashed by the model role, is_active, tenant_id: tenant.id, created_by: req.user.id }); console.log(`✅ User "${username}" created in tenant "${tenantId}" by admin "${req.user.username}"`); // Return user data (without password) const userData = { id: user.id, username: user.username, email: user.email, role: user.role, is_active: user.is_active, created_at: user.created_at }; res.status(201).json({ success: true, message: 'User created successfully', data: userData }); } catch (error) { console.error('Error creating tenant user:', error); res.status(500).json({ success: false, message: 'Failed to create user' }); } }); /** * PUT /tenant/users/:userId/status * Update user status (activate/deactivate) (user admin or higher, local auth only) */ router.put('/users/:userId/status', authenticateToken, requirePermissions(['users.edit']), async (req, res) => { try { // Determine tenant from request const tenantId = await multiAuth.determineTenant(req); if (!tenantId) { return res.status(400).json({ success: false, message: 'Unable to determine tenant' }); } const tenant = await Tenant.findOne({ where: { slug: tenantId } }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Check if tenant uses local authentication if (tenant.auth_provider !== 'local') { return res.status(400).json({ success: false, message: `User management is only available for local authentication. This tenant uses ${tenant.auth_provider}.` }); } const { userId } = req.params; const { is_active } = req.body; if (typeof is_active !== 'boolean') { return res.status(400).json({ success: false, message: 'is_active must be a boolean value' }); } // Find user in this tenant const user = await User.findOne({ where: { id: userId, tenant_id: tenant.id } }); if (!user) { return res.status(404).json({ success: false, message: 'User not found in this tenant' }); } // Prevent self-deactivation if (user.id === req.user.id && !is_active) { return res.status(400).json({ success: false, message: 'You cannot deactivate your own account' }); } // Update user status await user.update({ is_active }); console.log(`✅ User "${user.username}" ${is_active ? 'activated' : 'deactivated'} in tenant "${tenantId}" by admin "${req.user.username}"`); res.json({ success: true, message: `User ${is_active ? 'activated' : 'deactivated'} successfully`, data: { id: user.id, username: user.username, is_active: user.is_active } }); } catch (error) { console.error('Error updating user status:', error); res.status(500).json({ success: false, message: 'Failed to update user status' }); } }); module.exports = router;