UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

346 lines 11.7 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.message : "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