Fix jwt-token
This commit is contained in:
267
server/migrations/20250912000001-add-multi-tenant-support.js
Normal file
267
server/migrations/20250912000001-add-multi-tenant-support.js
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Migration: Add Multi-Tenant Support
|
||||
* Adds tenant table and updates user table for multi-tenancy
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
// Create tenants table
|
||||
await queryInterface.createTable('tenants', {
|
||||
id: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
comment: 'Human-readable tenant name'
|
||||
},
|
||||
slug: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: 'URL-safe tenant identifier'
|
||||
},
|
||||
domain: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Custom domain for this tenant'
|
||||
},
|
||||
subscription_type: {
|
||||
type: Sequelize.ENUM('free', 'basic', 'premium', 'enterprise'),
|
||||
defaultValue: 'basic',
|
||||
comment: 'Subscription tier'
|
||||
},
|
||||
is_active: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: true,
|
||||
comment: 'Whether tenant is active'
|
||||
},
|
||||
auth_provider: {
|
||||
type: Sequelize.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'),
|
||||
defaultValue: 'local',
|
||||
comment: 'Primary authentication provider'
|
||||
},
|
||||
auth_config: {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'Authentication provider configuration'
|
||||
},
|
||||
user_mapping: {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'User attribute mapping from external provider'
|
||||
},
|
||||
role_mapping: {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'Role mapping from external provider to internal roles'
|
||||
},
|
||||
branding: {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'Tenant-specific branding'
|
||||
},
|
||||
features: {
|
||||
type: Sequelize.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'
|
||||
},
|
||||
admin_email: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Primary admin email for this tenant'
|
||||
},
|
||||
admin_phone: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Primary admin phone for this tenant'
|
||||
},
|
||||
billing_email: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
payment_method_id: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Payment provider customer ID'
|
||||
},
|
||||
metadata: {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'Additional tenant metadata'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: Sequelize.NOW
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: Sequelize.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// Add indexes to tenants table
|
||||
await queryInterface.addIndex('tenants', ['slug'], { unique: true });
|
||||
await queryInterface.addIndex('tenants', ['domain'], {
|
||||
unique: true,
|
||||
where: { domain: { [Sequelize.Op.ne]: null } }
|
||||
});
|
||||
await queryInterface.addIndex('tenants', ['is_active']);
|
||||
await queryInterface.addIndex('tenants', ['auth_provider']);
|
||||
|
||||
// Add tenant-related columns to users table
|
||||
await queryInterface.addColumn('users', 'tenant_id', {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'tenants',
|
||||
key: 'id'
|
||||
},
|
||||
comment: 'Tenant this user belongs to'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('users', 'external_provider', {
|
||||
type: Sequelize.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'),
|
||||
defaultValue: 'local',
|
||||
comment: 'Authentication provider used for this user'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('users', 'external_id', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: 'User ID from external authentication provider'
|
||||
});
|
||||
|
||||
// Add indexes to users table
|
||||
await queryInterface.addIndex('users', ['tenant_id']);
|
||||
await queryInterface.addIndex('users', ['external_provider']);
|
||||
await queryInterface.addIndex('users', ['external_id', 'tenant_id'], {
|
||||
unique: true,
|
||||
name: 'users_external_id_tenant_unique',
|
||||
where: { external_id: { [Sequelize.Op.ne]: null } }
|
||||
});
|
||||
|
||||
// Create default tenant for backward compatibility
|
||||
const defaultTenantId = await queryInterface.bulkInsert('tenants', [{
|
||||
id: Sequelize.literal('gen_random_uuid()'),
|
||||
name: 'Default Organization',
|
||||
slug: 'default',
|
||||
subscription_type: 'enterprise',
|
||||
is_active: true,
|
||||
auth_provider: 'local',
|
||||
features: JSON.stringify({
|
||||
max_devices: -1,
|
||||
max_users: -1,
|
||||
api_rate_limit: 50000,
|
||||
data_retention_days: -1,
|
||||
features: ['all']
|
||||
}),
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
}], { returning: true });
|
||||
|
||||
// Associate existing users with default tenant
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE users
|
||||
SET tenant_id = (SELECT id FROM tenants WHERE slug = 'default')
|
||||
WHERE tenant_id IS NULL
|
||||
`);
|
||||
|
||||
// Add tenant_id to devices table if it exists
|
||||
try {
|
||||
await queryInterface.addColumn('devices', 'tenant_id', {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'tenants',
|
||||
key: 'id'
|
||||
},
|
||||
comment: 'Tenant this device belongs to'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('devices', ['tenant_id']);
|
||||
|
||||
// Associate existing devices with default tenant
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE devices
|
||||
SET tenant_id = (SELECT id FROM tenants WHERE slug = 'default')
|
||||
WHERE tenant_id IS NULL
|
||||
`);
|
||||
} catch (error) {
|
||||
console.log('Devices table not found or already has tenant_id column');
|
||||
}
|
||||
|
||||
// Add tenant_id to alert_rules table if it exists
|
||||
try {
|
||||
await queryInterface.addColumn('alert_rules', 'tenant_id', {
|
||||
type: Sequelize.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'tenants',
|
||||
key: 'id'
|
||||
},
|
||||
comment: 'Tenant this alert rule belongs to'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('alert_rules', ['tenant_id']);
|
||||
|
||||
// Associate existing alert rules with default tenant
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE alert_rules
|
||||
SET tenant_id = (SELECT id FROM tenants WHERE slug = 'default')
|
||||
WHERE tenant_id IS NULL
|
||||
`);
|
||||
} catch (error) {
|
||||
console.log('Alert_rules table not found or already has tenant_id column');
|
||||
}
|
||||
|
||||
console.log('✅ Multi-tenant support added successfully');
|
||||
console.log('✅ Default tenant created for backward compatibility');
|
||||
console.log('✅ Existing data associated with default tenant');
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
// Remove indexes from users table
|
||||
await queryInterface.removeIndex('users', ['tenant_id']);
|
||||
await queryInterface.removeIndex('users', ['external_provider']);
|
||||
await queryInterface.removeIndex('users', 'users_external_id_tenant_unique');
|
||||
|
||||
// Remove columns from users table
|
||||
await queryInterface.removeColumn('users', 'tenant_id');
|
||||
await queryInterface.removeColumn('users', 'external_provider');
|
||||
await queryInterface.removeColumn('users', 'external_id');
|
||||
|
||||
// Remove tenant_id from other tables
|
||||
try {
|
||||
await queryInterface.removeColumn('devices', 'tenant_id');
|
||||
} catch (error) {
|
||||
console.log('Devices table tenant_id column not found');
|
||||
}
|
||||
|
||||
try {
|
||||
await queryInterface.removeColumn('alert_rules', 'tenant_id');
|
||||
} catch (error) {
|
||||
console.log('Alert_rules table tenant_id column not found');
|
||||
}
|
||||
|
||||
// Drop ENUMs
|
||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_users_external_provider"');
|
||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_tenants_auth_provider"');
|
||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_tenants_subscription_type"');
|
||||
|
||||
// Drop tenants table
|
||||
await queryInterface.dropTable('tenants');
|
||||
|
||||
console.log('✅ Multi-tenant support removed');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user