feat: enhance security configuration and SSE management with robust token validation and client tracking

- Refactored `.env.example` with comprehensive security and configuration parameters
- Added new `security.config.ts` for centralized security configuration management
- Improved middleware with enhanced authentication, request validation, and error handling
- Updated SSE routes and manager with advanced client tracking, rate limiting, and connection management
- Implemented more granular token validation with IP-based rate limiting and connection tracking
- Added detailed error responses and improved logging for security-related events
This commit is contained in:
jango-blockchained
2025-02-03 22:02:12 +01:00
parent 840927998e
commit a814c427e9
8 changed files with 605 additions and 227 deletions

View File

@@ -47,6 +47,18 @@ const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
// Security configuration
const SECURITY_CONFIG = {
TOKEN_EXPIRY: 24 * 60 * 60 * 1000, // 24 hours
MAX_TOKEN_AGE: 30 * 24 * 60 * 60 * 1000, // 30 days
MIN_TOKEN_LENGTH: 32,
MAX_FAILED_ATTEMPTS: 5,
LOCKOUT_DURATION: 15 * 60 * 1000, // 15 minutes
};
// Track failed authentication attempts
const failedAttempts = new Map<string, { count: number; lastAttempt: number }>();
export class TokenManager {
/**
* Encrypts a token using AES-256-GCM
@@ -114,45 +126,123 @@ export class TokenManager {
}
/**
* Validates a JWT token
* Validates a JWT token with enhanced security checks
*/
static validateToken(token: string): boolean {
static validateToken(token: string, ip?: string): { valid: boolean; error?: string } {
if (!token || typeof token !== 'string') {
return false;
return { valid: false, error: 'Invalid token format' };
}
// Check for token length
if (token.length < SECURITY_CONFIG.MIN_TOKEN_LENGTH) {
return { valid: false, error: 'Token length below minimum requirement' };
}
// Check for rate limiting
if (ip) {
const attempts = failedAttempts.get(ip);
if (attempts) {
const timeSinceLastAttempt = Date.now() - attempts.lastAttempt;
if (attempts.count >= SECURITY_CONFIG.MAX_FAILED_ATTEMPTS) {
if (timeSinceLastAttempt < SECURITY_CONFIG.LOCKOUT_DURATION) {
return { valid: false, error: 'Too many failed attempts. Please try again later.' };
}
// Reset after lockout period
failedAttempts.delete(ip);
}
}
}
try {
const decoded = jwt.decode(token);
if (!decoded || typeof decoded !== 'object') {
return false;
this.recordFailedAttempt(ip);
return { valid: false, error: 'Invalid token structure' };
}
// Check for expiration
if (!decoded.exp) {
return false;
// Enhanced expiration checks
if (!decoded.exp || !decoded.iat) {
this.recordFailedAttempt(ip);
return { valid: false, error: 'Token missing required claims' };
}
const now = Math.floor(Date.now() / 1000);
if (decoded.exp <= now) {
return false;
this.recordFailedAttempt(ip);
return { valid: false, error: 'Token has expired' };
}
// Verify signature using the secret from environment variable
// Check token age
const tokenAge = (now - decoded.iat) * 1000;
if (tokenAge > SECURITY_CONFIG.MAX_TOKEN_AGE) {
this.recordFailedAttempt(ip);
return { valid: false, error: 'Token exceeds maximum age limit' };
}
// Verify signature
const secret = process.env.JWT_SECRET;
if (!secret) {
return false;
return { valid: false, error: 'JWT secret not configured' };
}
try {
jwt.verify(token, secret);
return true;
} catch {
return false;
// Reset failed attempts on successful validation
if (ip) {
failedAttempts.delete(ip);
}
return { valid: true };
} catch (error) {
this.recordFailedAttempt(ip);
return { valid: false, error: 'Invalid token signature' };
}
} catch {
return false;
} catch (error) {
this.recordFailedAttempt(ip);
return { valid: false, error: 'Token validation failed' };
}
}
/**
* Records a failed authentication attempt
*/
private static recordFailedAttempt(ip?: string): void {
if (!ip) return;
const attempts = failedAttempts.get(ip) || { count: 0, lastAttempt: 0 };
const now = Date.now();
// Reset count if last attempt was outside lockout period
if (now - attempts.lastAttempt > SECURITY_CONFIG.LOCKOUT_DURATION) {
attempts.count = 1;
} else {
attempts.count++;
}
attempts.lastAttempt = now;
failedAttempts.set(ip, attempts);
}
/**
* Generates a new JWT token with enhanced security
*/
static generateToken(payload: object, expiresIn: number = SECURITY_CONFIG.TOKEN_EXPIRY): string {
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error('JWT secret not configured');
}
return jwt.sign(
{
...payload,
iat: Math.floor(Date.now() / 1000),
},
secret,
{
expiresIn: Math.floor(expiresIn / 1000),
algorithm: 'HS256'
}
);
}
}
// Request validation middleware