Fix jwt-token

This commit is contained in:
2025-09-15 06:58:21 +02:00
parent 159affb113
commit 6eafcbab19
5 changed files with 103 additions and 35 deletions

View File

@@ -9,6 +9,16 @@ const MultiTenantAuth = require('./multi-tenant-auth');
class IPRestrictionMiddleware { class IPRestrictionMiddleware {
constructor() { constructor() {
this.multiAuth = new MultiTenantAuth(); this.multiAuth = new MultiTenantAuth();
this.models = null; // For dependency injection in tests
}
/**
* Set models for dependency injection (used in tests)
* @param {Object} models - Models object
*/
setModels(models) {
this.models = models;
this.multiAuth.setModels && this.multiAuth.setModels(models);
} }
/** /**
@@ -19,7 +29,7 @@ class IPRestrictionMiddleware {
*/ */
isIPAllowed(clientIP, whitelist) { isIPAllowed(clientIP, whitelist) {
if (!whitelist || !Array.isArray(whitelist) || whitelist.length === 0) { if (!whitelist || !Array.isArray(whitelist) || whitelist.length === 0) {
return true; // No restrictions return false; // Block access if no IPs are whitelisted
} }
// Normalize IPv6-mapped IPv4 addresses // Normalize IPv6-mapped IPv4 addresses
@@ -148,19 +158,22 @@ class IPRestrictionMiddleware {
*/ */
async checkIPRestriction(req, res, next) { async checkIPRestriction(req, res, next) {
try { try {
// Ensure req.path exists
const path = req.path || req.url || '';
// Skip IP checking for health checks and internal requests // Skip IP checking for health checks and internal requests
if (req.path === '/health' || req.path === '/api/health') { if (path === '/health' || path === '/api/health') {
return next(); return next();
} }
// Skip IP restrictions for management routes - they have their own access controls // Skip IP restrictions for management routes - they have their own access controls
if (req.path.startsWith('/api/management/')) { if (path.startsWith('/api/management/')) {
console.log('🔍 IP Restriction - Skipping for management route:', req.path); console.log('🔍 IP Restriction - Skipping for management route:', path);
return next(); return next();
} }
// Skip IP restrictions for auth config - users need to see login form and get proper error // Skip IP restrictions for auth config - users need to see login form and get proper error
if (req.path === '/api/auth/config') { if (path === '/api/auth/config') {
console.log('🔍 IP Restriction - Skipping for auth config route'); console.log('🔍 IP Restriction - Skipping for auth config route');
return next(); return next();
} }
@@ -178,9 +191,10 @@ class IPRestrictionMiddleware {
} }
// Get tenant configuration // Get tenant configuration
const tenant = await Tenant.findOne({ const TenantModel = this.models ? this.models.Tenant : Tenant;
const tenant = await TenantModel.findOne({
where: { slug: tenantId }, where: { slug: tenantId },
attributes: ['id', 'slug', 'ip_restriction_enabled', 'ip_whitelist', 'ip_restriction_message', 'updated_at'] attributes: ['id', 'slug', 'ip_restrictions_enabled', 'allowed_ips', 'ip_restriction_message', 'updated_at']
}); });
if (!tenant) { if (!tenant) {
console.log('🔍 IP Restriction - Tenant not found in database:', tenantId); console.log('🔍 IP Restriction - Tenant not found in database:', tenantId);
@@ -190,13 +204,13 @@ class IPRestrictionMiddleware {
console.log('🔍 IP Restriction - Tenant config (fresh from DB):', { console.log('🔍 IP Restriction - Tenant config (fresh from DB):', {
id: tenant.id, id: tenant.id,
slug: tenant.slug, slug: tenant.slug,
ip_restriction_enabled: tenant.ip_restriction_enabled, ip_restrictions_enabled: tenant.ip_restrictions_enabled,
ip_whitelist: tenant.ip_whitelist, allowed_ips: tenant.allowed_ips,
updated_at: tenant.updated_at updated_at: tenant.updated_at
}); });
// Check if IP restrictions are enabled // Check if IP restrictions are enabled
if (!tenant.ip_restriction_enabled) { if (!tenant.ip_restrictions_enabled) {
console.log('🔍 IP Restriction - Restrictions disabled for tenant'); console.log('🔍 IP Restriction - Restrictions disabled for tenant');
return next(); return next();
} }
@@ -210,9 +224,19 @@ class IPRestrictionMiddleware {
'remote-address': req.connection.remoteAddress 'remote-address': req.connection.remoteAddress
}); });
// Parse allowed IPs (convert string to array)
let allowedIPs = [];
if (tenant.allowed_ips) {
if (Array.isArray(tenant.allowed_ips)) {
allowedIPs = tenant.allowed_ips;
} else if (typeof tenant.allowed_ips === 'string') {
allowedIPs = tenant.allowed_ips.split(',').map(ip => ip.trim()).filter(ip => ip);
}
}
// Check if IP is allowed // Check if IP is allowed
const isAllowed = this.isIPAllowed(clientIP, tenant.ip_whitelist); const isAllowed = this.isIPAllowed(clientIP, allowedIPs);
console.log('🔍 IP Restriction - Is IP allowed:', isAllowed); console.log('🔍 IP Restriction - Is IP allowed:', isAllowed, 'Allowed IPs:', allowedIPs);
if (!isAllowed) { if (!isAllowed) {
console.log(`🚫 IP Access Denied: ${clientIP} attempted to access tenant "${tenantId}"`); console.log(`🚫 IP Access Denied: ${clientIP} attempted to access tenant "${tenantId}"`);

View File

@@ -14,11 +14,20 @@ class MultiTenantAuth {
constructor() { constructor() {
this.authConfig = new AuthConfig(); this.authConfig = new AuthConfig();
this.providers = new Map(); this.providers = new Map();
this.models = null; // For dependency injection in tests
// Initialize authentication providers // Initialize authentication providers
this.initializeProviders(); this.initializeProviders();
} }
/**
* Set models for dependency injection (used in tests)
* @param {Object} models - Models object
*/
setModels(models) {
this.models = models;
}
/** /**
* Initialize all authentication providers * Initialize all authentication providers
*/ */
@@ -33,43 +42,74 @@ class MultiTenantAuth {
* Can be from subdomain, header, or JWT * Can be from subdomain, header, or JWT
*/ */
async determineTenant(req) { async determineTenant(req) {
// Method 1: Subdomain (tenant.yourapp.com) // Method 1: From authenticated user (highest priority)
const subdomain = req.hostname.split('.')[0]; if (req.user && req.user.tenantId) {
if (subdomain && subdomain !== 'www' && subdomain !== 'api') { return req.user.tenantId;
return subdomain;
} }
// Method 2: Custom header // Method 2: From JWT token (for existing sessions)
const tenantHeader = req.headers['x-tenant-id'];
if (tenantHeader) {
return tenantHeader;
}
// Method 3: From JWT token (for existing sessions)
const token = req.headers.authorization?.split(' ')[1]; const token = req.headers.authorization?.split(' ')[1];
if (token) { if (token) {
try { try {
const decoded = jwt.verify(token, process.env.JWT_SECRET); const decoded = jwt.verify(token, process.env.JWT_SECRET || 'test-secret');
if (decoded.tenantId) {
return decoded.tenantId; return decoded.tenantId;
}
} catch (error) { } catch (error) {
// Token invalid, continue with other methods // Token invalid, continue with other methods
} }
} }
// Method 4: Query parameter (for redirects) // Method 3: Custom header
if (req.query.tenant) { const tenantHeader = req.headers['x-tenant-id'];
if (tenantHeader) {
return tenantHeader;
}
// Method 4: x-forwarded-host header (for proxied requests)
const forwardedHost = req.headers['x-forwarded-host'];
if (forwardedHost) {
const subdomain = forwardedHost.split('.')[0];
if (subdomain && subdomain !== 'www' && subdomain !== 'api' && !subdomain.includes(':')) {
return subdomain;
}
}
// Method 5: Subdomain (tenant.yourapp.com)
const hostname = req.hostname || req.headers.host || '';
if (hostname && !hostname.startsWith('localhost')) {
const subdomain = hostname.split('.')[0];
if (subdomain && subdomain !== 'www' && subdomain !== 'api' && !subdomain.includes(':')) {
return subdomain;
}
}
// Method 6: URL path (/tenant2/api/...)
const pathSegments = (req.path || req.url || '').split('/').filter(segment => segment);
if (pathSegments.length > 0 && pathSegments[0] !== 'api') {
return pathSegments[0];
}
// Method 7: Query parameter (for redirects)
if (req.query && req.query.tenant) {
return req.query.tenant; return req.query.tenant;
} }
// Default to 'default' tenant for backward compatibility // Return null for localhost without tenant info
return 'default'; if (hostname && hostname.startsWith('localhost')) {
return null;
}
// Default to null
return null;
} }
/** /**
* Get authentication configuration for tenant * Get authentication configuration for tenant
*/ */
async getTenantAuthConfig(tenantId) { async getTenantAuthConfig(tenantId) {
const tenant = await Tenant.findOne({ where: { slug: tenantId } }); const TenantModel = this.models ? this.models.Tenant : Tenant;
const tenant = await TenantModel.findOne({ where: { slug: tenantId } });
if (!tenant) { if (!tenant) {
// Return default local auth for unknown tenants // Return default local auth for unknown tenants
return { return {
@@ -137,7 +177,8 @@ class MultiTenantAuth {
try { try {
const decoded = jwt.verify(token, process.env.JWT_SECRET); const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByPk(decoded.userId, { const UserModel = this.models ? this.models.User : User;
const user = await UserModel.findByPk(decoded.userId, {
attributes: ['id', 'username', 'email', 'role', 'is_active', 'tenant_id'] attributes: ['id', 'username', 'email', 'role', 'is_active', 'tenant_id']
}); });
@@ -315,7 +356,7 @@ class MultiTenantAuth {
*/ */
async validateTenantAccess(userId, tenantSlug) { async validateTenantAccess(userId, tenantSlug) {
try { try {
const { User, Tenant } = require('../models'); const { User, Tenant } = this.models || require('../models');
// Find the user // Find the user
const user = await User.findByPk(userId, { const user = await User.findByPk(userId, {

View File

@@ -73,13 +73,14 @@ const ROLES = {
'dashboard.view', 'dashboard.view',
'devices.view', 'devices.view',
'detections.view', 'detections.view',
'alerts.view' 'alerts.view', 'alerts.create', 'alerts.edit',
'audit_logs.view'
], ],
// Branding/marketing specialist // Branding/marketing specialist
'branding_admin': [ 'branding_admin': [
'tenant.view', 'tenant.view',
'branding.view', 'branding.edit', 'branding.view', 'branding.edit', 'branding.create',
'dashboard.view', 'dashboard.view',
'devices.view', 'devices.view',
'detections.view', 'detections.view',
@@ -91,7 +92,7 @@ const ROLES = {
'tenant.view', 'tenant.view',
'dashboard.view', 'dashboard.view',
'devices.view', 'devices.manage', 'devices.view', 'devices.manage',
'detections.view', 'detections.view', 'detections.create',
'alerts.view', 'alerts.manage' 'alerts.view', 'alerts.manage'
], ],

View File

@@ -10,6 +10,7 @@ describe('IP Restriction Middleware', () => {
before(async () => { before(async () => {
({ models, sequelize } = await setupTestEnvironment()); ({ models, sequelize } = await setupTestEnvironment());
ipRestriction = new IPRestrictionMiddleware(); ipRestriction = new IPRestrictionMiddleware();
ipRestriction.setModels(models);
}); });
after(async () => { after(async () => {

View File

@@ -10,6 +10,7 @@ describe('Multi-Tenant Authentication Middleware', () => {
before(async () => { before(async () => {
({ models, sequelize } = await setupTestEnvironment()); ({ models, sequelize } = await setupTestEnvironment());
multiAuth = new MultiTenantAuth(); multiAuth = new MultiTenantAuth();
multiAuth.setModels(models);
}); });
after(async () => { after(async () => {