fix(netflow): подтверждать выбранный срез перед запросами
This commit is contained in:
1 parent
64df472202
commit
0c14da190b
13 files changed
+338
-21
No files matched your search
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user