@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
346 lines • 11.7 kB
JavaScript
/**
* 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