claude-coordination-system
Version:
🤖 Multi-Claude Parallel Processing Coordination System - Organize multiple Claude AI instances to work together seamlessly on complex development tasks
347 lines (298 loc) • 9.21 kB
JavaScript
/**
* Enterprise Authentication Manager
* JWT-based authentication and authorization for Claude Coordination System
*/
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const { SecurityManager, SecurityError } = require('./security-manager');
class AuthManager {
constructor(projectRoot, options = {}) {
this.projectRoot = projectRoot;
this.securityManager = new SecurityManager(projectRoot);
this.options = {
jwtSecret: options.jwtSecret || this.generateJWTSecret(),
tokenExpiry: options.tokenExpiry || '24h',
maxFailedAttempts: options.maxFailedAttempts || 5,
lockoutDuration: options.lockoutDuration || 900000, // 15 minutes
requireAuth: options.requireAuth || false,
...options
};
this.failedAttempts = new Map();
this.activeTokens = new Map();
this.authConfigFile = path.join(projectRoot, '.claude-coord', 'auth.json');
this.loadAuthConfig();
}
/**
* Generate secure JWT secret
*/
generateJWTSecret() {
return crypto.randomBytes(64).toString('hex');
}
/**
* Load authentication configuration
*/
async loadAuthConfig() {
try {
if (await fs.pathExists(this.authConfigFile)) {
const config = await fs.readJson(this.authConfigFile);
this.options = { ...this.options, ...config };
} else {
await this.saveAuthConfig();
}
} catch (error) {
console.warn(chalk.yellow('⚠️ Failed to load auth config, using defaults'));
}
}
/**
* Save authentication configuration
*/
async saveAuthConfig() {
try {
await fs.ensureDir(path.dirname(this.authConfigFile));
const config = {
jwtSecret: this.options.jwtSecret,
tokenExpiry: this.options.tokenExpiry,
maxFailedAttempts: this.options.maxFailedAttempts,
lockoutDuration: this.options.lockoutDuration,
requireAuth: this.options.requireAuth,
createdAt: new Date().toISOString()
};
await fs.writeJson(this.authConfigFile, config, { spaces: 2 });
} catch (error) {
console.error(chalk.red('❌ Failed to save auth config:'), error.message);
}
}
/**
* Generate API key for workers
*/
generateApiKey(workerId, permissions = ['worker']) {
const payload = {
workerId,
permissions,
type: 'api_key',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + this.parseTokenExpiry(this.options.tokenExpiry)
};
const token = jwt.sign(payload, this.options.jwtSecret);
this.activeTokens.set(token, payload);
return token;
}
/**
* Generate session token for coordinators
*/
generateSessionToken(coordinatorId, permissions = ['coordinator']) {
const payload = {
coordinatorId,
permissions,
type: 'session',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + this.parseTokenExpiry(this.options.tokenExpiry)
};
const token = jwt.sign(payload, this.options.jwtSecret);
this.activeTokens.set(token, payload);
return token;
}
/**
* Verify and validate token
*/
verifyToken(token) {
try {
if (!token) {
throw new SecurityError('No token provided');
}
// Check if token is in our active tokens
if (!this.activeTokens.has(token)) {
throw new SecurityError('Token not found in active tokens');
}
// Verify JWT signature and expiration
const decoded = jwt.verify(token, this.options.jwtSecret);
// Additional validation
if (!decoded.workerId && !decoded.coordinatorId) {
throw new SecurityError('Invalid token payload');
}
if (!decoded.permissions || !Array.isArray(decoded.permissions)) {
throw new SecurityError('Invalid token permissions');
}
return decoded;
} catch (error) {
if (error instanceof jwt.JsonWebTokenError) {
throw new SecurityError(`Invalid JWT: ${error.message}`);
}
throw error;
}
}
/**
* Check if user has required permission
*/
hasPermission(token, requiredPermission) {
try {
const decoded = this.verifyToken(token);
return decoded.permissions.includes(requiredPermission) ||
decoded.permissions.includes('admin');
} catch (error) {
return false;
}
}
/**
* Revoke token
*/
revokeToken(token) {
if (this.activeTokens.has(token)) {
this.activeTokens.delete(token);
return true;
}
return false;
}
/**
* Revoke all tokens for a user
*/
revokeUserTokens(userId) {
let revokedCount = 0;
for (const [token, payload] of this.activeTokens.entries()) {
if (payload.workerId === userId || payload.coordinatorId === userId) {
this.activeTokens.delete(token);
revokedCount++;
}
}
return revokedCount;
}
/**
* Record failed authentication attempt
*/
recordFailedAttempt(identifier) {
const now = Date.now();
const attempts = this.failedAttempts.get(identifier) || { count: 0, firstAttempt: now };
attempts.count++;
attempts.lastAttempt = now;
this.failedAttempts.set(identifier, attempts);
// Check if user should be locked out
if (attempts.count >= this.options.maxFailedAttempts) {
attempts.lockedUntil = now + this.options.lockoutDuration;
console.warn(chalk.red(`🚨 User ${identifier} locked out due to ${attempts.count} failed attempts`));
}
}
/**
* Check if user is locked out
*/
isLockedOut(identifier) {
const attempts = this.failedAttempts.get(identifier);
if (!attempts || !attempts.lockedUntil) {
return false;
}
if (Date.now() > attempts.lockedUntil) {
// Lockout period expired, clear the record
this.failedAttempts.delete(identifier);
return false;
}
return true;
}
/**
* Clear failed attempts for user
*/
clearFailedAttempts(identifier) {
this.failedAttempts.delete(identifier);
}
/**
* Middleware for protecting routes/operations
*/
requireAuth(requiredPermission = null) {
return (token) => {
if (!this.options.requireAuth) {
return true; // Auth disabled
}
try {
const decoded = this.verifyToken(token);
if (requiredPermission && !this.hasPermission(token, requiredPermission)) {
throw new SecurityError(`Insufficient permissions. Required: ${requiredPermission}`);
}
return decoded;
} catch (error) {
throw new SecurityError(`Authentication failed: ${error.message}`);
}
};
}
/**
* Get authentication status
*/
getAuthStatus() {
return {
requireAuth: this.options.requireAuth,
activeTokens: this.activeTokens.size,
lockedOutUsers: Array.from(this.failedAttempts.entries())
.filter(([_, attempts]) => attempts.lockedUntil && Date.now() < attempts.lockedUntil)
.map(([identifier]) => identifier),
failedAttempts: this.failedAttempts.size,
tokenExpiry: this.options.tokenExpiry
};
}
/**
* Enable authentication
*/
async enableAuth() {
this.options.requireAuth = true;
await this.saveAuthConfig();
console.log(chalk.green('✅ Authentication enabled'));
}
/**
* Disable authentication
*/
async disableAuth() {
this.options.requireAuth = false;
await this.saveAuthConfig();
console.log(chalk.yellow('⚠️ Authentication disabled'));
}
/**
* Parse token expiry string to seconds
*/
parseTokenExpiry(expiry) {
if (typeof expiry === 'number') return expiry;
const units = {
's': 1,
'm': 60,
'h': 3600,
'd': 86400
};
const match = expiry.match(/^(\d+)([smhd])$/);
if (!match) return 86400; // Default 24 hours
const [, amount, unit] = match;
return parseInt(amount) * units[unit];
}
/**
* Clean up expired tokens and failed attempts
*/
cleanup() {
const now = Date.now();
// Remove expired tokens
for (const [token, payload] of this.activeTokens.entries()) {
if (payload.exp * 1000 < now) {
this.activeTokens.delete(token);
}
}
// Remove expired lockouts
for (const [identifier, attempts] of this.failedAttempts.entries()) {
if (attempts.lockedUntil && now > attempts.lockedUntil) {
this.failedAttempts.delete(identifier);
}
}
}
/**
* Get security summary including auth events
*/
getSecuritySummary() {
const baseSummary = this.securityManager.getSecuritySummary();
return {
...baseSummary,
authentication: {
enabled: this.options.requireAuth,
activeTokens: this.activeTokens.size,
failedAttempts: this.failedAttempts.size,
lockedUsers: Array.from(this.failedAttempts.values())
.filter(attempts => attempts.lockedUntil && Date.now() < attempts.lockedUntil).length
}
};
}
}
module.exports = AuthManager;