/** * Tenant Model for Multi-Tenant Support * Stores tenant-specific configuration including authentication providers */ const { DataTypes, Op } = require('sequelize'); module.exports = (sequelize) => { const Tenant = sequelize.define('Tenant', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false, comment: 'Human-readable tenant name' }, slug: { type: DataTypes.STRING, allowNull: false, unique: true, validate: { is: /^[a-z0-9-]+$/i // Alphanumeric and hyphens only }, comment: 'URL-safe tenant identifier (subdomain/path)' }, domain: { type: DataTypes.STRING, allowNull: true, comment: 'Custom domain for this tenant' }, subscription_type: { type: DataTypes.ENUM('free', 'basic', 'premium', 'enterprise'), defaultValue: 'basic', comment: 'Subscription tier' }, is_active: { type: DataTypes.BOOLEAN, defaultValue: true, comment: 'Whether tenant is active' }, // Authentication Configuration auth_provider: { type: DataTypes.ENUM('local', 'saml', 'oauth', 'ldap', 'ad', 'custom_sso'), defaultValue: 'local', comment: 'Primary authentication provider' }, auth_config: { type: DataTypes.JSONB, allowNull: true, comment: 'Authentication provider configuration (encrypted)' }, user_mapping: { type: DataTypes.JSONB, allowNull: true, comment: 'User attribute mapping from external provider' }, role_mapping: { type: DataTypes.JSONB, allowNull: true, comment: 'Role mapping from external provider to internal roles' }, // Tenant Customization branding: { type: DataTypes.JSONB, allowNull: true, comment: 'Tenant-specific branding (logo, colors, etc.)' }, features: { type: DataTypes.JSONB, defaultValue: { max_devices: 10, max_users: 5, api_rate_limit: 1000, data_retention_days: 90, features: ['basic_detection', 'alerts', 'dashboard'] }, comment: 'Tenant feature limits and enabled features' }, // Security Configuration ip_whitelist: { type: DataTypes.JSONB, allowNull: true, defaultValue: null, comment: 'Array of allowed IP addresses/CIDR blocks for this tenant' }, ip_restriction_enabled: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Whether IP restrictions are enabled for this tenant' }, ip_restriction_message: { type: DataTypes.TEXT, allowNull: true, defaultValue: 'Access denied. Your IP address is not authorized to access this tenant.', comment: 'Custom message shown when IP access is denied' }, // Contact Information admin_email: { type: DataTypes.STRING, allowNull: true, validate: { isEmail: true }, comment: 'Primary admin email for this tenant' }, admin_phone: { type: DataTypes.STRING, allowNull: true, comment: 'Primary admin phone for this tenant' }, // Billing Information billing_email: { type: DataTypes.STRING, allowNull: true, validate: { isEmail: true } }, payment_method_id: { type: DataTypes.STRING, allowNull: true, comment: 'Stripe/payment provider customer ID' }, // Metadata metadata: { type: DataTypes.JSONB, allowNull: true, comment: 'Additional tenant metadata' }, // Session Configuration session_timeout: { type: DataTypes.INTEGER, defaultValue: 480, // 8 hours in minutes validate: { min: 15, // Minimum 15 minutes max: 1440 // Maximum 24 hours }, comment: 'Session timeout in minutes' }, require_mfa: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Whether multi-factor authentication is required' }, allow_concurrent_sessions: { type: DataTypes.BOOLEAN, defaultValue: true, comment: 'Whether users can have multiple concurrent sessions' }, allow_registration: { type: DataTypes.BOOLEAN, defaultValue: false, // Default to false for security comment: 'Whether self-registration is allowed for local auth' }, role_mappings: { type: DataTypes.JSONB, allowNull: true, comment: 'Mapping of external groups/attributes to system roles' }, created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, updated_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW } }, { tableName: 'tenants', timestamps: true, createdAt: 'created_at', updatedAt: 'updated_at', indexes: [ { fields: ['slug'], unique: true }, { fields: ['domain'], unique: true, where: { domain: { [Op.ne]: null } } }, { fields: ['is_active'] }, { fields: ['auth_provider'] } ], hooks: { beforeSave: (tenant) => { // Encrypt sensitive auth configuration if (tenant.auth_config && typeof tenant.auth_config === 'object') { // In production, encrypt sensitive fields like client_secret, private_key, etc. const sensitiveFields = ['client_secret', 'private_key', 'bind_password', 'admin_password']; sensitiveFields.forEach(field => { if (tenant.auth_config[field]) { // Simple base64 encoding for demo - use proper encryption in production tenant.auth_config[field] = Buffer.from(tenant.auth_config[field]).toString('base64'); } }); } }, afterFind: (tenants) => { // Decrypt auth configuration after retrieval const processOne = (tenant) => { if (tenant.auth_config && typeof tenant.auth_config === 'object') { const sensitiveFields = ['client_secret', 'private_key', 'bind_password', 'admin_password']; sensitiveFields.forEach(field => { if (tenant.auth_config[field]) { try { tenant.auth_config[field] = Buffer.from(tenant.auth_config[field], 'base64').toString(); } catch (e) { // Field might not be encrypted, leave as-is } } }); } }; if (Array.isArray(tenants)) { tenants.forEach(processOne); } else if (tenants) { processOne(tenants); } } } }); // Associations Tenant.associate = (models) => { // A tenant has many users Tenant.hasMany(models.User, { foreignKey: 'tenant_id', as: 'users' }); // A tenant has many devices Tenant.hasMany(models.Device, { foreignKey: 'tenant_id', as: 'devices' }); // A tenant has many alert rules Tenant.hasMany(models.AlertRule, { foreignKey: 'tenant_id', as: 'alertRules' }); }; return Tenant; };