Fix jwt-token
This commit is contained in:
@@ -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}"`);
|
||||||
|
|||||||
@@ -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');
|
||||||
return decoded.tenantId;
|
if (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, {
|
||||||
|
|||||||
@@ -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'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user