UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

350 lines 11.8 kB
/** * Authentication and authorization for MCP */ import { createHash, timingSafeEqual } from 'node:crypto'; /** * Authentication manager implementation */ export class AuthManager { config; logger; revokedTokens = new Set(); tokenStore = new Map(); constructor(config, logger) { this.config = config; this.logger = logger; // Start token cleanup timer if (config.enabled) { setInterval(() => { this.cleanupExpiredTokens(); }, 300000); // Clean up every 5 minutes } } async authenticate(credentials) { if (!this.config.enabled) { return { success: true, user: 'anonymous', permissions: ['*'], }; } this.logger.debug('Authenticating credentials', { method: this.config.method, hasCredentials: !!credentials, }); try { switch (this.config.method) { case 'token': return await this.authenticateToken(credentials); case 'basic': return await this.authenticateBasic(credentials); case 'oauth': return await this.authenticateOAuth(credentials); default: return { success: false, error: `Unsupported authentication method: ${this.config.method}`, }; } } catch (error) { this.logger.error('Authentication error', error); return { success: false, error: error instanceof Error ? error instanceof Error ? error.message : String(error) : 'Authentication failed', }; } } authorize(session, permission) { if (!this.config.enabled || !session.authenticated) { return !this.config.enabled; // If auth disabled, allow all } const permissions = session.authData?.permissions || []; // Check for wildcard permission if (permissions.includes('*')) { return true; } // Check for exact permission match if (permissions.includes(permission)) { return true; } // Check for prefix-based permissions (e.g., "tools.*" matches "tools.list") for (const perm of permissions) { if (perm.endsWith('*') && permission.startsWith(perm.slice(0, -1))) { return true; } } this.logger.warn('Authorization denied', { sessionId: session.id, user: session.authData?.user, permission, userPermissions: permissions, }); return false; } async validateToken(token) { if (this.revokedTokens.has(token)) { return { valid: false, error: 'Token has been revoked', }; } const tokenData = this.tokenStore.get(token); if (!tokenData) { return { valid: false, error: 'Invalid token', }; } if (tokenData.expiresAt < new Date()) { this.tokenStore.delete(token); return { valid: false, error: 'Token has expired', }; } return { valid: true, user: tokenData.user, permissions: tokenData.permissions, expiresAt: tokenData.expiresAt, }; } async generateToken(userId, permissions) { const token = this.createSecureToken(); const now = new Date(); const expiresAt = new Date(now.getTime() + (this.config.sessionTimeout || 3600000)); this.tokenStore.set(token, { user: userId, permissions, createdAt: now, expiresAt, }); this.logger.info('Token generated', { userId, permissions, expiresAt, }); return token; } async revokeToken(token) { this.revokedTokens.add(token); this.tokenStore.delete(token); this.logger.info('Token revoked', { token: token.substring(0, 8) + '...' }); } async authenticateToken(credentials) { const token = this.extractToken(credentials); if (!token) { return { success: false, error: 'Token not provided', }; } // Check if it's a stored token (generated by us) const validation = await this.validateToken(token); if (validation.valid) { return { success: true, user: validation.user, permissions: validation.permissions, token, }; } // Check against configured static tokens if (this.config.tokens && this.config.tokens.length > 0) { const isValid = this.config.tokens.some((validToken) => { return this.timingSafeEqual(token, validToken); }); if (isValid) { return { success: true, user: 'token-user', permissions: ['*'], // Static tokens get all permissions token, }; } } return { success: false, error: 'Invalid token', }; } async authenticateBasic(credentials) { const { username, password } = this.extractBasicAuth(credentials); if (!username || !password) { return { success: false, error: 'Username and password required', }; } if (!this.config.users || this.config.users.length === 0) { return { success: false, error: 'No users configured', }; } const user = this.config.users.find((u) => u.username === username); if (!user) { return { success: false, error: 'Invalid username or password', }; } // Verify password const isValidPassword = this.verifyPassword(password, user.password); if (!isValidPassword) { return { success: false, error: 'Invalid username or password', }; } // Generate a session token const token = await this.generateToken(username, user.permissions); return { success: true, user: username, permissions: user.permissions, token, }; } async authenticateOAuth(credentials) { // TODO: Implement OAuth authentication // This would typically involve: // 1. Validating JWT tokens // 2. Checking token expiration // 3. Extracting user info and permissions from token claims this.logger.warn('OAuth authentication not yet implemented'); return { success: false, error: 'OAuth authentication not implemented', }; } extractToken(credentials) { if (typeof credentials === 'string') { return credentials; } if (typeof credentials === 'object' && credentials !== null) { const creds = credentials; if (typeof creds.token === 'string') { return creds.token; } if (typeof creds.authorization === 'string') { const match = creds.authorization.match(/^Bearer\s+(.+)$/i); return match ? match[1] : null; } } return null; } extractBasicAuth(credentials) { if (typeof credentials === 'object' && credentials !== null) { const creds = credentials; if (typeof creds.username === 'string' && typeof creds.password === 'string') { return { username: creds.username, password: creds.password, }; } if (typeof creds.authorization === 'string') { const match = creds.authorization.match(/^Basic\s+(.+)$/i); if (match) { try { const decoded = atob(match[1]); const colonIndex = decoded.indexOf(':'); if (colonIndex >= 0) { return { username: decoded.substring(0, colonIndex), password: decoded.substring(colonIndex + 1), }; } } catch { // Invalid base64 } } } } return {}; } verifyPassword(providedPassword, storedPassword) { // For now, using simple hash comparison // In production, use proper password hashing like bcrypt const hashedProvided = this.hashPassword(providedPassword); const hashedStored = this.hashPassword(storedPassword); return this.timingSafeEqual(hashedProvided, hashedStored); } hashPassword(password) { return createHash('sha256').update(password).digest('hex'); } timingSafeEqual(a, b) { const encoder = new TextEncoder(); const bufferA = encoder.encode(a); const bufferB = encoder.encode(b); if (bufferA.length !== bufferB.length) { return false; } return timingSafeEqual(bufferA, bufferB); } createSecureToken() { // Generate a secure random token const timestamp = Date.now().toString(36); const random1 = Math.random().toString(36).substring(2, 15); const random2 = Math.random().toString(36).substring(2, 15); const hash = createHash('sha256') .update(`${timestamp}${random1}${random2}`) .digest('hex') .substring(0, 32); return `mcp_${timestamp}_${hash}`; } cleanupExpiredTokens() { const now = new Date(); let cleaned = 0; for (const [token, data] of this.tokenStore.entries()) { if (data.expiresAt < now) { this.tokenStore.delete(token); cleaned++; } } if (cleaned > 0) { this.logger.debug('Cleaned up expired tokens', { count: cleaned }); } } } /** * Permission constants for common operations */ export const Permissions = { // System operations SYSTEM_INFO: 'system.info', SYSTEM_HEALTH: 'system.health', SYSTEM_METRICS: 'system.metrics', // Tool operations TOOLS_LIST: 'tools.list', TOOLS_INVOKE: 'tools.invoke', TOOLS_DESCRIBE: 'tools.describe', // Agent operations AGENTS_LIST: 'agents.list', AGENTS_SPAWN: 'agents.spawn', AGENTS_TERMINATE: 'agents.terminate', AGENTS_INFO: 'agents.info', // Task operations TASKS_LIST: 'tasks.list', TASKS_CREATE: 'tasks.create', TASKS_CANCEL: 'tasks.cancel', TASKS_STATUS: 'tasks.status', // Memory operations MEMORY_READ: 'memory.read', MEMORY_WRITE: 'memory.write', MEMORY_QUERY: 'memory.query', MEMORY_DELETE: 'memory.delete', // Administrative operations ADMIN_CONFIG: 'admin.config', ADMIN_LOGS: 'admin.logs', ADMIN_SESSIONS: 'admin.sessions', // Wildcard permission ALL: '*', }; //# sourceMappingURL=auth.js.map