Fix jwt-token
This commit is contained in:
381
server/routes/tenants.js
Normal file
381
server/routes/tenants.js
Normal file
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user