UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

659 lines 23.3 kB
"use strict"; /** * Authentication and Authorization Manager for MCP * * Handles authentication, authorization, and access control for LLM interactions */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthManager = void 0; const events_1 = require("events"); const crypto = __importStar(require("crypto")); const jwt = __importStar(require("jsonwebtoken")); const types_1 = require("../types"); /** * Authentication and Authorization Manager */ class AuthManager extends events_1.EventEmitter { constructor(authConfig, authzConfig, agentManager) { super(); this.authConfig = authConfig; this.authzConfig = authzConfig; this.agentManager = agentManager; this.sessions = new Map(); this.tokens = new Map(); this.sessionTimeouts = new Map(); this.failedAttempts = new Map(); this.blacklist = new Set(); // Initialize JWT secret this.jwtSecret = process.env.MCP_JWT_SECRET || crypto.randomBytes(32).toString('hex'); // Start session cleanup this.startSessionCleanup(); } /** * Authenticate agent */ async authenticate(agentDID, credentials, method = types_1.AuthenticationMethod.API_KEY) { // Check if agent is blacklisted if (this.blacklist.has(agentDID)) { return { authenticated: false, error: 'Agent is blacklisted' }; } // Check failed attempts const attempts = this.failedAttempts.get(agentDID) || 0; if (attempts >= 5) { this.blacklist.add(agentDID); this.emit('security_alert', { type: 'excessive_failed_attempts', agentDID, attempts }); return { authenticated: false, error: 'Too many failed attempts' }; } try { let authenticated = false; let requiresMFA = false; switch (method) { case types_1.AuthenticationMethod.API_KEY: authenticated = await this.authenticateAPIKey(agentDID, credentials.apiKey); break; case types_1.AuthenticationMethod.JWT: authenticated = await this.authenticateJWT(agentDID, credentials.token); break; case types_1.AuthenticationMethod.OAUTH2: authenticated = await this.authenticateOAuth2(agentDID, credentials.accessToken); break; case types_1.AuthenticationMethod.CERTIFICATE: authenticated = await this.authenticateCertificate(agentDID, credentials.certificate); break; case types_1.AuthenticationMethod.DELEGATION: authenticated = await this.authenticateDelegation(agentDID, credentials.delegationCredential); break; default: throw new Error(`Unsupported authentication method: ${method}`); } if (!authenticated) { this.failedAttempts.set(agentDID, attempts + 1); return { authenticated: false, error: 'Invalid credentials' }; } // Check if MFA is required if (this.authConfig.multiFactorEnabled) { requiresMFA = await this.checkMFARequired(agentDID, method); if (requiresMFA && !credentials.mfaCode) { return { authenticated: false, requiresMFA: true, error: 'MFA code required' }; } if (requiresMFA) { const mfaValid = await this.validateMFA(agentDID, credentials.mfaCode); if (!mfaValid) { return { authenticated: false, error: 'Invalid MFA code' }; } } } // Create authentication token const token = await this.createAuthToken(agentDID, method); // Clear failed attempts this.failedAttempts.delete(agentDID); this.emit('authentication_success', { agentDID, method, tokenId: token.id }); return { authenticated: true, token }; } catch (error) { this.emit('authentication_error', { agentDID, method, error: error.message }); return { authenticated: false, error: error.message }; } } /** * Authorize request */ async authorize(agentDID, resource, action, context) { // Check if authorization is enabled if (!this.authzConfig.enableRBAC && !this.authzConfig.enableABAC) { return { authorized: true }; } const deniedReasons = []; // Get agent permissions const permissions = await this.getAgentPermissions(agentDID); // Check RBAC (Role-Based Access Control) if (this.authzConfig.enableRBAC) { const rbacResult = this.checkRBACPermission(permissions, resource, action); if (!rbacResult.authorized) { deniedReasons.push(`RBAC: ${rbacResult.reason}`); } } // Check ABAC (Attribute-Based Access Control) if (this.authzConfig.enableABAC) { const abacResult = await this.checkABACPermission(agentDID, resource, action, context); if (!abacResult.authorized) { deniedReasons.push(`ABAC: ${abacResult.reason}`); } } // Check ACL rules const aclResult = this.checkACLRules(agentDID, resource, action, context); if (!aclResult.authorized) { deniedReasons.push(`ACL: ${aclResult.reason}`); } // Final decision const authorized = deniedReasons.length === 0 || !this.authzConfig.defaultDeny; if (authorized) { this.emit('authorization_granted', { agentDID, resource, action }); } else { this.emit('authorization_denied', { agentDID, resource, action, reasons: deniedReasons }); } return { authorized, permissions: authorized ? permissions : undefined, deniedReasons: authorized ? undefined : deniedReasons }; } /** * Validate token */ async validateToken(tokenId) { const token = this.tokens.get(tokenId); if (!token) { return null; } // Check expiration if (token.expiresAt < new Date()) { this.tokens.delete(tokenId); return null; } return token; } /** * Refresh token */ async refreshToken(refreshToken) { try { // Decode and verify refresh token const decoded = jwt.verify(refreshToken, this.jwtSecret); const oldToken = this.tokens.get(decoded.tokenId); if (!oldToken || oldToken.refreshToken !== refreshToken) { return { authenticated: false, error: 'Invalid refresh token' }; } // Check refresh token expiration if (oldToken.refreshExpiresAt && oldToken.refreshExpiresAt < new Date()) { return { authenticated: false, error: 'Refresh token expired' }; } // Create new token const newToken = await this.createAuthToken(oldToken.agentDID, oldToken.method); // Invalidate old token this.tokens.delete(oldToken.id); return { authenticated: true, token: newToken }; } catch (error) { return { authenticated: false, error: 'Invalid refresh token' }; } } /** * Create session for authenticated agent */ async createSession(token) { const permissions = await this.getAgentPermissions(token.agentDID); const session = { id: token.sessionId, agentDID: token.agentDID, token, permissions, lastActivity: new Date(), requestCount: 0 }; this.sessions.set(session.id, session); // Setup session timeout this.setupSessionTimeout(session.id); return session; } /** * Get session */ getSession(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return null; } // Update activity session.lastActivity = new Date(); session.requestCount++; // Reset timeout this.setupSessionTimeout(sessionId); return session; } /** * Authenticate API key */ async authenticateAPIKey(agentDID, apiKey) { // In production, this would validate against stored API keys // For now, we'll do a simple validation if (!apiKey || apiKey.length < 32) { return false; } // Validate agent exists if (this.agentManager) { const agent = await this.agentManager.getAgent(agentDID); if (!agent) { return false; } } return true; } /** * Authenticate JWT */ async authenticateJWT(agentDID, token) { try { const decoded = jwt.verify(token, this.jwtSecret); return decoded.agentDID === agentDID; } catch { return false; } } /** * Authenticate OAuth2 */ async authenticateOAuth2(agentDID, accessToken) { // In production, this would validate against OAuth2 provider // For now, we'll do a simple validation return !!(accessToken && accessToken.length > 0); } /** * Authenticate certificate */ async authenticateCertificate(agentDID, certificate) { // In production, this would validate the certificate chain // For now, we'll do a simple validation return !!(certificate && certificate.includes('BEGIN CERTIFICATE')); } /** * Authenticate delegation credential */ async authenticateDelegation(agentDID, delegationCredential) { if (!delegationCredential) { return false; } // Verify the delegation credential is for this agent if (delegationCredential.credentialSubject.agentDID !== agentDID) { return false; } // Verify signature (simplified - in production would use proper verification) return delegationCredential.proof && delegationCredential.proof.jws.length > 0; } /** * Check if MFA is required */ async checkMFARequired(agentDID, method) { // In production, this would check user settings and policies // For now, require MFA for OAuth2 and certificate auth return method === types_1.AuthenticationMethod.OAUTH2 || method === types_1.AuthenticationMethod.CERTIFICATE; } /** * Validate MFA code */ async validateMFA(agentDID, mfaCode) { // In production, this would validate against TOTP/SMS/etc // For now, accept any 6-digit code return /^\d{6}$/.test(mfaCode); } /** * Create authentication token */ async createAuthToken(agentDID, method) { const tokenId = `token-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const token = { id: tokenId, agentDID, sessionId, method, issuedAt: new Date(), expiresAt: new Date(Date.now() + this.authConfig.tokenExpiration), }; // Create refresh token if configured if (this.authConfig.refreshTokenExpiration > 0) { token.refreshToken = jwt.sign({ tokenId, agentDID }, this.jwtSecret, { expiresIn: this.authConfig.refreshTokenExpiration / 1000 }); token.refreshExpiresAt = new Date(Date.now() + this.authConfig.refreshTokenExpiration); } this.tokens.set(tokenId, token); return token; } /** * Get agent permissions */ async getAgentPermissions(agentDID) { const permissions = this.authzConfig.agentPermissions.get(agentDID) || []; // Add default permissions if needed if (permissions.length === 0 && !this.authzConfig.defaultDeny) { permissions.push({ resource: '*', actions: ['read'], expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours }); } return permissions; } /** * Check RBAC permission */ checkRBACPermission(permissions, resource, action) { for (const permission of permissions) { // Check resource match (supports wildcards) const resourceMatch = this.matchResource(permission.resource, resource); if (!resourceMatch) continue; // Check action match const actionMatch = permission.actions.includes(action) || permission.actions.includes('*'); if (!actionMatch) continue; // Check expiration if (permission.expiresAt && permission.expiresAt < new Date()) continue; // Check conditions if (permission.conditions) { const conditionsMatch = this.evaluateConditions(permission.conditions, {}); if (!conditionsMatch) continue; } return { authorized: true }; } return { authorized: false, reason: `No permission for ${action} on ${resource}` }; } /** * Check ABAC permission */ async checkABACPermission(agentDID, resource, action, context) { // In production, this would evaluate attribute-based policies // For now, we'll implement some basic checks // Example: Time-based access const hour = new Date().getHours(); if (context?.requiresBusinessHours && (hour < 9 || hour > 17)) { return { authorized: false, reason: 'Access denied outside business hours' }; } // Example: Resource owner check if (context?.resourceOwner && context.resourceOwner !== agentDID) { return { authorized: false, reason: 'Not resource owner' }; } return { authorized: true }; } /** * Check ACL rules */ checkACLRules(agentDID, resource, action, context) { const acl = this.authzConfig.resourceAccess; let finalDecision = acl.defaultAction; for (const rule of acl.rules) { // Check subject match if (rule.subject !== agentDID && rule.subject !== '*') continue; // Check resource match if (!this.matchResource(rule.resource, resource)) continue; // Check action match if (rule.action !== action && rule.action !== '*') continue; // Check conditions if (rule.conditions) { const conditionsMatch = this.evaluateConditions(rule.conditions, context); if (!conditionsMatch) continue; } // Apply rule effect finalDecision = rule.effect; break; // First matching rule wins } return finalDecision === types_1.AccessAction.ALLOW ? { authorized: true } : { authorized: false, reason: 'ACL denied' }; } /** * Match resource pattern */ matchResource(pattern, resource) { if (pattern === '*') return true; if (pattern === resource) return true; // Support wildcard patterns const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$'); return regex.test(resource); } /** * Evaluate policy conditions */ evaluateConditions(conditions, context) { for (const condition of conditions) { const contextValue = context[condition.type]; switch (condition.operator) { case 'equals': if (contextValue !== condition.value) return false; break; case 'not_equals': if (contextValue === condition.value) return false; break; case 'contains': if (!contextValue?.includes(condition.value)) return false; break; case 'greater_than': if (contextValue <= condition.value) return false; break; case 'less_than': if (contextValue >= condition.value) return false; break; default: return false; } } return true; } /** * Setup session timeout */ setupSessionTimeout(sessionId) { // Clear existing timeout const existingTimeout = this.sessionTimeouts.get(sessionId); if (existingTimeout) { clearTimeout(existingTimeout); } // Set new timeout const timeout = setTimeout(() => { this.invalidateSession(sessionId); }, this.authConfig.sessionTimeout); this.sessionTimeouts.set(sessionId, timeout); } /** * Invalidate session */ invalidateSession(sessionId) { const session = this.sessions.get(sessionId); if (!session) return; // Remove session this.sessions.delete(sessionId); // Remove associated token this.tokens.delete(session.token.id); // Clear timeout const timeout = this.sessionTimeouts.get(sessionId); if (timeout) { clearTimeout(timeout); this.sessionTimeouts.delete(sessionId); } this.emit('session_expired', { sessionId, agentDID: session.agentDID }); } /** * Start session cleanup timer */ startSessionCleanup() { setInterval(() => { const now = new Date(); // Clean expired tokens for (const [tokenId, token] of this.tokens.entries()) { if (token.expiresAt < now) { this.tokens.delete(tokenId); } } // Clean inactive sessions for (const [sessionId, session] of this.sessions.entries()) { const inactivityTime = now.getTime() - session.lastActivity.getTime(); if (inactivityTime > this.authConfig.sessionTimeout) { this.invalidateSession(sessionId); } } }, 60000); // Run every minute } /** * Add permission for agent */ addAgentPermission(agentDID, permission) { const permissions = this.authzConfig.agentPermissions.get(agentDID) || []; permissions.push(permission); this.authzConfig.agentPermissions.set(agentDID, permissions); } /** * Remove permission for agent */ removeAgentPermission(agentDID, resource, action) { const permissions = this.authzConfig.agentPermissions.get(agentDID) || []; const filtered = permissions.filter(p => !(p.resource === resource && p.actions.includes(action))); this.authzConfig.agentPermissions.set(agentDID, filtered); } /** * Add ACL rule */ addACLRule(rule) { this.authzConfig.resourceAccess.rules.push(rule); } /** * Remove ACL rule */ removeACLRule(subject, resource, action) { this.authzConfig.resourceAccess.rules = this.authzConfig.resourceAccess.rules.filter(rule => !(rule.subject === subject && rule.resource === resource && rule.action === action)); } /** * Get authentication statistics */ getStatistics() { return { activeSessions: this.sessions.size, activeTokens: this.tokens.size, failedAttempts: Array.from(this.failedAttempts.values()).reduce((a, b) => a + b, 0), blacklistedAgents: this.blacklist.size }; } /** * Shutdown auth manager */ shutdown() { // Clear all session timeouts for (const timeout of this.sessionTimeouts.values()) { clearTimeout(timeout); } // Clear all data this.sessions.clear(); this.tokens.clear(); this.sessionTimeouts.clear(); this.failedAttempts.clear(); this.blacklist.clear(); this.removeAllListeners(); } } exports.AuthManager = AuthManager; exports.default = AuthManager; //# sourceMappingURL=auth-manager.js.map