/** * Tenant Management Routes * Admin interface for managing tenants and their authentication configurations */ const express = require('express'); const router = express.Router(); const Joi = require('joi'); const { Tenant, User } = require('../models'); const { validateRequest } = require('../middleware/validation'); const { authenticateToken, requireRole } = require('../middleware/auth'); // Validation schemas const tenantSchema = Joi.object({ name: Joi.string().required(), slug: Joi.string().pattern(/^[a-z0-9-]+$/).required(), domain: Joi.string().optional(), subscription_type: Joi.string().valid('free', 'basic', 'premium', 'enterprise').default('basic'), auth_provider: Joi.string().valid('local', 'saml', 'oauth', 'ldap', 'custom_sso').default('local'), auth_config: Joi.object().optional(), user_mapping: Joi.object().optional(), role_mapping: Joi.object().optional(), branding: Joi.object().optional(), features: Joi.object().optional(), admin_email: Joi.string().email().optional(), admin_phone: Joi.string().optional(), billing_email: Joi.string().email().optional() }); const authConfigSchema = Joi.object({ // SAML Configuration sso_url: Joi.string().uri().when('auth_provider', { is: 'saml', then: Joi.required() }), certificate: Joi.string().when('auth_provider', { is: 'saml', then: Joi.required() }), issuer: Joi.string().when('auth_provider', { is: 'saml', then: Joi.required() }), logout_url: Joi.string().uri().optional(), // OAuth Configuration client_id: Joi.string().when('auth_provider', { is: 'oauth', then: Joi.required() }), client_secret: Joi.string().when('auth_provider', { is: 'oauth', then: Joi.required() }), authorization_url: Joi.string().uri().when('auth_provider', { is: 'oauth', then: Joi.required() }), token_url: Joi.string().uri().when('auth_provider', { is: 'oauth', then: Joi.required() }), userinfo_url: Joi.string().uri().optional(), scopes: Joi.array().items(Joi.string()).optional(), // LDAP Configuration url: Joi.string().when('auth_provider', { is: 'ldap', then: Joi.required() }), base_dn: Joi.string().when('auth_provider', { is: 'ldap', then: Joi.required() }), bind_dn: Joi.string().optional(), bind_password: Joi.string().optional(), user_search_filter: Joi.string().optional(), domain: Joi.string().optional() }); /** * GET /api/tenants - List all tenants (super admin only) */ router.get('/', authenticateToken, requireRole(['admin']), 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, attributes: { exclude: ['auth_config'] }, // Don't expose auth secrets include: [{ model: User, as: 'users', attributes: ['id', 'username', 'email', 'role'], limit: 5 }], 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('Error fetching tenants:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenants' }); } }); /** * POST /api/tenants - Create new tenant */ router.post('/', authenticateToken, requireRole(['admin']), validateRequest(tenantSchema), async (req, res) => { try { const tenantData = req.body; // Check if slug is unique const existingTenant = await Tenant.findOne({ where: { slug: tenantData.slug } }); if (existingTenant) { return res.status(409).json({ success: false, message: 'Tenant slug already exists' }); } // Set default features based on subscription type if (!tenantData.features) { tenantData.features = getDefaultFeatures(tenantData.subscription_type); } const tenant = await Tenant.create(tenantData); res.status(201).json({ success: true, data: tenant, message: 'Tenant created successfully' }); } catch (error) { console.error('Error creating tenant:', error); res.status(500).json({ success: false, message: 'Failed to create tenant' }); } }); /** * GET /api/tenants/:id - Get tenant details */ router.get('/:id', authenticateToken, requireRole(['admin']), 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' }); } // Mask sensitive auth configuration const tenantData = tenant.toJSON(); if (tenantData.auth_config) { tenantData.auth_config = maskSensitiveConfig(tenantData.auth_config); } res.json({ success: true, data: tenantData }); } catch (error) { console.error('Error fetching tenant:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenant' }); } }); /** * PUT /api/tenants/:id - Update tenant */ router.put('/:id', authenticateToken, requireRole(['admin']), 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' }); } await tenant.update(req.body); res.json({ success: true, data: tenant, message: 'Tenant updated successfully' }); } catch (error) { console.error('Error updating tenant:', error); res.status(500).json({ success: false, message: 'Failed to update tenant' }); } }); /** * PUT /api/tenants/:id/auth-config - Update tenant authentication configuration */ router.put('/:id/auth-config', authenticateToken, requireRole(['admin']), 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' }); } const { auth_provider, auth_config, user_mapping, role_mapping } = req.body; // Validate auth configuration based on provider const validationSchema = authConfigSchema.fork('auth_provider', (schema) => schema.default(auth_provider) ); const { error } = validationSchema.validate({ auth_provider, ...auth_config }); if (error) { return res.status(400).json({ success: false, message: 'Invalid authentication configuration', details: error.details }); } await tenant.update({ auth_provider, auth_config, user_mapping, role_mapping }); res.json({ success: true, message: 'Authentication configuration updated successfully' }); } catch (error) { console.error('Error updating auth config:', error); res.status(500).json({ success: false, message: 'Failed to update authentication configuration' }); } }); /** * POST /api/tenants/:id/test-auth - Test authentication configuration */ router.post('/:id/test-auth', authenticateToken, requireRole(['admin']), 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' }); } // Use the auth test endpoint const authTestRoute = require('./auth'); req.params.tenantId = tenant.slug; return authTestRoute.testAuth(req, res); } catch (error) { console.error('Error testing auth config:', error); res.status(500).json({ success: false, message: 'Failed to test authentication configuration' }); } }); /** * DELETE /api/tenants/:id - Delete tenant (soft delete) */ router.delete('/:id', authenticateToken, requireRole(['admin']), 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' }); } // Soft delete by setting is_active to false await tenant.update({ is_active: false }); res.json({ success: true, message: 'Tenant deactivated successfully' }); } catch (error) { console.error('Error deleting tenant:', error); res.status(500).json({ success: false, message: 'Failed to delete tenant' }); } }); /** * Helper function to get default features based on subscription type */ function getDefaultFeatures(subscriptionType) { const featureMap = { free: { max_devices: 2, max_users: 1, api_rate_limit: 100, data_retention_days: 7, features: ['basic_detection'] }, basic: { max_devices: 10, max_users: 5, api_rate_limit: 1000, data_retention_days: 90, features: ['basic_detection', 'alerts', 'dashboard'] }, premium: { max_devices: 50, max_users: 20, api_rate_limit: 5000, data_retention_days: 365, features: ['basic_detection', 'alerts', 'dashboard', 'advanced_analytics', 'api_access'] }, enterprise: { max_devices: -1, // Unlimited max_users: -1, // Unlimited api_rate_limit: 50000, data_retention_days: -1, // Unlimited features: ['all'] } }; return featureMap[subscriptionType] || featureMap.basic; } /** * Helper function to mask sensitive configuration data */ function maskSensitiveConfig(config) { const maskedConfig = { ...config }; const sensitiveFields = ['client_secret', 'private_key', 'bind_password', 'admin_password']; sensitiveFields.forEach(field => { if (maskedConfig[field]) { maskedConfig[field] = '****'; } }); return maskedConfig; } module.exports = router;