dnsweeper
Version:
Advanced CLI tool for DNS record risk analysis and cleanup. Features CSV import for Cloudflare/Route53, automated risk assessment, and parallel DNS validation.
145 lines (124 loc) • 4.79 kB
text/typescript
import { Request, Response, NextFunction } from 'express';
import { changeHistoryService } from '../services/changeHistoryService';
import type { DnsChangeRecord } from '../types/history';
/**
* DNS変更操作時に自動的に履歴を記録するミドルウェア
*/
export const recordDnsChange = (changeType: 'create' | 'update' | 'delete') => {
return async (req: Request, res: Response, next: NextFunction) => {
// 元のレスポンス処理をフック
const originalSend = res.send.bind(res);
res.send = function(data: any) {
// 成功時のみ履歴を記録
if (res.statusCode >= 200 && res.statusCode < 300) {
recordChange(req, res, changeType, data);
}
return originalSend(data);
};
next();
};
};
async function recordChange(
req: Request,
res: Response,
changeType: 'create' | 'update' | 'delete',
responseData: any
) {
try {
// リクエストからDNS変更情報を抽出
const recordData = extractRecordData(req, responseData, changeType);
if (recordData) {
await changeHistoryService.recordChange(recordData);
}
} catch (error) {
// 履歴記録エラーは元の処理を阻害しないようにログのみ
console.error('Failed to record DNS change history:', error);
}
}
function extractRecordData(
req: Request,
responseData: any,
changeType: 'create' | 'update' | 'delete'
): Omit<DnsChangeRecord, 'id' | 'timestamp'> | null {
const body = req.body;
const params = req.params;
const query = req.query;
// リクエストパスに基づいて情報を抽出
const path = req.route?.path || req.path;
// 基本的な変更情報の構築
const changeData: Partial<Omit<DnsChangeRecord, 'id' | 'timestamp'>> = {
changeType,
source: determineSource(req),
userId: req.headers['user-id'] as string, // 認証実装時に設定
userEmail: req.headers['user-email'] as string,
metadata: {
ipAddress: req.ip || req.connection.remoteAddress,
userAgent: req.headers['user-agent']
}
};
// パスとボディに基づいてレコード情報を抽出
if (path.includes('/dns/records')) {
// DNS レコード操作
if (body.domain && body.type) {
changeData.recordId = params.id || body.id || generateRecordId(body.domain, body.type);
changeData.domain = body.domain;
changeData.recordType = body.type;
if (changeType === 'create' || changeType === 'update') {
changeData.newValue = body.value;
changeData.newTtl = body.ttl;
}
if (changeType === 'update' || changeType === 'delete') {
// 更新・削除の場合は前の値も記録(実際の実装では事前に取得)
changeData.previousValue = body.previousValue;
changeData.previousTtl = body.previousTtl;
}
changeData.reason = body.reason || query.reason as string;
}
} else if (path.includes('/upload')) {
// ファイルアップロード
if (req.file && responseData.data) {
// アップロード経由の一括変更として記録
changeData.source = 'import';
changeData.metadata = {
...changeData.metadata,
importFileName: req.file.originalname
};
// 実際の実装では、アップロードされたファイルの内容を解析して
// 個別のレコード変更として記録
}
}
// 必須フィールドが揃っているかチェック
if (changeData.recordId && changeData.domain && changeData.recordType && changeData.changeType && changeData.source) {
return changeData as Omit<DnsChangeRecord, 'id' | 'timestamp'>;
}
return null;
}
function determineSource(req: Request): 'manual' | 'import' | 'api' | 'monitoring' {
const userAgent = req.headers['user-agent'] || '';
const apiKey = req.headers['x-api-key'];
const isFileUpload = req.file !== undefined;
if (apiKey) return 'api';
if (isFileUpload) return 'import';
if (userAgent.includes('monitor') || userAgent.includes('bot')) return 'monitoring';
return 'manual';
}
function generateRecordId(domain: string, type: string): string {
return `${domain}_${type}_${Date.now()}`.replace(/[^a-zA-Z0-9_]/g, '_');
}
/**
* 一括変更履歴記録のためのヘルパー関数
*/
export async function recordBulkDnsChanges(
changes: Array<Omit<DnsChangeRecord, 'id' | 'timestamp'>>,
source: 'import' | 'api' | 'monitoring' = 'api'
): Promise<void> {
try {
const changesWithSource = changes.map(change => ({
...change,
source
}));
await changeHistoryService.bulkRecordChanges(changesWithSource);
} catch (error) {
console.error('Failed to record bulk DNS changes:', error);
}
}