Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
273 lines
5.8 KiB
JavaScript
273 lines
5.8 KiB
JavaScript
/**
|
|
* Audit Logger - Phase 8 Enterprise Pattern
|
|
*
|
|
* Tracks all state changes, user actions, and workflow transitions
|
|
* for compliance, debugging, and analytics.
|
|
*/
|
|
|
|
class AuditLogger {
|
|
constructor() {
|
|
this.logs = [];
|
|
this.maxLogs = 1000;
|
|
this.storageKey = 'dss-audit-logs';
|
|
this.sessionId = this.generateSessionId();
|
|
this.logLevel = 'info'; // 'debug', 'info', 'warn', 'error'
|
|
this.loadFromStorage();
|
|
}
|
|
|
|
/**
|
|
* Generate unique session ID
|
|
*/
|
|
generateSessionId() {
|
|
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
/**
|
|
* Create audit log entry
|
|
*/
|
|
createLogEntry(action, category, details = {}, level = 'info') {
|
|
return {
|
|
id: `log-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
|
|
timestamp: new Date().toISOString(),
|
|
sessionId: this.sessionId,
|
|
action,
|
|
category,
|
|
level,
|
|
details,
|
|
userAgent: navigator.userAgent,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Log user action
|
|
*/
|
|
logAction(action, details = {}) {
|
|
const entry = this.createLogEntry(action, 'user_action', details, 'info');
|
|
this.addLog(entry);
|
|
return entry.id;
|
|
}
|
|
|
|
/**
|
|
* Log state change
|
|
*/
|
|
logStateChange(key, oldValue, newValue, details = {}) {
|
|
const entry = this.createLogEntry(
|
|
`state_change`,
|
|
'state',
|
|
{
|
|
key,
|
|
oldValue: this.sanitize(oldValue),
|
|
newValue: this.sanitize(newValue),
|
|
...details
|
|
},
|
|
'info'
|
|
);
|
|
this.addLog(entry);
|
|
return entry.id;
|
|
}
|
|
|
|
/**
|
|
* Log API call
|
|
*/
|
|
logApiCall(method, endpoint, status, responseTime = 0, details = {}) {
|
|
const entry = this.createLogEntry(
|
|
`api_${method.toLowerCase()}`,
|
|
'api',
|
|
{
|
|
endpoint,
|
|
method,
|
|
status,
|
|
responseTime,
|
|
...details
|
|
},
|
|
status >= 400 ? 'warn' : 'info'
|
|
);
|
|
this.addLog(entry);
|
|
return entry.id;
|
|
}
|
|
|
|
/**
|
|
* Log error
|
|
*/
|
|
logError(error, context = '') {
|
|
const entry = this.createLogEntry(
|
|
'error',
|
|
'error',
|
|
{
|
|
message: error.message,
|
|
stack: error.stack,
|
|
context
|
|
},
|
|
'error'
|
|
);
|
|
this.addLog(entry);
|
|
console.error('[AuditLogger]', error);
|
|
return entry.id;
|
|
}
|
|
|
|
/**
|
|
* Log warning
|
|
*/
|
|
logWarning(message, details = {}) {
|
|
const entry = this.createLogEntry(
|
|
'warning',
|
|
'warning',
|
|
{ message, ...details },
|
|
'warn'
|
|
);
|
|
this.addLog(entry);
|
|
return entry.id;
|
|
}
|
|
|
|
/**
|
|
* Log permission check
|
|
*/
|
|
logPermissionCheck(action, allowed, user, reason = '') {
|
|
const entry = this.createLogEntry(
|
|
'permission_check',
|
|
'security',
|
|
{
|
|
action,
|
|
allowed,
|
|
user,
|
|
reason
|
|
},
|
|
allowed ? 'info' : 'warn'
|
|
);
|
|
this.addLog(entry);
|
|
return entry.id;
|
|
}
|
|
|
|
/**
|
|
* Add log entry to collection
|
|
*/
|
|
addLog(entry) {
|
|
this.logs.unshift(entry);
|
|
if (this.logs.length > this.maxLogs) {
|
|
this.logs.pop();
|
|
}
|
|
this.saveToStorage();
|
|
}
|
|
|
|
/**
|
|
* Sanitize sensitive data before logging
|
|
*/
|
|
sanitize(value) {
|
|
if (typeof value !== 'object') return value;
|
|
|
|
const sanitized = { ...value };
|
|
const sensitiveKeys = ['password', 'token', 'apiKey', 'secret', 'key'];
|
|
|
|
for (const key of Object.keys(sanitized)) {
|
|
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
|
|
sanitized[key] = '***REDACTED***';
|
|
}
|
|
}
|
|
|
|
return sanitized;
|
|
}
|
|
|
|
/**
|
|
* Get logs filtered by criteria
|
|
*/
|
|
getLogs(filters = {}) {
|
|
let result = [...this.logs];
|
|
|
|
if (filters.action) {
|
|
result = result.filter(l => l.action === filters.action);
|
|
}
|
|
if (filters.category) {
|
|
result = result.filter(l => l.category === filters.category);
|
|
}
|
|
if (filters.level) {
|
|
result = result.filter(l => l.level === filters.level);
|
|
}
|
|
if (filters.startTime) {
|
|
result = result.filter(l => new Date(l.timestamp) >= new Date(filters.startTime));
|
|
}
|
|
if (filters.endTime) {
|
|
result = result.filter(l => new Date(l.timestamp) <= new Date(filters.endTime));
|
|
}
|
|
if (filters.sessionId) {
|
|
result = result.filter(l => l.sessionId === filters.sessionId);
|
|
}
|
|
if (filters.limit) {
|
|
result = result.slice(0, filters.limit);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get statistics
|
|
*/
|
|
getStats() {
|
|
return {
|
|
totalLogs: this.logs.length,
|
|
sessionId: this.sessionId,
|
|
byCategory: this.logs.reduce((acc, log) => {
|
|
acc[log.category] = (acc[log.category] || 0) + 1;
|
|
return acc;
|
|
}, {}),
|
|
byLevel: this.logs.reduce((acc, log) => {
|
|
acc[log.level] = (acc[log.level] || 0) + 1;
|
|
return acc;
|
|
}, {}),
|
|
oldestLog: this.logs[this.logs.length - 1]?.timestamp,
|
|
newestLog: this.logs[0]?.timestamp,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Export logs as JSON
|
|
*/
|
|
exportLogs(filters = {}) {
|
|
const logs = this.getLogs(filters);
|
|
return JSON.stringify({
|
|
exportDate: new Date().toISOString(),
|
|
sessionId: this.sessionId,
|
|
count: logs.length,
|
|
logs
|
|
}, null, 2);
|
|
}
|
|
|
|
/**
|
|
* Clear all logs
|
|
*/
|
|
clearLogs() {
|
|
this.logs = [];
|
|
this.saveToStorage();
|
|
}
|
|
|
|
/**
|
|
* Save logs to localStorage
|
|
*/
|
|
saveToStorage() {
|
|
try {
|
|
localStorage.setItem(this.storageKey, JSON.stringify(this.logs));
|
|
} catch (e) {
|
|
console.warn('[AuditLogger] Failed to save to storage:', e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load logs from localStorage
|
|
*/
|
|
loadFromStorage() {
|
|
try {
|
|
const stored = localStorage.getItem(this.storageKey);
|
|
if (stored) {
|
|
this.logs = JSON.parse(stored);
|
|
}
|
|
} catch (e) {
|
|
console.warn('[AuditLogger] Failed to load from storage:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create and export singleton
|
|
const auditLogger = new AuditLogger();
|
|
|
|
export { AuditLogger };
|
|
export default auditLogger;
|