fix(netflow): подтверждать выбранный срез перед запросами

This commit is contained in:
Denozordec committed 2026-10-11 18:16:29 +07:00
1 parent 64df472202
commit 0c14da190b
13 files changed
+338 -21

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@
"test:auth": "tsx src/lib/permissions.test.ts && tsx src/plugins/auth.smoke.test.ts && tsx src/plugins/auth.ospf-optimizer.test.ts",
"test:wireguard": "npx tsx src/services/wireguard-config.test.ts && npx tsx --test src/services/wireguard-import.test.ts src/services/wireguard-import-apply.test.ts src/services/wireguard-import-revision.test.ts && npx tsx src/routes/wireguard-import.test.ts",
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts",
"test:traffic-flow": "tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
"test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
"test:pg": "tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
"test:recursive-routes": "tsx src/routes/recursive-routes.test.ts && tsx src/services/recursive-route-catalog.test.ts && tsx src/services/recursive-apply.test.ts",
@@ -0,0 +1,29 @@
import assert from "node:assert/strict"
import Fastify from "fastify"
import { serializerCompiler, validatorCompiler } from "@fastify/type-provider-zod"
import trafficFlowRoutes, { resetFlowLiveSlotsForTests, tryAcquireFlowLiveSlot, releaseFlowLiveSlot } from "./traffic-flow.js"
import { closePool } from "../db/index.js"
const app = Fastify({ logger: false })
app.setValidatorCompiler(validatorCompiler)
app.setSerializerCompiler(serializerCompiler)
await app.register(trafficFlowRoutes, { prefix: "/api" })
resetFlowLiveSlotsForTests()
try {
for (const id of ["srv1", "1suffix", "-1", "0", "1.5", "1e3", "9007199254740992"]) {
for (const endpoint of ["analytics", "monthly", "live"]) {
const response = await app.inject({ method: "GET", url: `/api/traffic/flow/${endpoint}?serverId=${encodeURIComponent(id)}&month=2026-10` })
assert.equal(response.statusCode, 400, `${endpoint}/${id}`)
assert.match(response.body, /Некорректный идентификатор сервера/)
}
}
// Failed query validation must not consume the bounded live subscription quota.
for (let i = 0; i < 4; i++) assert.equal(tryAcquireFlowLiveSlot(), true)
assert.equal(tryAcquireFlowLiveSlot(), false)
for (let i = 0; i < 4; i++) releaseFlowLiveSlot()
console.log("traffic-flow-query.test.ts: 21 invalid scoped requests rejected; SSE slots retained")
} finally {
resetFlowLiveSlotsForTests()
await app.close()
await closePool()
}
+5 -3
View File
@@ -2,6 +2,7 @@ import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { registerDatabaseStreamAbort } from "../plugins/database-request-gate.js"
import type { FastifyReply, FastifyRequest } from "fastify"
import { env } from "../config.js"
import { parseDbServerId } from "../utils/server-id.js"
import {
trafficFlowOverlayRequestSchema,
} from "@mmapp/contracts/traffic-flow"
@@ -64,8 +65,9 @@ function rangeToMinutes(range: string | undefined): number {
function parseId(raw: unknown): number | undefined {
if (raw == null || raw === "") return undefined
const n = Number.parseInt(String(raw), 10)
return Number.isFinite(n) ? n : undefined
const n = parseDbServerId(raw)
if (n === null || !Number.isSafeInteger(n)) throw Object.assign(new Error("Некорректный идентификатор сервера"), { statusCode: 400 })
return n
}
function parseDedup(raw: unknown): boolean {
@@ -236,10 +238,10 @@ const trafficFlowRoutes: FastifyPluginAsyncZod = async (app) => {
})
app.get("/traffic/flow/live", async (req, reply) => {
const query = analyticsQuery(req)
if (!tryAcquireFlowLiveSlot()) {
return reply.status(429).send({ error: "Слишком много live-подписок" })
}
const query = analyticsQuery(req)
const liveQuery = {
serverId: query.serverId,
userId: query.userId,