UNPKG

claude-coordination-system

Version:

🤖 Multi-Claude Parallel Processing Coordination System - Organize multiple Claude AI instances to work together seamlessly on complex development tasks

347 lines (298 loc) 9.21 kB
/** * Enterprise Authentication Manager * JWT-based authentication and authorization for Claude Coordination System */ const crypto = require('crypto'); const jwt = require('jsonwebtoken'); const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const { SecurityManager, SecurityError } = require('./security-manager'); class AuthManager { constructor(projectRoot, options = {}) { this.projectRoot = projectRoot; this.securityManager = new SecurityManager(projectRoot); this.options = { jwtSecret: options.jwtSecret || this.generateJWTSecret(), tokenExpiry: options.tokenExpiry || '24h', maxFailedAttempts: options.maxFailedAttempts || 5, lockoutDuration: options.lockoutDuration || 900000, // 15 minutes requireAuth: options.requireAuth || false, ...options }; this.failedAttempts = new Map(); this.activeTokens = new Map(); this.authConfigFile = path.join(projectRoot, '.claude-coord', 'auth.json'); this.loadAuthConfig(); } /** * Generate secure JWT secret */ generateJWTSecret() { return crypto.randomBytes(64).toString('hex'); } /** * Load authentication configuration */ async loadAuthConfig() { try { if (await fs.pathExists(this.authConfigFile)) { const config = await fs.readJson(this.authConfigFile); this.options = { ...this.options, ...config }; } else { await this.saveAuthConfig(); } } catch (error) { console.warn(chalk.yellow('⚠️ Failed to load auth config, using defaults')); } } /** * Save authentication configuration */ async saveAuthConfig() { try { await fs.ensureDir(path.dirname(this.authConfigFile)); const config = { jwtSecret: this.options.jwtSecret, tokenExpiry: this.options.tokenExpiry, maxFailedAttempts: this.options.maxFailedAttempts, lockoutDuration: this.options.lockoutDuration, requireAuth: this.options.requireAuth, createdAt: new Date().toISOString() }; await fs.writeJson(this.authConfigFile, config, { spaces: 2 }); } catch (error) { console.error(chalk.red('❌ Failed to save auth config:'), error.message); } } /** * Generate API key for workers */ generateApiKey(workerId, permissions = ['worker']) { const payload = { workerId, permissions, type: 'api_key', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + this.parseTokenExpiry(this.options.tokenExpiry) }; const token = jwt.sign(payload, this.options.jwtSecret); this.activeTokens.set(token, payload); return token; } /** * Generate session token for coordinators */ generateSessionToken(coordinatorId, permissions = ['coordinator']) { const payload = { coordinatorId, permissions, type: 'session', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + this.parseTokenExpiry(this.options.tokenExpiry) }; const token = jwt.sign(payload, this.options.jwtSecret); this.activeTokens.set(token, payload); return token; } /** * Verify and validate token */ verifyToken(token) { try { if (!token) { throw new SecurityError('No token provided'); } // Check if token is in our active tokens if (!this.activeTokens.has(token)) { throw new SecurityError('Token not found in active tokens'); } // Verify JWT signature and expiration const decoded = jwt.verify(token, this.options.jwtSecret); // Additional validation if (!decoded.workerId && !decoded.coordinatorId) { throw new SecurityError('Invalid token payload'); } if (!decoded.permissions || !Array.isArray(decoded.permissions)) { throw new SecurityError('Invalid token permissions'); } return decoded; } catch (error) { if (error instanceof jwt.JsonWebTokenError) { throw new SecurityError(`Invalid JWT: ${error.message}`); } throw error; } } /** * Check if user has required permission */ hasPermission(token, requiredPermission) { try { const decoded = this.verifyToken(token); return decoded.permissions.includes(requiredPermission) || decoded.permissions.includes('admin'); } catch (error) { return false; } } /** * Revoke token */ revokeToken(token) { if (this.activeTokens.has(token)) { this.activeTokens.delete(token); return true; } return false; } /** * Revoke all tokens for a user */ revokeUserTokens(userId) { let revokedCount = 0; for (const [token, payload] of this.activeTokens.entries()) { if (payload.workerId === userId || payload.coordinatorId === userId) { this.activeTokens.delete(token); revokedCount++; } } return revokedCount; } /** * Record failed authentication attempt */ recordFailedAttempt(identifier) { const now = Date.now(); const attempts = this.failedAttempts.get(identifier) || { count: 0, firstAttempt: now }; attempts.count++; attempts.lastAttempt = now; this.failedAttempts.set(identifier, attempts); // Check if user should be locked out if (attempts.count >= this.options.maxFailedAttempts) { attempts.lockedUntil = now + this.options.lockoutDuration; console.warn(chalk.red(`🚨 User ${identifier} locked out due to ${attempts.count} failed attempts`)); } } /** * Check if user is locked out */ isLockedOut(identifier) { const attempts = this.failedAttempts.get(identifier); if (!attempts || !attempts.lockedUntil) { return false; } if (Date.now() > attempts.lockedUntil) { // Lockout period expired, clear the record this.failedAttempts.delete(identifier); return false; } return true; } /** * Clear failed attempts for user */ clearFailedAttempts(identifier) { this.failedAttempts.delete(identifier); } /** * Middleware for protecting routes/operations */ requireAuth(requiredPermission = null) { return (token) => { if (!this.options.requireAuth) { return true; // Auth disabled } try { const decoded = this.verifyToken(token); if (requiredPermission && !this.hasPermission(token, requiredPermission)) { throw new SecurityError(`Insufficient permissions. Required: ${requiredPermission}`); } return decoded; } catch (error) { throw new SecurityError(`Authentication failed: ${error.message}`); } }; } /** * Get authentication status */ getAuthStatus() { return { requireAuth: this.options.requireAuth, activeTokens: this.activeTokens.size, lockedOutUsers: Array.from(this.failedAttempts.entries()) .filter(([_, attempts]) => attempts.lockedUntil && Date.now() < attempts.lockedUntil) .map(([identifier]) => identifier), failedAttempts: this.failedAttempts.size, tokenExpiry: this.options.tokenExpiry }; } /** * Enable authentication */ async enableAuth() { this.options.requireAuth = true; await this.saveAuthConfig(); console.log(chalk.green('✅ Authentication enabled')); } /** * Disable authentication */ async disableAuth() { this.options.requireAuth = false; await this.saveAuthConfig(); console.log(chalk.yellow('⚠️ Authentication disabled')); } /** * Parse token expiry string to seconds */ parseTokenExpiry(expiry) { if (typeof expiry === 'number') return expiry; const units = { 's': 1, 'm': 60, 'h': 3600, 'd': 86400 }; const match = expiry.match(/^(\d+)([smhd])$/); if (!match) return 86400; // Default 24 hours const [, amount, unit] = match; return parseInt(amount) * units[unit]; } /** * Clean up expired tokens and failed attempts */ cleanup() { const now = Date.now(); // Remove expired tokens for (const [token, payload] of this.activeTokens.entries()) { if (payload.exp * 1000 < now) { this.activeTokens.delete(token); } } // Remove expired lockouts for (const [identifier, attempts] of this.failedAttempts.entries()) { if (attempts.lockedUntil && now > attempts.lockedUntil) { this.failedAttempts.delete(identifier); } } } /** * Get security summary including auth events */ getSecuritySummary() { const baseSummary = this.securityManager.getSecuritySummary(); return { ...baseSummary, authentication: { enabled: this.options.requireAuth, activeTokens: this.activeTokens.size, failedAttempts: this.failedAttempts.size, lockedUsers: Array.from(this.failedAttempts.values()) .filter(attempts => attempts.lockedUntil && Date.now() < attempts.lockedUntil).length } }; } } module.exports = AuthManager;