optivise
Version:
Optivise - The Ultimate Optimizely Development Assistant with AI-powered features, zero-config setup, and comprehensive development support
634 lines • 24.1 kB
JavaScript
/**
* Advanced Security and Privacy Service
* Provides comprehensive security features including encryption, access control, and privacy protection
*/
import crypto from 'crypto';
import { EventEmitter } from 'events';
export class SecurityService extends EventEmitter {
config;
logger;
activeSessions = new Map();
failedAttempts = new Map();
lockedAccounts = new Map();
auditLogs = [];
accessAttempts = [];
privacyRules = new Map();
dataClassifications = new Map();
encryptionKeys = new Map();
cleanupInterval;
constructor(logger, config) {
super();
this.logger = logger;
this.config = {
encryption: {
algorithm: 'aes-256-gcm',
keyLength: 32,
saltLength: 16
},
authentication: {
tokenExpiry: 24 * 60 * 60 * 1000, // 24 hours
maxFailedAttempts: 5,
lockoutDuration: 15 * 60 * 1000 // 15 minutes
},
privacy: {
dataRetentionDays: 365,
anonymizeAfterDays: 90,
enableAuditLogging: true
},
permissions: {
defaultRole: 'viewer',
hierarchicalRoles: true,
resourceBasedAccess: true
},
...config
};
this.initializeDefaultPrivacyRules();
this.initializeDefaultClassifications();
this.startCleanupTimer();
this.logger.info('Security Service initialized', {
encryption: this.config.encryption.algorithm,
tokenExpiry: this.config.authentication.tokenExpiry,
auditLogging: this.config.privacy.enableAuditLogging
});
}
/**
* Create secure user session with token
*/
createSession(userId, permissions, metadata) {
const sessionId = this.generateSecureId();
const token = this.generateSecureToken();
const now = Date.now();
const session = {
id: sessionId,
userId,
token,
createdAt: now,
expiresAt: now + this.config.authentication.tokenExpiry,
lastActivity: now,
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent,
permissions: new Set(permissions),
isRevoked: false
};
this.activeSessions.set(sessionId, session);
this.logAudit({
userId,
action: 'session_created',
resource: 'authentication',
details: { sessionId, permissions: permissions.length },
severity: 'low',
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent
});
this.logger.info('User session created', { userId, sessionId, permissions: permissions.length });
this.emit('sessionCreated', session);
return session;
}
/**
* Validate session token and refresh if needed
*/
validateSession(token) {
for (const session of this.activeSessions.values()) {
if (session.token === token && !session.isRevoked) {
const now = Date.now();
// Check expiration
if (now > session.expiresAt) {
this.revokeSession(session.id);
return null;
}
// Update last activity
session.lastActivity = now;
// Auto-refresh token if needed (when > 80% of expiry time has passed)
const lifespan = session.expiresAt - session.createdAt;
const elapsed = now - session.createdAt;
if (elapsed > lifespan * 0.8) {
session.token = this.generateSecureToken();
session.expiresAt = now + this.config.authentication.tokenExpiry;
this.logAudit({
userId: session.userId,
action: 'token_refreshed',
resource: 'authentication',
details: { sessionId: session.id },
severity: 'low'
});
}
return session;
}
}
return null;
}
/**
* Revoke user session
*/
revokeSession(sessionId) {
const session = this.activeSessions.get(sessionId);
if (session) {
session.isRevoked = true;
this.activeSessions.delete(sessionId);
this.logAudit({
userId: session.userId,
action: 'session_revoked',
resource: 'authentication',
details: { sessionId },
severity: 'medium'
});
this.logger.info('User session revoked', { userId: session.userId, sessionId });
this.emit('sessionRevoked', session);
return true;
}
return false;
}
/**
* Check if user has permission for resource and action
*/
checkPermission(userId, resource, action, metadata) {
// Check if account is locked
const lockEndTime = this.lockedAccounts.get(userId);
if (lockEndTime && Date.now() < lockEndTime) {
this.recordAccessAttempt(userId, resource, action, false, 'account_locked', metadata);
return false;
}
// Find active session
const session = Array.from(this.activeSessions.values()).find(s => s.userId === userId && !s.isRevoked && Date.now() < s.expiresAt);
if (!session) {
this.recordAccessAttempt(userId, resource, action, false, 'no_valid_session', metadata);
return false;
}
// Check permissions
const requiredPermission = `${resource}:${action}`;
const hasPermission = session.permissions.has(requiredPermission) ||
session.permissions.has(`${resource}:*`) ||
session.permissions.has('*:*');
this.recordAccessAttempt(userId, resource, action, hasPermission, hasPermission ? undefined : 'insufficient_permissions', metadata);
if (hasPermission) {
this.logAudit({
userId,
action: 'permission_granted',
resource,
details: { requiredPermission },
severity: 'low',
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent
});
}
else {
this.logAudit({
userId,
action: 'permission_denied',
resource,
details: { requiredPermission, availablePermissions: Array.from(session.permissions) },
severity: 'medium',
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent
});
}
return hasPermission;
}
/**
* Encrypt sensitive data
*/
encrypt(data, keyId) {
try {
const effectiveKeyId = keyId || 'default';
let encryptionKey = this.encryptionKeys.get(effectiveKeyId);
if (!encryptionKey) {
encryptionKey = crypto.randomBytes(this.config.encryption.keyLength);
this.encryptionKeys.set(effectiveKeyId, encryptionKey);
this.logAudit({
userId: 'system',
action: 'encryption_key_generated',
resource: 'security',
details: { keyId: effectiveKeyId },
severity: 'high'
});
}
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.config.encryption.algorithm, encryptionKey, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted,
iv: iv.toString('hex'),
keyId: effectiveKeyId
};
}
catch (error) {
this.logger.error('Encryption failed', error);
throw new Error('Encryption failed');
}
}
/**
* Decrypt sensitive data
*/
decrypt(encryptedData, iv, keyId) {
try {
const encryptionKey = this.encryptionKeys.get(keyId);
if (!encryptionKey) {
throw new Error('Encryption key not found');
}
const ivBuffer = Buffer.from(iv, 'hex');
const decipher = crypto.createDecipheriv(this.config.encryption.algorithm, encryptionKey, ivBuffer);
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
catch (error) {
this.logger.error('Decryption failed', error, { keyId });
throw new Error('Decryption failed');
}
}
/**
* Hash sensitive data (one-way)
*/
hash(data, salt) {
const effectiveSalt = salt || crypto.randomBytes(this.config.encryption.saltLength).toString('hex');
const hash = crypto.pbkdf2Sync(data, effectiveSalt, 100000, 64, 'sha512');
return {
hash: hash.toString('hex'),
salt: effectiveSalt
};
}
/**
* Verify hashed data
*/
verifyHash(data, hash, salt) {
try {
const computedHash = crypto.pbkdf2Sync(data, salt, 100000, 64, 'sha512');
return computedHash.toString('hex') === hash;
}
catch (error) {
this.logger.error('Hash verification failed', error);
return false;
}
}
/**
* Anonymize user data for privacy compliance
*/
anonymizeData(data, preserveFields = []) {
const anonymized = { ...data };
// Fields to anonymize by default
const sensitiveFields = [
'email', 'name', 'phone', 'address', 'ip', 'userId', 'username'
];
for (const field of sensitiveFields) {
if (field in anonymized && !preserveFields.includes(field)) {
if (typeof anonymized[field] === 'string') {
anonymized[field] = this.generateAnonymizedValue(field, anonymized[field]);
}
}
}
this.logAudit({
userId: 'system',
action: 'data_anonymized',
resource: 'privacy',
details: { anonymizedFields: sensitiveFields.filter(f => f in data && !preserveFields.includes(f)) },
severity: 'medium'
});
return anonymized;
}
/**
* Classify data based on sensitivity
*/
classifyData(data, resourceType) {
let classification = this.dataClassifications.get(resourceType) || {
level: 'internal',
categories: ['general'],
retentionPeriod: this.config.privacy.dataRetentionDays,
encryptionRequired: false,
accessRestrictions: []
};
// Check for sensitive data patterns
const dataString = JSON.stringify(data).toLowerCase();
if (this.containsSensitiveInfo(dataString)) {
classification = {
...classification,
level: 'confidential',
encryptionRequired: true,
accessRestrictions: ['authenticated_users_only']
};
}
if (this.containsPersonalInfo(dataString)) {
classification = {
...classification,
level: 'restricted',
categories: [...classification.categories, 'pii'],
encryptionRequired: true,
accessRestrictions: [...classification.accessRestrictions, 'privacy_officer_approval']
};
}
return classification;
}
/**
* Apply privacy rule to data operation
*/
applyPrivacyRule(ruleId, operation, data) {
const rule = this.privacyRules.get(ruleId);
if (!rule?.enabled) {
return { allowed: true, requirements: [] };
}
const applicableAction = rule.actions.find(action => action.trigger === operation);
if (!applicableAction) {
return { allowed: true, requirements: [] };
}
// Check if data contains any of the rule's data types
const dataString = JSON.stringify(data).toLowerCase();
const containsRuleData = rule.dataTypes.some(dataType => dataString.includes(dataType.toLowerCase()));
if (!containsRuleData) {
return { allowed: true, requirements: [] };
}
this.logAudit({
userId: 'system',
action: 'privacy_rule_applied',
resource: 'privacy',
details: { ruleId, operation, dataTypes: rule.dataTypes },
severity: 'medium'
});
return {
allowed: true,
requirements: applicableAction.requirements,
modifications: operation === 'store' ? this.anonymizeData(data) : undefined
};
}
/**
* Get security metrics and statistics
*/
getSecurityMetrics() {
const now = Date.now();
const last24h = now - (24 * 60 * 60 * 1000);
return {
activeSessions: this.activeSessions.size,
failedAttemptsLast24h: this.accessAttempts.filter(attempt => !attempt.success && attempt.timestamp >= last24h).length,
lockedAccounts: Array.from(this.lockedAccounts.values()).filter(lockTime => lockTime > now).length,
auditLogsLast24h: this.auditLogs.filter(log => log.timestamp >= last24h).length,
encryptionKeysCount: this.encryptionKeys.size,
privacyRulesActive: Array.from(this.privacyRules.values()).filter(rule => rule.enabled).length
};
}
/**
* Get audit logs with filtering
*/
getAuditLogs(filters) {
let logs = [...this.auditLogs];
if (filters) {
if (filters.userId)
logs = logs.filter(log => log.userId === filters.userId);
if (filters.action)
logs = logs.filter(log => log.action === filters.action);
if (filters.resource)
logs = logs.filter(log => log.resource === filters.resource);
if (filters.severity)
logs = logs.filter(log => log.severity === filters.severity);
if (filters.startTime)
logs = logs.filter(log => log.timestamp >= filters.startTime);
if (filters.endTime)
logs = logs.filter(log => log.timestamp <= filters.endTime);
}
logs.sort((a, b) => b.timestamp - a.timestamp);
return logs.slice(0, filters?.limit || 1000);
}
/**
* Generate secure random ID
*/
generateSecureId() {
return crypto.randomBytes(16).toString('hex');
}
/**
* Generate secure token
*/
generateSecureToken() {
return crypto.randomBytes(32).toString('base64url');
}
/**
* Record access attempt for security monitoring
*/
recordAccessAttempt(userId, resource, action, success, failureReason, metadata) {
const attempt = {
userId,
resource,
action,
timestamp: Date.now(),
success,
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent,
failureReason
};
this.accessAttempts.push(attempt);
if (!success) {
const failedCount = (this.failedAttempts.get(userId) || 0) + 1;
this.failedAttempts.set(userId, failedCount);
if (failedCount >= this.config.authentication.maxFailedAttempts) {
const lockEndTime = Date.now() + this.config.authentication.lockoutDuration;
this.lockedAccounts.set(userId, lockEndTime);
this.failedAttempts.delete(userId);
this.logAudit({
userId,
action: 'account_locked',
resource: 'security',
details: { reason: 'max_failed_attempts', lockDuration: this.config.authentication.lockoutDuration },
severity: 'high',
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent
});
this.emit('accountLocked', { userId, lockEndTime });
}
}
else {
// Reset failed attempts on successful access
this.failedAttempts.delete(userId);
}
// Keep only recent access attempts
if (this.accessAttempts.length > 10000) {
this.accessAttempts = this.accessAttempts.slice(-5000);
}
}
/**
* Log audit event
*/
logAudit(params) {
if (!this.config.privacy.enableAuditLogging) {
return;
}
const auditLog = {
id: this.generateSecureId(),
timestamp: params.timestamp || Date.now(),
...params
};
this.auditLogs.push(auditLog);
// Keep only recent audit logs
if (this.auditLogs.length > 50000) {
this.auditLogs = this.auditLogs.slice(-25000);
}
this.emit('auditLog', auditLog);
}
/**
* Generate anonymized value for a field
*/
generateAnonymizedValue(fieldType, originalValue) {
switch (fieldType) {
case 'email':
return `user${crypto.randomBytes(4).toString('hex')}.com`;
case 'name':
return `Anonymous User ${crypto.randomBytes(2).toString('hex').toUpperCase()}`;
case 'ip':
return '192.168.1.1';
case 'userId':
case 'username':
return `user_${crypto.randomBytes(6).toString('hex')}`;
default:
return `[ANONYMIZED_${crypto.randomBytes(3).toString('hex').toUpperCase()}]`;
}
}
/**
* Check if data contains sensitive information
*/
containsSensitiveInfo(dataString) {
const sensitivePatterns = [
/password/i, /secret/i, /token/i, /key/i, /credential/i,
/ssn/i, /social.?security/i, /credit.?card/i, /bank.?account/i
];
return sensitivePatterns.some(pattern => pattern.test(dataString));
}
/**
* Check if data contains personal information
*/
containsPersonalInfo(dataString) {
const piiPatterns = [
/email/i, /phone/i, /address/i, /name/i, /birth/i,
/\b\d{3}-?\d{2}-?\d{4}\b/, // SSN pattern
/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/ // Credit card pattern
];
return piiPatterns.some(pattern => pattern.test(dataString));
}
/**
* Initialize default privacy rules
*/
initializeDefaultPrivacyRules() {
const defaultRules = [
{
id: 'gdpr_personal_data',
name: 'GDPR Personal Data Protection',
description: 'Ensures GDPR compliance for personal data processing',
dataTypes: ['email', 'name', 'phone', 'address', 'ip'],
actions: [
{
trigger: 'collect',
requirements: ['explicit_consent', 'purpose_declaration'],
approvals: ['privacy_officer']
},
{
trigger: 'process',
requirements: ['legal_basis', 'purpose_limitation']
},
{
trigger: 'store',
requirements: ['data_minimization', 'encryption', 'retention_limit']
}
],
compliance: ['GDPR'],
enabled: true
}
];
defaultRules.forEach(rule => {
this.privacyRules.set(rule.id, rule);
});
}
/**
* Initialize default data classifications
*/
initializeDefaultClassifications() {
const defaultClassifications = [
['user_session', {
level: 'confidential',
categories: ['authentication'],
retentionPeriod: 30,
encryptionRequired: true,
accessRestrictions: ['authenticated_users_only']
}],
['workspace_data', {
level: 'internal',
categories: ['collaboration'],
retentionPeriod: 365,
encryptionRequired: false,
accessRestrictions: ['workspace_members_only']
}],
['audit_logs', {
level: 'restricted',
categories: ['security'],
retentionPeriod: 2555, // 7 years
encryptionRequired: true,
accessRestrictions: ['security_team_only']
}]
];
defaultClassifications.forEach(([key, classification]) => {
this.dataClassifications.set(key, classification);
});
}
/**
* Start cleanup timer for expired data
*/
startCleanupTimer() {
this.cleanupInterval = setInterval(() => {
this.performSecurityCleanup();
}, 60 * 60 * 1000); // Every hour
}
/**
* Perform security cleanup
*/
performSecurityCleanup() {
const now = Date.now();
// Remove expired sessions
let expiredSessions = 0;
for (const [sessionId, session] of this.activeSessions) {
if (session.isRevoked || now > session.expiresAt) {
this.activeSessions.delete(sessionId);
expiredSessions++;
}
}
// Remove expired account locks
let unlockedAccounts = 0;
for (const [userId, lockEndTime] of this.lockedAccounts) {
if (now > lockEndTime) {
this.lockedAccounts.delete(userId);
unlockedAccounts++;
}
}
// Clean old audit logs based on retention policy
const retentionCutoff = now - (this.config.privacy.dataRetentionDays * 24 * 60 * 60 * 1000);
const beforeAuditCount = this.auditLogs.length;
this.auditLogs = this.auditLogs.filter(log => log.timestamp >= retentionCutoff);
// Clean old access attempts
const accessAttemptCutoff = now - (7 * 24 * 60 * 60 * 1000); // 7 days
const beforeAccessCount = this.accessAttempts.length;
this.accessAttempts = this.accessAttempts.filter(attempt => attempt.timestamp >= accessAttemptCutoff);
if (expiredSessions > 0 || unlockedAccounts > 0 || this.auditLogs.length < beforeAuditCount || this.accessAttempts.length < beforeAccessCount) {
this.logger.debug('Security cleanup completed', {
expiredSessions,
unlockedAccounts,
auditLogsRemoved: beforeAuditCount - this.auditLogs.length,
accessAttemptsRemoved: beforeAccessCount - this.accessAttempts.length
});
}
}
/**
* Cleanup resources
*/
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
// Securely clear encryption keys
this.encryptionKeys.clear();
this.activeSessions.clear();
this.failedAttempts.clear();
this.lockedAccounts.clear();
this.auditLogs = [];
this.accessAttempts = [];
this.privacyRules.clear();
this.dataClassifications.clear();
this.removeAllListeners();
this.logger.info('Security Service destroyed');
}
}
// Global security service instance
export const securityService = (logger, config) => new SecurityService(logger, config);
//# sourceMappingURL=security-service.js.map