fix(alerts): защитить ручной повтор очереди проверкой версии

This commit is contained in:
Denozordec committed 2026-10-03 19:14:27 +07:00
1 parent 89c31289bd
commit 7d0608bbce
16 files changed
+86 -17

No files matched your search

+4 -4
View File
@@ -7,12 +7,12 @@ process.env.AUTH_REQUIRED="true";process.env.AUTH_JWT_SECRET="evolution-fixture-
const {default:authPlugin,requireAuth}=await import("./auth.js")
const {createAlertsRoutes,defaultAlertsDependencies}=await import("../routes/alerts.js")
const app=Fastify();await app.register(authPlugin);app.addHook("preHandler",requireAuth)
let writes=0,sends=0,reads=0
await app.register(createAlertsRoutes({...defaultAlertsDependencies,outbox:async()=>({counts:{total:0,pending:0,processing:0,unknown:0,sent:0,failed:0,unrecognized:0},items:[],itemsLimit:20}),overview:async()=>({revision,telegram:{chatId:"123",tokenConfigured:true,connected:true,messageThreadId:null},groups:[],rules:[],history:[],meta:{servers:[],greClients:[],probeTargets:[],rttLossTargets:[],trafficServers:[],serversDetail:[]}}),writeTelegram:async()=>{writes++;return {chatId:"123",tokenConfigured:true,messageThreadId:null}},readTelegram:async()=>{reads++;return {botToken:"fixture-only",chatId:"123",messageThreadId:null}},send:async()=>{sends++;return {ok:true}},replace:async()=>{writes++;return {ok:true as const,revision,groups:[],rules:[]}}}),{prefix:"/api"})
let writes=0,sends=0,reads=0,retries=0
await app.register(createAlertsRoutes({...defaultAlertsDependencies,retryOutbox:async(id)=>{retries++;return {ok:true as const,id,status:"pending" as const,resolutionVersion:1}},outbox:async()=>({counts:{total:0,pending:0,processing:0,unknown:0,sent:0,failed:0,unrecognized:0},items:[],itemsLimit:20}),overview:async()=>({revision,telegram:{chatId:"123",tokenConfigured:true,connected:true,messageThreadId:null},groups:[],rules:[],history:[],meta:{servers:[],greClients:[],probeTargets:[],rttLossTargets:[],trafficServers:[],serversDetail:[]}}),writeTelegram:async()=>{writes++;return {chatId:"123",tokenConfigured:true,messageThreadId:null}},readTelegram:async()=>{reads++;return {botToken:"fixture-only",chatId:"123",messageThreadId:null}},send:async()=>{sends++;return {ok:true}},replace:async()=>{writes++;return {ok:true as const,revision,groups:[],rules:[]}}}),{prefix:"/api"})
for(const permission of ["mm:alerts:read","mm:alerts:write","mm:alerts:admin","mm:settings:admin"]){
const token=await new SignJWT({sub:"fixture",apps:["mm"],permissions:[permission]}).setProtectedHeader({alg:"HS256"}).setIssuer("https://auth.test.local").setExpirationTime("1h").sign(new TextEncoder().encode("evolution-fixture-secret"))
for(const [method,url] of [["GET","/api/alerts"],["GET","/api/alerts/outbox"],["PUT","/api/alerts/telegram"],["POST","/api/alerts/telegram/test"],["PUT","/api/alerts/rules"]] as const){const permitted=permission==="mm:alerts:write"||permission==="mm:alerts:admin"||method==="GET"&&permission==="mm:alerts:read";const response=await app.inject({method,url,headers:{authorization:"Bearer "+token,"if-match":'"'+revision+'"'},...(method==="PUT"?{payload:url.endsWith("rules")?{rules:[],groups:[]}:{chatId:"123"}}:{})});assert.equal(response.statusCode,permitted?200:403);assert.equal(response.body.includes("fixture-only"),false)}
for(const [method,url] of [["GET","/api/alerts"],["GET","/api/alerts/outbox"],["PUT","/api/alerts/telegram"],["POST","/api/alerts/telegram/test"],["POST","/api/alerts/outbox/a/retry"],["PUT","/api/alerts/rules"]] as const){const permitted=permission==="mm:alerts:write"||permission==="mm:alerts:admin"||method==="GET"&&permission==="mm:alerts:read";const response=await app.inject({method,url,headers:{authorization:"Bearer "+token,"if-match":'"'+revision+'"'},...(url.endsWith("/retry")?{payload:{expectedVersion:0,acknowledgedDuplicateRisk:true}}:method==="PUT"?{payload:url.endsWith("rules")?{rules:[],groups:[]}:{chatId:"123"}}:{})});assert.equal(response.statusCode,permitted?200:403);assert.equal(response.body.includes("fixture-only"),false)}
}
assert.equal(writes,4);assert.equal(sends,2);assert.equal(reads,2)
assert.equal(retries,2);assert.equal(writes,4);assert.equal(sends,2);assert.equal(reads,2)
for(const [method,url] of [["GET","/api/alerts"],["GET","/api/alerts/outbox"],["POST","/api/alerts/telegram/test"]] as const)assert.equal((await app.inject({method,url})).statusCode,401)
assert.equal(sends,2);await app.close();console.log("Alerts JWT: real read/write/admin gates before save/send/read, no token echo")