UNPKG

tusktsk

Version:

TuskTsk - The Freedom Configuration Language. Query databases, use any syntax, never bow to any king!

685 lines (589 loc) 21.2 kB
/** * TuskLang Enhanced Security Manager * ================================== * Enterprise-grade security with advanced encryption, threat detection, and audit logging */ const crypto = require('crypto'); const bcrypt = require('bcryptjs'); class SecurityManager { constructor(options = {}) { this.options = { algorithm: options.algorithm || 'aes-256-gcm', keyLength: options.keyLength || 32, saltRounds: options.saltRounds || 12, sessionTimeout: options.sessionTimeout || 3600000, // 1 hour maxLoginAttempts: options.maxLoginAttempts || 5, lockoutDuration: options.lockoutDuration || 900000, // 15 minutes threatDetectionEnabled: options.threatDetectionEnabled !== false, auditLogEnabled: options.auditLogEnabled !== false, encryptionEnabled: options.encryptionEnabled !== false, ...options }; this.users = new Map(); this.sessions = new Map(); this.tokens = new Map(); this.failedAttempts = new Map(); this.blacklistedTokens = new Set(); this.securityPolicies = new Map(); this.auditLog = []; this.threats = []; this.encryptionKeys = new Map(); this.initializeSecurityPolicies(); this.startSecurityMonitoring(); } /** * Initialize default security policies */ initializeSecurityPolicies() { // Password policy this.addSecurityPolicy('password', { name: 'Password Policy', appliesTo: ['register', 'change_password'], validate: (data) => { const password = data.password; if (!password || typeof password !== 'string') { return { valid: false, reason: 'Password is required' }; } if (password.length < 8) { return { valid: false, reason: 'Password must be at least 8 characters' }; } if (!/(?=.*[a-z])/.test(password)) { return { valid: false, reason: 'Password must contain at least one lowercase letter' }; } if (!/(?=.*[A-Z])/.test(password)) { return { valid: false, reason: 'Password must contain at least one uppercase letter' }; } if (!/(?=.*\d)/.test(password)) { return { valid: false, reason: 'Password must contain at least one number' }; } if (!/(?=.*[@$!%*?&])/.test(password)) { return { valid: false, reason: 'Password must contain at least one special character' }; } return { valid: true }; } }); // Username policy this.addSecurityPolicy('username', { name: 'Username Policy', appliesTo: ['register'], validate: (data) => { const username = data.username; if (!username || typeof username !== 'string') { return { valid: false, reason: 'Username is required' }; } if (username.length < 3 || username.length > 50) { return { valid: false, reason: 'Username must be between 3 and 50 characters' }; } if (!/^[a-zA-Z0-9_-]+$/.test(username)) { return { valid: false, reason: 'Username can only contain letters, numbers, underscores, and hyphens' }; } return { valid: true }; } }); // Rate limiting policy this.addSecurityPolicy('rate_limit', { name: 'Rate Limiting Policy', appliesTo: ['login', 'register'], validate: (data) => { const ip = data.ip; const attempts = this.failedAttempts.get(ip) || { count: 0, firstAttempt: Date.now() }; if (attempts.count >= this.options.maxLoginAttempts) { const timeSinceFirstAttempt = Date.now() - attempts.firstAttempt; if (timeSinceFirstAttempt < this.options.lockoutDuration) { return { valid: false, reason: 'Too many failed attempts, please try again later' }; } } return { valid: true }; } }); } /** * Start security monitoring */ startSecurityMonitoring() { if (this.options.threatDetectionEnabled) { // Clean up expired sessions every 5 minutes setInterval(() => { this.cleanupExpiredSessions(); }, 5 * 60 * 1000); // Clean up old audit logs every hour setInterval(() => { this.cleanupAuditLog(); }, 60 * 60 * 1000); // Monitor for threats every 30 seconds setInterval(() => { this.detectThreats(); }, 30 * 1000); } } /** * Register a new user with enhanced security */ async registerUser(username, password, metadata = {}) { try { // Validate against security policies const violations = this.validateSecurityPolicies('register', { username, password, ...metadata }); if (violations.length > 0) { throw new Error(`Security policy violations: ${violations.map(v => v.reason).join(', ')}`); } if (this.users.has(username)) { throw new Error('User already exists'); } const salt = crypto.randomBytes(16).toString('hex'); const hashedPassword = await this.hashPassword(password, salt); const user = { id: this.generateUserId(), username, passwordHash: hashedPassword, salt, metadata: { createdAt: new Date().toISOString(), lastLogin: null, isActive: true, failedAttempts: 0, lastFailedAttempt: null, ...metadata } }; this.users.set(username, user); // Log security event this.logSecurityEvent('user_registered', { username, userId: user.id, ip: metadata.ip || 'unknown', userAgent: metadata.userAgent || 'unknown' }); return { userId: user.id, username }; } catch (error) { this.logSecurityEvent('user_registration_failed', { username, reason: error.message, ip: metadata.ip || 'unknown' }); throw error; } } /** * Authenticate user with enhanced security */ async authenticateUser(username, password, metadata = {}) { try { // Check for account lockout if (this.isAccountLocked(username)) { this.logSecurityEvent('login_blocked', { username, reason: 'Account locked', ip: metadata.ip || 'unknown' }); throw new Error('Account is temporarily locked due to too many failed attempts'); } const user = this.users.get(username); if (!user || !user.metadata.isActive) { this.recordFailedAttempt(username, metadata); throw new Error('Invalid credentials'); } const isValid = await this.verifyPassword(password, user.passwordHash, user.salt); if (!isValid) { this.recordFailedAttempt(username, metadata); throw new Error('Invalid credentials'); } // Clear failed attempts on successful login this.failedAttempts.delete(username); user.metadata.failedAttempts = 0; user.metadata.lastFailedAttempt = null; // Update user metadata user.metadata.lastLogin = new Date().toISOString(); // Create session const session = await this.createSession(user.id, metadata); this.logSecurityEvent('user_login', { username, userId: user.id, sessionId: session.id, ip: metadata.ip || 'unknown', userAgent: metadata.userAgent || 'unknown' }); return session; } catch (error) { this.logSecurityEvent('login_failed', { username, reason: error.message, ip: metadata.ip || 'unknown' }); throw error; } } /** * Create a new session with enhanced security */ async createSession(userId, metadata = {}) { const sessionId = this.generateSessionId(); const token = this.generateToken(); const session = { id: sessionId, userId, token, createdAt: Date.now(), expiresAt: Date.now() + this.options.sessionTimeout, metadata: { ip: metadata.ip || 'unknown', userAgent: metadata.userAgent || 'unknown', ...metadata } }; this.sessions.set(sessionId, session); this.tokens.set(token, sessionId); return { sessionId, token, expiresAt: session.expiresAt }; } /** * Validate session token with enhanced security */ validateSession(token) { const sessionId = this.tokens.get(token); if (!sessionId) { return null; } const session = this.sessions.get(sessionId); if (!session || session.expiresAt < Date.now()) { this.invalidateSession(sessionId); return null; } // Check if token is blacklisted if (this.blacklistedTokens.has(token)) { this.invalidateSession(sessionId); return null; } // Check for suspicious activity if (this.isSuspiciousActivity(session)) { this.logSecurityEvent('suspicious_activity', { sessionId, userId: session.userId, reason: 'Suspicious session activity detected' }); } return session; } /** * Invalidate session with enhanced security */ invalidateSession(sessionId) { const session = this.sessions.get(sessionId); if (session) { this.tokens.delete(session.token); this.blacklistedTokens.add(session.token); this.sessions.delete(sessionId); this.logSecurityEvent('session_invalidated', { sessionId, userId: session.userId, reason: 'Session expired or invalidated' }); } } /** * Logout user with enhanced security */ logout(token) { const session = this.validateSession(token); if (session) { this.invalidateSession(session.id); this.logSecurityEvent('user_logout', { userId: session.userId, sessionId: session.id }); return true; } return false; } /** * Hash password with enhanced security */ async hashPassword(password, salt) { return new Promise((resolve, reject) => { bcrypt.hash(password, this.options.saltRounds, (err, hash) => { if (err) reject(err); else resolve(hash); }); }); } /** * Verify password with enhanced security */ async verifyPassword(password, hash, salt) { return new Promise((resolve, reject) => { bcrypt.compare(password, hash, (err, result) => { if (err) reject(err); else resolve(result); }); }); } /** * Generate secure token */ generateToken() { return crypto.randomBytes(32).toString('hex'); } /** * Generate session ID */ generateSessionId() { return crypto.randomBytes(16).toString('hex'); } /** * Generate user ID */ generateUserId() { return crypto.randomBytes(8).toString('hex'); } /** * Record failed login attempt with enhanced tracking */ recordFailedAttempt(username, metadata = {}) { const attempts = this.failedAttempts.get(username) || { count: 0, firstAttempt: Date.now() }; attempts.count++; attempts.lastAttempt = Date.now(); attempts.ip = metadata.ip || 'unknown'; this.failedAttempts.set(username, attempts); // Update user metadata const user = this.users.get(username); if (user) { user.metadata.failedAttempts = attempts.count; user.metadata.lastFailedAttempt = new Date().toISOString(); } this.logSecurityEvent('failed_login_attempt', { username, attemptCount: attempts.count, ip: metadata.ip || 'unknown', userAgent: metadata.userAgent || 'unknown' }); } /** * Check if account is locked */ isAccountLocked(username) { const attempts = this.failedAttempts.get(username); if (!attempts) return false; const timeSinceFirstAttempt = Date.now() - attempts.firstAttempt; const isLocked = attempts.count >= this.options.maxLoginAttempts && timeSinceFirstAttempt < this.options.lockoutDuration; // Auto-unlock after lockout duration if (!isLocked && timeSinceFirstAttempt >= this.options.lockoutDuration) { this.failedAttempts.delete(username); } return isLocked; } /** * Get user information */ getUserInfo(username) { const user = this.users.get(username); if (!user) return null; return { id: user.id, username: user.username, metadata: { ...user.metadata }, isLocked: this.isAccountLocked(username) }; } /** * Get active sessions */ getActiveSessions() { const activeSessions = []; for (const [sessionId, session] of this.sessions) { if (session.expiresAt > Date.now()) { activeSessions.push({ sessionId, userId: session.userId, createdAt: session.createdAt, expiresAt: session.expiresAt, metadata: session.metadata }); } } return activeSessions; } /** * Clean up expired sessions */ cleanupExpiredSessions() { const now = Date.now(); const expiredSessions = []; for (const [sessionId, session] of this.sessions) { if (session.expiresAt < now) { expiredSessions.push(sessionId); } } expiredSessions.forEach(sessionId => { this.invalidateSession(sessionId); }); return expiredSessions.length; } /** * Log security event with enhanced logging */ logSecurityEvent(action, data) { if (!this.options.auditLogEnabled) return; const event = { id: this.generateEventId(), action, timestamp: new Date().toISOString(), data, severity: this.getEventSeverity(action) }; this.auditLog.push(event); // Keep only last 10000 events if (this.auditLog.length > 10000) { this.auditLog = this.auditLog.slice(-10000); } // Log to console in development if (process.env.NODE_ENV === 'development') { console.log('Security Event:', event); } } /** * Generate event ID */ generateEventId() { return 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } /** * Get event severity */ getEventSeverity(action) { const highSeverity = ['login_failed', 'suspicious_activity', 'security_violation']; const mediumSeverity = ['failed_login_attempt', 'account_locked', 'session_invalidated']; if (highSeverity.includes(action)) return 'high'; if (mediumSeverity.includes(action)) return 'medium'; return 'low'; } /** * Get audit log */ getAuditLog(limit = 100, severity = null) { let events = this.auditLog; if (severity) { events = events.filter(event => event.severity === severity); } return events.slice(-limit); } /** * Clean up old audit logs */ cleanupAuditLog() { const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); this.auditLog = this.auditLog.filter(event => new Date(event.timestamp) > oneWeekAgo ); } /** * Add security policy */ addSecurityPolicy(name, policy) { this.securityPolicies.set(name, policy); } /** * Validate against security policies */ validateSecurityPolicies(action, data) { const violations = []; for (const [name, policy] of this.securityPolicies) { if (policy.appliesTo.includes(action)) { const result = policy.validate(data); if (!result.valid) { violations.push({ policy: name, reason: result.reason }); } } } return violations; } /** * Detect threats */ detectThreats() { const threats = []; // Check for multiple failed attempts from same IP const ipAttempts = new Map(); for (const [username, attempts] of this.failedAttempts) { const ip = attempts.ip; if (ip && ip !== 'unknown') { ipAttempts.set(ip, (ipAttempts.get(ip) || 0) + attempts.count); } } for (const [ip, count] of ipAttempts) { if (count > 20) { threats.push({ type: 'brute_force_attempt', ip, count, timestamp: new Date().toISOString() }); } } // Check for suspicious session activity for (const [sessionId, session] of this.sessions) { if (this.isSuspiciousActivity(session)) { threats.push({ type: 'suspicious_session', sessionId, userId: session.userId, timestamp: new Date().toISOString() }); } } this.threats.push(...threats); // Keep only last 100 threats if (this.threats.length > 100) { this.threats = this.threats.slice(-100); } return threats; } /** * Check for suspicious activity */ isSuspiciousActivity(session) { // Check for rapid session creation const recentSessions = Array.from(this.sessions.values()) .filter(s => s.userId === session.userId) .filter(s => Date.now() - s.createdAt < 60000); // Last minute return recentSessions.length > 5; } /** * Get security statistics */ getSecurityStats() { return { totalUsers: this.users.size, activeSessions: this.getActiveSessions().length, totalSessions: this.sessions.size, blacklistedTokens: this.blacklistedTokens.size, failedAttempts: this.failedAttempts.size, auditLogEntries: this.auditLog.length, securityPolicies: this.securityPolicies.size, threats: this.threats.length, lockedAccounts: Array.from(this.users.values()) .filter(user => this.isAccountLocked(user.username)).length }; } /** * Get threats */ getThreats(limit = 10) { return this.threats.slice(-limit); } /** * Clear security data (for testing) */ clear() { this.users.clear(); this.sessions.clear(); this.tokens.clear(); this.failedAttempts.clear(); this.blacklistedTokens.clear(); this.auditLog = []; this.threats = []; } } module.exports = SecurityManager;