UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

417 lines (416 loc) 17.3 kB
import crypto from 'crypto'; import { logSecurityEvent } from './audit-logger.js'; import logger from '../../../logger.js'; const ROLE_PERMISSIONS = { admin: [ 'task:create', 'task:read', 'task:update', 'task:delete', 'task:execute', 'project:create', 'project:read', 'project:update', 'project:delete', 'agent:manage', 'agent:assign', 'agent:monitor', 'system:admin', 'system:config', 'system:audit', 'file:read', 'file:write', 'file:execute' ], manager: [ 'task:create', 'task:read', 'task:update', 'task:delete', 'task:execute', 'project:create', 'project:read', 'project:update', 'project:delete', 'agent:assign', 'agent:monitor', 'file:read', 'file:write' ], developer: [ 'task:create', 'task:read', 'task:update', 'task:execute', 'project:read', 'project:update', 'agent:monitor', 'file:read', 'file:write', 'file:execute' ], viewer: [ 'task:read', 'project:read', 'agent:monitor', 'file:read' ], guest: [ 'task:read', 'project:read' ] }; export class AuthenticationIntegration { static instance = null; config; activeSessions = new Map(); activeTokens = new Map(); sessionCleanupTimer = null; constructor(config) { this.config = { enabled: true, tokenSecret: process.env.VIBE_AUTH_SECRET || this.generateSecureSecret(), tokenExpirationMs: 3600000, refreshTokenExpirationMs: 86400000, sessionTimeoutMs: 7200000, maxConcurrentSessions: 5, enableRoleBasedAccess: true, enableAuditLogging: true, requireSecureTokens: true, allowGuestAccess: false, ...config }; this.startSessionCleanup(); logger.info({ enabled: this.config.enabled, roleBasedAccess: this.config.enableRoleBasedAccess, auditLogging: this.config.enableAuditLogging }, 'Authentication Integration initialized'); } static getInstance(config) { if (!AuthenticationIntegration.instance) { AuthenticationIntegration.instance = new AuthenticationIntegration(config); } return AuthenticationIntegration.instance; } async authenticate(userId, role, context) { if (!this.config.enabled) { return { success: true }; } try { const userSessions = Array.from(this.activeSessions.values()) .filter(session => session.userId === userId && session.isActive); if (userSessions.length >= this.config.maxConcurrentSessions) { await logSecurityEvent('authentication', 'medium', 'auth-integration', 'session_limit_exceeded', 'blocked', `User ${userId} exceeded concurrent session limit`, { actor: { userId, ipAddress: context?.ipAddress, userAgent: context?.userAgent }, metadata: { currentSessions: userSessions.length, limit: this.config.maxConcurrentSessions } }); return { success: false, error: 'Maximum concurrent sessions exceeded' }; } const sessionId = this.generateSessionId(); const now = new Date(); const session = { id: sessionId, userId, role, permissions: this.getRolePermissions(role), createdAt: now, lastActivity: now, expiresAt: new Date(now.getTime() + this.config.sessionTimeoutMs), ipAddress: context?.ipAddress, userAgent: context?.userAgent, isActive: true, metadata: context?.metadata }; const token = await this.createAuthToken(userId, sessionId, role, context); this.activeSessions.set(sessionId, session); this.activeTokens.set(token.id, token); await logSecurityEvent('authentication', 'info', 'auth-integration', 'login', 'success', `User ${userId} authenticated successfully`, { actor: { userId, sessionId, ipAddress: context?.ipAddress, userAgent: context?.userAgent }, metadata: { role, permissions: session.permissions.length } }); return { success: true, token, session }; } catch (error) { await logSecurityEvent('authentication', 'high', 'auth-integration', 'login', 'failure', `Authentication failed for user ${userId}`, { actor: { userId, ipAddress: context?.ipAddress, userAgent: context?.userAgent }, metadata: { error: error instanceof Error ? error.message : String(error) } }); return { success: false, error: error instanceof Error ? error.message : 'Authentication failed' }; } } async validateToken(tokenString) { if (!this.config.enabled) { return { success: true }; } try { const token = Array.from(this.activeTokens.values()) .find(t => t.token === tokenString); if (!token) { await logSecurityEvent('authentication', 'medium', 'auth-integration', 'token_validation', 'failure', 'Invalid token provided', { metadata: { tokenPrefix: tokenString.substring(0, 8) } }); return { success: false, error: 'Invalid token' }; } if (Date.now() > token.expiresAt.getTime()) { await logSecurityEvent('authentication', 'low', 'auth-integration', 'token_validation', 'failure', 'Expired token used', { actor: { userId: token.userId, sessionId: token.sessionId }, metadata: { tokenId: token.id, expiredAt: token.expiresAt } }); return { success: false, error: 'Token expired', requiresRefresh: true }; } token.lastUsed = new Date(); const session = this.activeSessions.get(token.sessionId); if (!session || !session.isActive) { return { success: false, error: 'Session not found or inactive' }; } session.lastActivity = new Date(); return { success: true, token, session }; } catch (error) { await logSecurityEvent('authentication', 'high', 'auth-integration', 'token_validation', 'failure', 'Token validation error', { metadata: { error: error instanceof Error ? error.message : String(error) } }); return { success: false, error: 'Token validation failed' }; } } async refreshToken(refreshTokenString) { if (!this.config.enabled) { return { success: true }; } try { const token = Array.from(this.activeTokens.values()) .find(t => t.refreshToken === refreshTokenString); if (!token) { await logSecurityEvent('authentication', 'medium', 'auth-integration', 'token_refresh', 'failure', 'Invalid refresh token provided'); return { success: false, error: 'Invalid refresh token' }; } const session = this.activeSessions.get(token.sessionId); if (!session || !session.isActive) { return { success: false, error: 'Session not found or inactive' }; } const newToken = await this.createAuthToken(token.userId, token.sessionId, session.role, { ipAddress: token.ipAddress, userAgent: token.userAgent, metadata: token.metadata }); this.activeTokens.delete(token.id); this.activeTokens.set(newToken.id, newToken); await logSecurityEvent('authentication', 'info', 'auth-integration', 'token_refresh', 'success', 'Token refreshed successfully', { actor: { userId: token.userId, sessionId: token.sessionId }, metadata: { oldTokenId: token.id, newTokenId: newToken.id } }); return { success: true, token: newToken, session }; } catch (error) { await logSecurityEvent('authentication', 'high', 'auth-integration', 'token_refresh', 'failure', 'Token refresh failed', { metadata: { error: error instanceof Error ? error.message : String(error) } }); return { success: false, error: 'Token refresh failed' }; } } async authorize(sessionId, permission, resource) { if (!this.config.enabled || !this.config.enableRoleBasedAccess) { return { authorized: true }; } try { const session = this.activeSessions.get(sessionId); if (!session || !session.isActive) { await logSecurityEvent('authorization', 'medium', 'auth-integration', 'permission_check', 'blocked', 'Authorization attempted with invalid session', { actor: { sessionId }, resource, metadata: { requiredPermission: permission } }); return { authorized: false, reason: 'Invalid or inactive session', requiredPermission: permission }; } const hasPermission = session.permissions.includes(permission); if (!hasPermission) { await logSecurityEvent('authorization', 'medium', 'auth-integration', 'permission_check', 'blocked', `Access denied: insufficient permissions`, { actor: { userId: session.userId, sessionId }, resource, metadata: { requiredPermission: permission, userRole: session.role, userPermissions: session.permissions } }); return { authorized: false, reason: 'Insufficient permissions', requiredPermission: permission, userRole: session.role, userPermissions: session.permissions }; } session.lastActivity = new Date(); return { authorized: true, userRole: session.role, userPermissions: session.permissions }; } catch (error) { await logSecurityEvent('authorization', 'high', 'auth-integration', 'permission_check', 'failure', 'Authorization check failed', { actor: { sessionId }, resource, metadata: { error: error instanceof Error ? error.message : String(error), requiredPermission: permission } }); return { authorized: false, reason: 'Authorization check failed', requiredPermission: permission }; } } async logout(sessionId) { try { const session = this.activeSessions.get(sessionId); if (!session) { return false; } session.isActive = false; const sessionTokens = Array.from(this.activeTokens.entries()) .filter(([, token]) => token.sessionId === sessionId); for (const [tokenId] of sessionTokens) { this.activeTokens.delete(tokenId); } await logSecurityEvent('authentication', 'info', 'auth-integration', 'logout', 'success', 'User logged out successfully', { actor: { userId: session.userId, sessionId }, metadata: { tokensRemoved: sessionTokens.length } }); return true; } catch (error) { await logSecurityEvent('authentication', 'medium', 'auth-integration', 'logout', 'failure', 'Logout failed', { actor: { sessionId }, metadata: { error: error instanceof Error ? error.message : String(error) } }); return false; } } async createAuthToken(userId, sessionId, role, context) { const tokenId = this.generateTokenId(); const now = new Date(); const token = { id: tokenId, userId, sessionId, token: this.generateSecureToken(), refreshToken: this.generateSecureToken(), issuedAt: now, expiresAt: new Date(now.getTime() + this.config.tokenExpirationMs), lastUsed: now, ipAddress: context?.ipAddress, userAgent: context?.userAgent, permissions: this.getRolePermissions(role), role, metadata: context?.metadata }; return token; } getRolePermissions(role) { return [...ROLE_PERMISSIONS[role]]; } generateSessionId() { return `session_${crypto.randomBytes(16).toString('hex')}_${Date.now()}`; } generateTokenId() { return `token_${crypto.randomBytes(16).toString('hex')}_${Date.now()}`; } generateSecureToken() { const payload = { random: crypto.randomBytes(32).toString('hex'), timestamp: Date.now() }; const signature = crypto .createHmac('sha256', this.config.tokenSecret) .update(JSON.stringify(payload)) .digest('hex'); return `${Buffer.from(JSON.stringify(payload)).toString('base64')}.${signature}`; } generateSecureSecret() { return crypto.randomBytes(64).toString('hex'); } startSessionCleanup() { this.sessionCleanupTimer = setInterval(() => { this.cleanupExpiredSessions(); }, 300000); } async cleanupExpiredSessions() { const now = Date.now(); let cleanedSessions = 0; let cleanedTokens = 0; for (const [sessionId, session] of this.activeSessions) { if (now > session.expiresAt.getTime() || (session.isActive && now - session.lastActivity.getTime() > this.config.sessionTimeoutMs)) { session.isActive = false; this.activeSessions.delete(sessionId); cleanedSessions++; await logSecurityEvent('authentication', 'info', 'auth-integration', 'session_cleanup', 'success', 'Expired session cleaned up', { actor: { userId: session.userId, sessionId }, metadata: { expiredAt: session.expiresAt, lastActivity: session.lastActivity } }); } } for (const [tokenId, token] of this.activeTokens) { if (now > token.expiresAt.getTime() || !this.activeSessions.has(token.sessionId)) { this.activeTokens.delete(tokenId); cleanedTokens++; } } if (cleanedSessions > 0 || cleanedTokens > 0) { logger.debug({ cleanedSessions, cleanedTokens, activeSessions: this.activeSessions.size, activeTokens: this.activeTokens.size }, 'Session and token cleanup completed'); } } getAuthenticationStatistics() { const sessionsByRole = {}; let totalSessionDuration = 0; let sessionCount = 0; for (const session of this.activeSessions.values()) { if (session.isActive) { sessionsByRole[session.role] = (sessionsByRole[session.role] || 0) + 1; totalSessionDuration += Date.now() - session.createdAt.getTime(); sessionCount++; } } return { activeSessions: this.activeSessions.size, activeTokens: this.activeTokens.size, sessionsByRole, averageSessionDuration: sessionCount > 0 ? totalSessionDuration / sessionCount : 0 }; } async shutdown() { if (this.sessionCleanupTimer) { clearInterval(this.sessionCleanupTimer); } const sessionIds = Array.from(this.activeSessions.keys()); for (const sessionId of sessionIds) { await this.logout(sessionId); } this.activeSessions.clear(); this.activeTokens.clear(); logger.info('Authentication Integration shutdown'); } } export function getAuthenticationIntegration() { return AuthenticationIntegration.getInstance(); }