Fix jwt-token

This commit is contained in:
2025-09-12 12:11:14 +02:00
parent 8b0234986d
commit d8bba047bb
14 changed files with 3236 additions and 1 deletions

313
server/routes/auth.js Normal file
View File

@@ -0,0 +1,313 @@
/**
* Multi-Tenant Authentication Routes
* Handles authentication for different providers and tenants
*/
const express = require('express');
const router = express.Router();
const passport = require('passport');
const session = require('express-session');
const { Tenant } = require('../models');
const MultiTenantAuth = require('../middleware/multi-tenant-auth');
const SAMLAuth = require('../middleware/saml-auth');
const OAuthAuth = require('../middleware/oauth-auth');
const LDAPAuth = require('../middleware/ldap-auth');
// Initialize multi-tenant auth
const multiAuth = new MultiTenantAuth();
// Session middleware for OAuth state management
router.use(session({
secret: process.env.SESSION_SECRET || 'your-session-secret',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 10 * 60 * 1000 } // 10 minutes
}));
// Initialize passport
router.use(passport.initialize());
router.use(passport.session());
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));
/**
* GET /auth/config/:tenantId
* Get authentication configuration for a tenant
*/
router.get('/config/:tenantId', async (req, res) => {
try {
const { tenantId } = req.params;
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
if (!tenant) {
return res.status(404).json({
success: false,
message: 'Tenant not found'
});
}
// Return public auth configuration (no secrets)
const publicConfig = {
provider: tenant.auth_provider,
enabled: tenant.is_active,
features: {
local_login: tenant.auth_provider === 'local',
sso_login: ['saml', 'oauth', 'ldap'].includes(tenant.auth_provider),
registration: tenant.auth_provider === 'local'
}
};
// Add provider-specific public config
if (tenant.auth_provider === 'saml') {
publicConfig.saml = {
login_url: `/auth/saml/${tenantId}/login`,
metadata_url: `/auth/saml/${tenantId}/metadata`
};
} else if (tenant.auth_provider === 'oauth') {
publicConfig.oauth = {
login_url: `/auth/oauth/${tenantId}/login`
};
}
res.json({
success: true,
data: publicConfig
});
} catch (error) {
console.error('Error fetching auth config:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch authentication configuration'
});
}
});
/**
* POST /auth/login
* Universal login endpoint that routes to appropriate provider
*/
router.post('/login', async (req, res, next) => {
try {
// Determine tenant
const tenantId = await multiAuth.determineTenant(req);
const authConfig = await multiAuth.getTenantAuthConfig(tenantId);
req.tenant = { id: tenantId, authConfig };
// Route based on authentication provider
switch (authConfig.type) {
case 'local':
return require('../routes/user').loginLocal(req, res, next);
case 'ldap':
const ldapAuth = new LDAPAuth();
return ldapAuth.authenticate(req, res, next);
case 'saml':
case 'oauth':
return res.status(400).json({
success: false,
message: `Please use SSO login for ${authConfig.type} authentication`,
redirect_url: `/auth/${authConfig.type}/${tenantId}/login`
});
default:
return res.status(400).json({
success: false,
message: 'Authentication provider not configured'
});
}
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
success: false,
message: 'Login failed'
});
}
});
/**
* SAML Authentication Routes
*/
// GET /auth/saml/:tenantId/login - Initiate SAML login
router.get('/saml/:tenantId/login', async (req, res, next) => {
try {
const tenantId = req.params.tenantId;
req.tenant = {
id: tenantId,
authConfig: await multiAuth.getTenantAuthConfig(tenantId)
};
const samlAuth = new SAMLAuth();
return samlAuth.authenticate(req, res, next);
} catch (error) {
console.error('SAML login error:', error);
res.redirect(`/login?error=saml_error&tenant=${req.params.tenantId}`);
}
});
// POST /auth/saml/:tenantId/callback - SAML callback
router.post('/saml/:tenantId/callback', async (req, res, next) => {
const samlAuth = new SAMLAuth();
return samlAuth.handleCallback(req, res, next);
});
// GET /auth/saml/:tenantId/metadata - SAML metadata
router.get('/saml/:tenantId/metadata', async (req, res) => {
try {
const { tenantId } = req.params;
const authConfig = await multiAuth.getTenantAuthConfig(tenantId);
if (authConfig.type !== 'saml') {
return res.status(404).json({ message: 'SAML not configured for this tenant' });
}
const samlAuth = new SAMLAuth();
const metadata = samlAuth.generateMetadata(tenantId, authConfig.config);
res.set('Content-Type', 'application/xml');
res.send(metadata);
} catch (error) {
console.error('SAML metadata error:', error);
res.status(500).json({ message: 'Failed to generate SAML metadata' });
}
});
/**
* OAuth Authentication Routes
*/
// GET /auth/oauth/:tenantId/login - Initiate OAuth login
router.get('/oauth/:tenantId/login', async (req, res, next) => {
try {
const tenantId = req.params.tenantId;
req.tenant = {
id: tenantId,
authConfig: await multiAuth.getTenantAuthConfig(tenantId)
};
const oauthAuth = new OAuthAuth();
return oauthAuth.authenticate(req, res, next);
} catch (error) {
console.error('OAuth login error:', error);
res.redirect(`/login?error=oauth_error&tenant=${req.params.tenantId}`);
}
});
// GET /auth/oauth/:tenantId/callback - OAuth callback
router.get('/oauth/:tenantId/callback', async (req, res, next) => {
const oauthAuth = new OAuthAuth();
return oauthAuth.handleCallback(req, res, next);
});
/**
* Logout endpoint for all providers
*/
router.post('/logout', async (req, res) => {
try {
const tenantId = await multiAuth.determineTenant(req);
const authConfig = await multiAuth.getTenantAuthConfig(tenantId);
// Clear local session
req.logout((err) => {
if (err) console.error('Logout error:', err);
});
// Provider-specific logout
if (authConfig.type === 'saml' && authConfig.config.logout_url) {
return res.json({
success: true,
logout_url: authConfig.config.logout_url,
message: 'Please complete logout with your identity provider'
});
}
res.json({
success: true,
message: 'Logged out successfully'
});
} catch (error) {
console.error('Logout error:', error);
res.status(500).json({
success: false,
message: 'Logout failed'
});
}
});
/**
* Test authentication configuration
*/
router.post('/test/:tenantId', async (req, res) => {
try {
const { tenantId } = req.params;
const authConfig = await multiAuth.getTenantAuthConfig(tenantId);
let testResult = { success: false, message: 'Unknown provider' };
switch (authConfig.type) {
case 'ldap':
const ldapAuth = new LDAPAuth();
try {
await ldapAuth.testConnection(authConfig.config);
testResult = { success: true, message: 'LDAP connection successful' };
} catch (error) {
testResult = { success: false, message: error.message };
}
break;
case 'local':
testResult = { success: true, message: 'Local authentication ready' };
break;
case 'saml':
// Test SAML configuration validity
const requiredSamlFields = ['sso_url', 'certificate', 'issuer'];
const missingSamlFields = requiredSamlFields.filter(field => !authConfig.config[field]);
if (missingSamlFields.length > 0) {
testResult = {
success: false,
message: `Missing SAML configuration: ${missingSamlFields.join(', ')}`
};
} else {
testResult = { success: true, message: 'SAML configuration valid' };
}
break;
case 'oauth':
// Test OAuth configuration validity
const requiredOAuthFields = ['client_id', 'client_secret', 'authorization_url', 'token_url'];
const missingOAuthFields = requiredOAuthFields.filter(field => !authConfig.config[field]);
if (missingOAuthFields.length > 0) {
testResult = {
success: false,
message: `Missing OAuth configuration: ${missingOAuthFields.join(', ')}`
};
} else {
testResult = { success: true, message: 'OAuth configuration valid' };
}
break;
}
res.json(testResult);
} catch (error) {
console.error('Auth test error:', error);
res.status(500).json({
success: false,
message: 'Authentication test failed'
});
}
});
module.exports = router;

381
server/routes/tenants.js Normal file
View 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;