@codai/cbd
Version:
Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server
488 lines • 17.4 kB
JavaScript
/**
* Enhanced Security Framework
* JWT authentication, RBAC, API key management, request signing, encryption, audit logging
*/
import { EventEmitter } from 'events';
import { randomBytes, createHash, createHmac } from 'crypto';
import { performance } from 'perf_hooks';
class EnhancedSecurityFramework extends EventEmitter {
config;
userStore = new Map();
apiKeyStore = new Map();
sessionStore = new Map();
auditLog = [];
rateLimitStore = new Map();
encryptionKeys = new Map();
rbacEngine;
jwtHandler;
encryptionManager;
constructor(config) {
super();
this.config = config;
this.rbacEngine = new RBACEngine(config.rbac);
this.jwtHandler = new JWTHandler(config.jwtSecret, config.jwtExpirationTime);
this.encryptionManager = new EncryptionManager(config.encryption);
this.initializeSecurity();
}
initializeSecurity() {
// Initialize RBAC system
this.rbacEngine.initialize();
// Setup audit logging
if (this.config.auditLogging) {
this.setupAuditLogging();
}
// Initialize encryption keys
this.initializeEncryptionKeys();
// Setup rate limiting cleanup
this.setupRateLimitCleanup();
// Setup session cleanup
this.setupSessionCleanup();
}
/**
* JWT Authentication
*/
async authenticateUser(username, password, options = {}) {
const startTime = performance.now();
try {
// Find user
const user = Array.from(this.userStore.values()).find(u => u.username === username && u.isActive);
if (!user) {
await this.logAuditEvent({
action: 'authentication_failed',
resource: 'auth',
method: 'login',
success: false,
details: { reason: 'user_not_found', username },
duration: performance.now() - startTime
});
return {
success: false,
error: 'Invalid credentials'
};
}
// Verify password (simplified - in reality would use bcrypt)
const isValidPassword = await this.verifyPassword(password, user);
if (!isValidPassword) {
await this.logAuditEvent({
userId: user.id,
action: 'authentication_failed',
resource: 'auth',
method: 'login',
success: false,
details: { reason: 'invalid_password', username },
duration: performance.now() - startTime
});
return {
success: false,
error: 'Invalid credentials'
};
}
// Generate JWT token
const tokenData = {
userId: user.id,
username: user.username,
roles: user.roles,
permissions: user.permissions
};
const expirationTime = options.rememberMe ? '30d' : this.config.jwtExpirationTime;
const token = await this.jwtHandler.generateToken(tokenData, expirationTime);
const expiresAt = new Date(Date.now() + this.parseExpirationTime(expirationTime));
// Update user last login
user.lastLogin = new Date();
// Create session
const sessionId = this.generateSessionId();
this.sessionStore.set(sessionId, {
userId: user.id,
token,
expiresAt,
clientInfo: options.clientInfo,
createdAt: new Date()
});
await this.logAuditEvent({
userId: user.id,
action: 'authentication_success',
resource: 'auth',
method: 'login',
success: true,
details: { username, rememberMe: options.rememberMe },
duration: performance.now() - startTime
});
this.emit('userAuthenticated', {
userId: user.id,
username: user.username,
sessionId
});
return {
success: true,
user,
token,
expiresAt,
permissions: user.permissions
};
}
catch (error) {
await this.logAuditEvent({
action: 'authentication_error',
resource: 'auth',
method: 'login',
success: false,
details: { error: error.message, username },
duration: performance.now() - startTime
});
this.emit('authenticationError', { username, error });
throw error;
}
}
/**
* API Key Management
*/
async generateAPIKey(userId, name, options = {}) {
try {
const keyId = `key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const rawKey = this.generateRandomKey(this.config.apiKeyLength);
const hashedKey = createHash('sha256').update(rawKey).digest('hex');
const apiKey = {
id: keyId,
name,
key: rawKey,
hashedKey,
userId,
permissions: options.permissions || [],
rateLimit: options.rateLimit,
expiresAt: options.expiresAt,
isActive: true,
createdAt: new Date(),
usageCount: 0
};
this.apiKeyStore.set(keyId, apiKey);
await this.logAuditEvent({
userId,
action: 'api_key_generated',
resource: 'api_keys',
method: 'create',
success: true,
details: { keyId, name, permissions: options.permissions }
});
this.emit('apiKeyGenerated', {
keyId,
userId,
name,
permissions: options.permissions
});
return apiKey;
}
catch (error) {
this.emit('apiKeyGenerationError', { userId, name, error });
throw error;
}
}
/**
* Role-Based Access Control (RBAC)
*/
async checkPermission(userId, resource, action, context) {
try {
const user = this.userStore.get(userId);
if (!user || !user.isActive) {
return {
granted: false,
reason: 'User not found or inactive'
};
}
const result = await this.rbacEngine.checkPermission(user.roles, user.permissions, resource, action, context);
await this.logAuditEvent({
userId,
action: 'permission_check',
resource,
method: action,
success: result.granted,
details: {
resource,
action,
granted: result.granted,
reason: result.reason
}
});
return result;
}
catch (error) {
this.emit('permissionCheckError', { userId, resource, action, error });
throw error;
}
}
/**
* Request Signing & Validation
*/
async signRequest(method, url, body, apiKey, timestamp) {
try {
const requestTimestamp = timestamp || Date.now();
const nonce = randomBytes(16).toString('hex');
const stringToSign = [
method.toUpperCase(),
url,
JSON.stringify(body || {}),
apiKey,
requestTimestamp.toString(),
nonce
].join('\n');
const signature = createHmac('sha256', this.config.jwtSecret)
.update(stringToSign)
.digest('hex');
return {
signature,
timestamp: requestTimestamp,
nonce
};
}
catch (error) {
this.emit('requestSigningError', { method, url, error });
throw error;
}
}
async validateRequestSignature(method, url, body, apiKey, signature, timestamp, nonce) {
try {
// Check timestamp validity (5 minute window)
const now = Date.now();
const timeDiff = Math.abs(now - timestamp);
if (timeDiff > 5 * 60 * 1000) {
return {
valid: false,
reason: 'Request timestamp too old'
};
}
// Regenerate signature
const expectedSignature = await this.signRequest(method, url, body, apiKey, timestamp);
// Compare signatures
if (signature !== expectedSignature.signature) {
return {
valid: false,
reason: 'Invalid signature'
};
}
return { valid: true };
}
catch (error) {
this.emit('signatureValidationError', { method, url, error });
return {
valid: false,
reason: 'Signature validation error'
};
}
}
/**
* Data Encryption at Rest
*/
async encryptData(data, keyId) {
try {
const dataString = JSON.stringify(data);
const encryptionKeyId = keyId || 'default';
const result = await this.encryptionManager.encrypt(dataString, encryptionKeyId);
await this.logAuditEvent({
action: 'data_encrypted',
resource: 'encryption',
method: 'encrypt',
success: true,
details: { keyId: encryptionKeyId, dataSize: dataString.length }
});
return result;
}
catch (error) {
this.emit('encryptionError', { error });
throw error;
}
}
async decryptData(encryptedData, keyId, iv) {
try {
const decryptedString = await this.encryptionManager.decrypt(encryptedData, keyId, iv);
const data = JSON.parse(decryptedString);
await this.logAuditEvent({
action: 'data_decrypted',
resource: 'encryption',
method: 'decrypt',
success: true,
details: { keyId, dataSize: decryptedString.length }
});
return data;
}
catch (error) {
this.emit('decryptionError', { keyId, error });
throw error;
}
}
/**
* Audit Logging
*/
async getAuditLogs(filters = {}) {
try {
let filteredLogs = [...this.auditLog];
// Apply filters
if (filters.userId) {
filteredLogs = filteredLogs.filter(log => log.userId === filters.userId);
}
if (filters.action) {
filteredLogs = filteredLogs.filter(log => log.action === filters.action);
}
if (filters.resource) {
filteredLogs = filteredLogs.filter(log => log.resource === filters.resource);
}
if (filters.startDate) {
filteredLogs = filteredLogs.filter(log => log.timestamp >= filters.startDate);
}
if (filters.endDate) {
filteredLogs = filteredLogs.filter(log => log.timestamp <= filters.endDate);
}
if (filters.success !== undefined) {
filteredLogs = filteredLogs.filter(log => log.success === filters.success);
}
// Sort by timestamp (newest first)
filteredLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
// Apply pagination
const offset = filters.offset || 0;
const limit = filters.limit || 100;
const paginatedLogs = filteredLogs.slice(offset, offset + limit);
return {
logs: paginatedLogs,
total: this.auditLog.length,
filtered: filteredLogs.length
};
}
catch (error) {
this.emit('auditLogRetrievalError', { filters, error });
throw error;
}
}
// Private helper methods
async verifyPassword(password, user) {
// Simplified password verification - in reality would use bcrypt
return password === 'password'; // Placeholder
}
parseExpirationTime(expirationTime) {
// Parse expiration time string (e.g., '1h', '7d', '30d')
const unit = expirationTime.slice(-1);
const value = parseInt(expirationTime.slice(0, -1));
switch (unit) {
case 'h': return value * 60 * 60 * 1000;
case 'd': return value * 24 * 60 * 60 * 1000;
case 'm': return value * 60 * 1000;
default: return 60 * 60 * 1000; // Default 1 hour
}
}
generateSessionId() {
return `session_${Date.now()}_${randomBytes(16).toString('hex')}`;
}
generateRandomKey(length) {
return randomBytes(length).toString('base64url');
}
async logAuditEvent(event) {
if (!this.config.auditLogging)
return;
const auditEntry = {
id: `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date(),
userId: event.userId,
action: event.action || 'unknown',
resource: event.resource || 'unknown',
method: event.method || 'unknown',
ip: event.ip || 'unknown',
userAgent: event.userAgent || 'unknown',
success: event.success !== undefined ? event.success : true,
details: event.details,
duration: event.duration || 0
};
this.auditLog.push(auditEntry);
// Emit audit event
this.emit('auditLogEntry', auditEntry);
// Keep only last 10000 entries to prevent memory overflow
if (this.auditLog.length > 10000) {
this.auditLog = this.auditLog.slice(-10000);
}
}
initializeEncryptionKeys() {
// Generate default encryption key
const defaultKey = randomBytes(32); // 256-bit key
this.encryptionKeys.set('default', defaultKey);
}
setupAuditLogging() {
// Setup audit log file rotation and persistence
// This would typically write to files or a database
}
setupRateLimitCleanup() {
if (!this.config.rateLimiting.enabled)
return;
setInterval(() => {
const now = Date.now();
const windowMs = this.config.rateLimiting.windowMs;
Array.from(this.rateLimitStore.entries()).forEach(([key, data]) => {
if (now - data.resetTime > windowMs) {
this.rateLimitStore.delete(key);
}
});
}, this.config.rateLimiting.windowMs);
}
setupSessionCleanup() {
setInterval(() => {
const now = new Date();
Array.from(this.sessionStore.entries()).forEach(([sessionId, session]) => {
if (session.expiresAt < now) {
this.sessionStore.delete(sessionId);
this.emit('sessionExpired', { sessionId, userId: session.userId });
}
});
}, 5 * 60 * 1000); // Check every 5 minutes
}
}
// Supporting classes
class RBACEngine {
config;
constructor(config) {
this.config = config;
}
async initialize() {
// Initialize RBAC system
}
async checkPermission(userRoles, userPermissions, resource, action, context) {
// RBAC permission checking logic
return {
granted: true,
matchedPermissions: userPermissions
};
}
}
class JWTHandler {
secret;
defaultExpiration;
constructor(secret, defaultExpiration) {
this.secret = secret;
this.defaultExpiration = defaultExpiration;
}
async generateToken(payload, expiration) {
// JWT token generation (simplified)
const exp = expiration || this.defaultExpiration;
return `jwt_token_${Date.now()}_${JSON.stringify(payload)}_${exp}`;
}
async verifyToken(token) {
// JWT token verification (simplified)
return { valid: true, payload: {} };
}
}
class EncryptionManager {
config;
constructor(config) {
this.config = config;
}
async encrypt(data, keyId) {
// Data encryption logic
const iv = randomBytes(16).toString('hex');
return {
encryptedData: `encrypted_${data}`,
keyId,
algorithm: this.config.algorithm,
iv
};
}
async decrypt(encryptedData, keyId, iv) {
// Data decryption logic
return encryptedData.replace('encrypted_', '');
}
}
export { EnhancedSecurityFramework };
//# sourceMappingURL=security-framework.js.map