UNPKG

@gftdcojp/gftd-orm

Version:

Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture

408 lines 13.7 kB
"use strict"; /** * Audit Log System - 監査ログ機能 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuditLogManager = exports.AuditEventType = exports.AuditLogLevel = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); /** * 監査ログレベル */ var AuditLogLevel; (function (AuditLogLevel) { AuditLogLevel["INFO"] = "INFO"; AuditLogLevel["WARN"] = "WARN"; AuditLogLevel["ERROR"] = "ERROR"; AuditLogLevel["SECURITY"] = "SECURITY"; })(AuditLogLevel || (exports.AuditLogLevel = AuditLogLevel = {})); /** * 監査ログの種類 */ var AuditEventType; (function (AuditEventType) { // 認証関連 AuditEventType["AUTH_LOGIN"] = "AUTH_LOGIN"; AuditEventType["AUTH_LOGOUT"] = "AUTH_LOGOUT"; AuditEventType["AUTH_FAILED"] = "AUTH_FAILED"; AuditEventType["AUTH_TOKEN_REFRESH"] = "AUTH_TOKEN_REFRESH"; // データアクセス関連 AuditEventType["DATA_READ"] = "DATA_READ"; AuditEventType["DATA_WRITE"] = "DATA_WRITE"; AuditEventType["DATA_DELETE"] = "DATA_DELETE"; AuditEventType["DATA_EXPORT"] = "DATA_EXPORT"; // システム関連 AuditEventType["SYSTEM_START"] = "SYSTEM_START"; AuditEventType["SYSTEM_STOP"] = "SYSTEM_STOP"; AuditEventType["SYSTEM_ERROR"] = "SYSTEM_ERROR"; // セキュリティ関連 AuditEventType["SECURITY_VIOLATION"] = "SECURITY_VIOLATION"; AuditEventType["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED"; AuditEventType["UNAUTHORIZED_ACCESS"] = "UNAUTHORIZED_ACCESS"; // 管理者操作 AuditEventType["ADMIN_USER_CREATE"] = "ADMIN_USER_CREATE"; AuditEventType["ADMIN_USER_DELETE"] = "ADMIN_USER_DELETE"; AuditEventType["ADMIN_POLICY_CHANGE"] = "ADMIN_POLICY_CHANGE"; })(AuditEventType || (exports.AuditEventType = AuditEventType = {})); /** * 監査ログマネージャー */ class AuditLogManager { constructor() { this.logQueue = []; this.isProcessing = false; // セキュリティ設定(デフォルト値) this.config = { enabled: process.env.GFTD_AUDIT_ENABLED?.toLowerCase() === 'true' || true, logFile: process.env.GFTD_AUDIT_LOG_FILE || './logs/audit.log', maxFileSize: 10 * 1024 * 1024, // 10MB maxFiles: 10, compressRotated: false, }; this.ensureLogDirectory(); } /** * シングルトンインスタンスを取得 */ static getInstance() { if (!AuditLogManager.instance) { AuditLogManager.instance = new AuditLogManager(); } return AuditLogManager.instance; } /** * 監査ログを記録 */ static log(entry) { const manager = AuditLogManager.getInstance(); manager.addLogEntry(entry); } /** * 認証成功ログ */ static logAuthSuccess(userId, tenantId, sessionId, ip) { this.log({ level: AuditLogLevel.INFO, eventType: AuditEventType.AUTH_LOGIN, userId, tenantId, sessionId, ip, result: 'SUCCESS', message: `User ${userId} logged in successfully`, }); } /** * 認証失敗ログ */ static logAuthFailure(email, reason, ip) { this.log({ level: AuditLogLevel.WARN, eventType: AuditEventType.AUTH_FAILED, ip, result: 'FAILURE', message: `Authentication failed for ${email}: ${reason}`, details: { email, reason }, }); } /** * データアクセスログ */ static logDataAccess(userId, tenantId, action, resource, success, details) { let eventType; switch (action) { case 'read': eventType = AuditEventType.DATA_READ; break; case 'write': eventType = AuditEventType.DATA_WRITE; break; case 'delete': eventType = AuditEventType.DATA_DELETE; break; default: eventType = AuditEventType.DATA_READ; break; } this.log({ level: AuditLogLevel.INFO, eventType, userId, tenantId, resource, action, result: success ? 'SUCCESS' : 'FAILURE', message: `${action.toUpperCase()} operation on ${resource} by user ${userId}`, details, }); } /** * セキュリティ違反ログ */ static logSecurityViolation(userId, tenantId, violationType, details, ip) { this.log({ level: AuditLogLevel.SECURITY, eventType: AuditEventType.SECURITY_VIOLATION, userId, tenantId, ip, result: 'ERROR', message: `Security violation detected: ${violationType}`, details: { violationType, ...details }, }); } /** * レート制限違反ログ */ static logRateLimitViolation(ip, endpoint, userId) { this.log({ level: AuditLogLevel.WARN, eventType: AuditEventType.RATE_LIMIT_EXCEEDED, userId, ip, resource: endpoint, result: 'ERROR', message: `Rate limit exceeded for ${ip} on ${endpoint}`, details: { endpoint, ip }, }); } /** * 管理者操作ログ */ static logAdminAction(adminUserId, tenantId, action, targetUserId, details) { this.log({ level: AuditLogLevel.INFO, eventType: AuditEventType.ADMIN_USER_CREATE, // 動的に変更する必要がある userId: adminUserId, tenantId, result: 'SUCCESS', message: `Admin ${adminUserId} performed ${action}`, details: { action, targetUserId, ...details }, }); } /** * ログエントリを追加 */ addLogEntry(entry) { if (!this.config.enabled) { return; } const logEntry = { ...entry, timestamp: new Date().toISOString(), }; this.logQueue.push(logEntry); this.processLogQueue(); } /** * ログキューを処理 */ async processLogQueue() { if (this.isProcessing || this.logQueue.length === 0) { return; } this.isProcessing = true; try { while (this.logQueue.length > 0) { const entry = this.logQueue.shift(); if (entry) { await this.writeLogEntry(entry); } } } catch (error) { console.error('Failed to process audit log queue:', error); } finally { this.isProcessing = false; } } /** * ログエントリをファイルに書き込み */ async writeLogEntry(entry) { try { const logLine = JSON.stringify(entry) + '\n'; // ファイルサイズをチェックしてローテーション await this.checkAndRotateLog(); // ファイルに書き込み await fs_1.default.promises.appendFile(this.config.logFile, logLine, 'utf8'); } catch (error) { console.error('Failed to write audit log entry:', error); } } /** * ログディレクトリが存在することを確認 */ ensureLogDirectory() { const logDir = path_1.default.dirname(this.config.logFile); if (!fs_1.default.existsSync(logDir)) { fs_1.default.mkdirSync(logDir, { recursive: true }); } } /** * ログファイルのローテーション */ async checkAndRotateLog() { try { if (!fs_1.default.existsSync(this.config.logFile)) { return; } const stats = await fs_1.default.promises.stat(this.config.logFile); if (stats.size >= this.config.maxFileSize) { await this.rotateLogFile(); } } catch (error) { console.error('Failed to check log file size:', error); } } /** * ログファイルをローテーション */ async rotateLogFile() { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const rotatedFile = `${this.config.logFile}.${timestamp}`; await fs_1.default.promises.rename(this.config.logFile, rotatedFile); // 古いログファイルを削除 await this.cleanupOldLogFiles(); } catch (error) { console.error('Failed to rotate log file:', error); } } /** * 古いログファイルを削除 */ async cleanupOldLogFiles() { try { const logDir = path_1.default.dirname(this.config.logFile); const logFileName = path_1.default.basename(this.config.logFile); const files = await fs_1.default.promises.readdir(logDir); const logFiles = files .filter(file => file.startsWith(logFileName) && file !== logFileName) .map(file => ({ name: file, path: path_1.default.join(logDir, file), stats: fs_1.default.statSync(path_1.default.join(logDir, file)), })) .sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime()); // 最大ファイル数を超えた古いファイルを削除 if (logFiles.length > this.config.maxFiles) { const filesToDelete = logFiles.slice(this.config.maxFiles); for (const file of filesToDelete) { await fs_1.default.promises.unlink(file.path); } } } catch (error) { console.error('Failed to cleanup old log files:', error); } } /** * ログを検索 */ static async searchLogs(criteria) { const manager = AuditLogManager.getInstance(); return manager.searchLogEntries(criteria); } /** * ログエントリを検索 */ async searchLogEntries(criteria) { try { if (!fs_1.default.existsSync(this.config.logFile)) { return []; } const content = await fs_1.default.promises.readFile(this.config.logFile, 'utf8'); const lines = content.split('\n').filter(line => line.trim()); const entries = []; for (const line of lines) { try { const entry = JSON.parse(line); if (this.matchesCriteria(entry, criteria)) { entries.push(entry); } } catch (error) { // 無効なJSONはスキップ } } // 日付でソート(新しい順) entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); // 制限を適用 if (criteria.limit) { return entries.slice(0, criteria.limit); } return entries; } catch (error) { console.error('Failed to search log entries:', error); return []; } } /** * ログエントリが検索条件に一致するかチェック */ matchesCriteria(entry, criteria) { if (criteria.userId && entry.userId !== criteria.userId) { return false; } if (criteria.tenantId && entry.tenantId !== criteria.tenantId) { return false; } if (criteria.eventType && entry.eventType !== criteria.eventType) { return false; } if (criteria.startDate && new Date(entry.timestamp) < criteria.startDate) { return false; } if (criteria.endDate && new Date(entry.timestamp) > criteria.endDate) { return false; } return true; } /** * 統計情報を取得 */ static async getStatistics(tenantId, startDate, endDate) { const manager = AuditLogManager.getInstance(); return manager.getLogStatistics(tenantId, startDate, endDate); } /** * ログ統計を取得 */ async getLogStatistics(tenantId, startDate, endDate) { const entries = await this.searchLogEntries({ tenantId, startDate, endDate, }); const stats = { totalEvents: entries.length, eventsByType: {}, eventsByUser: {}, securityViolations: 0, }; for (const entry of entries) { // イベントタイプ別集計 stats.eventsByType[entry.eventType] = (stats.eventsByType[entry.eventType] || 0) + 1; // ユーザー別集計 if (entry.userId) { stats.eventsByUser[entry.userId] = (stats.eventsByUser[entry.userId] || 0) + 1; } // セキュリティ違反数 if (entry.level === AuditLogLevel.SECURITY) { stats.securityViolations++; } } return stats; } } exports.AuditLogManager = AuditLogManager; //# sourceMappingURL=audit-log.js.map