CSP 관찰 요약 추가
This commit is contained in:
@@ -48,3 +48,77 @@ export const recordCspReports = async (reports) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* CSP 위반 집계 행을 관리자 표시용 값으로 변환한다.
|
||||
* @param {Object} row - DB 집계 행
|
||||
* @returns {Object} 관리자 표시용 집계 행
|
||||
*/
|
||||
const normalizeCspViolationRow = (row) => {
|
||||
return {
|
||||
policyVersion: row.policy_version || '',
|
||||
documentPath: row.document_path || '/',
|
||||
effectiveDirective: row.effective_directive || 'unknown',
|
||||
blockedOrigin: row.blocked_origin || 'unknown',
|
||||
userAgentFamily: row.user_agent_family || 'Other',
|
||||
violationCount: Number(row.violation_count || 0),
|
||||
firstSeenAt: row.first_seen_at || null,
|
||||
lastSeenAt: row.last_seen_at || null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 CSP 관찰용 최근 위반 집계 요약을 조회한다.
|
||||
* @param {{ days?: number, limit?: number }} [options] - 조회 옵션
|
||||
* @returns {Promise<{ days: number, totalViolations: number, totalRows: number, recentViolations: number, latestSeenAt: string | null, topViolations: Array<Object> }>} CSP 위반 요약
|
||||
*/
|
||||
export const getCspViolationSummary = async (options = {}) => {
|
||||
const sql = getPostgresClient()
|
||||
const days = Math.min(Math.max(Number(options.days || 30), 1), 90)
|
||||
const limit = Math.min(Math.max(Number(options.limit || 8), 1), 20)
|
||||
|
||||
if (!sql) {
|
||||
return {
|
||||
days,
|
||||
totalViolations: 0,
|
||||
totalRows: 0,
|
||||
recentViolations: 0,
|
||||
latestSeenAt: null,
|
||||
topViolations: []
|
||||
}
|
||||
}
|
||||
|
||||
const [summary] = await sql`
|
||||
SELECT
|
||||
COALESCE(SUM(violation_count), 0)::bigint AS total_violations,
|
||||
COUNT(*)::bigint AS total_rows,
|
||||
COALESCE(SUM(violation_count) FILTER (WHERE last_seen_at >= now() - (${days}::int * interval '1 day')), 0)::bigint AS recent_violations,
|
||||
MAX(last_seen_at) AS latest_seen_at
|
||||
FROM csp_violation_aggregates
|
||||
`
|
||||
|
||||
const topRows = await sql`
|
||||
SELECT
|
||||
policy_version,
|
||||
document_path,
|
||||
effective_directive,
|
||||
blocked_origin,
|
||||
user_agent_family,
|
||||
violation_count,
|
||||
first_seen_at,
|
||||
last_seen_at
|
||||
FROM csp_violation_aggregates
|
||||
WHERE last_seen_at >= now() - (${days}::int * interval '1 day')
|
||||
ORDER BY violation_count DESC, last_seen_at DESC
|
||||
LIMIT ${limit}
|
||||
`
|
||||
|
||||
return {
|
||||
days,
|
||||
totalViolations: Number(summary?.total_violations || 0),
|
||||
totalRows: Number(summary?.total_rows || 0),
|
||||
recentViolations: Number(summary?.recent_violations || 0),
|
||||
latestSeenAt: summary?.latest_seen_at || null,
|
||||
topViolations: topRows.map(normalizeCspViolationRow)
|
||||
}
|
||||
}
|
||||
|
||||
17
server/routes/admin/api/security/csp-violations.get.js
Normal file
17
server/routes/admin/api/security/csp-violations.get.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { requireAdminSession } from '../../../../utils/admin-auth.js'
|
||||
import { getCspViolationSummary } from '../../../../repositories/csp-report-repository.js'
|
||||
|
||||
/**
|
||||
* 관리자 CSP 위반 관찰 요약 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Promise<Object>} CSP 위반 집계 요약
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAdminSession(event)
|
||||
|
||||
const query = getQuery(event)
|
||||
return getCspViolationSummary({
|
||||
days: Number(query.days) || 30,
|
||||
limit: Number(query.limit) || 8
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user