anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
659 lines • 23.3 kB
JavaScript
"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