@gftdcojp/gftd-orm
Version:
Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture
408 lines • 13.7 kB
JavaScript
;
/**
* 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