fix(api): enforce job ownership on cancel endpoint (#599)

The job cancel endpoint authenticated the caller but never checked that the job belonged to them, so any authenticated user could cancel another user's job by ID. Load the job's owner and allow cancellation only for the owner or a caller with files:all; return 404 for missing and non-owned jobs alike. Extract the route into a shared registerJobRoutes() so the ownership check is covered by tests.

Reported by Alpesh Bhagwatkar.
This commit is contained in:
SnapOtter
2026-07-21 15:09:00 +08:00
committed by GitHub
parent a6bce6825a
commit 577d74bdb1
4 changed files with 194 additions and 58 deletions
+3 -18
View File
@@ -67,6 +67,7 @@ import { feedbackRoutes } from "./routes/feedback.js";
import { registerFetchUrlsRoute } from "./routes/fetch-urls.js";
import { filePreviewRoutes } from "./routes/file-preview.js";
import { fileRoutes } from "./routes/files.js";
import { registerJobRoutes } from "./routes/jobs.js";
import { registerMemeTemplates } from "./routes/meme-templates.js";
import { registerPipelineRoutes } from "./routes/pipeline.js";
import { preferencesRoutes } from "./routes/preferences.js";
@@ -706,24 +707,8 @@ app.get("/api/v1/readyz", async (_request, reply) => {
return reply.code(ok ? 200 : 503).send({ ok, postgres, redis, disk: diskOk, s3: s3Ok });
});
// Cancel a job (authenticated)
app.post(
"/api/v1/jobs/:jobId/cancel",
{ config: { rateLimit: { max: 300, timeWindow: "1 minute" } } },
async (
request: import("fastify").FastifyRequest<{ Params: { jobId: string } }>,
reply: import("fastify").FastifyReply,
) => {
const { requireAuth } = await import("./plugins/auth.js");
const user = requireAuth(request, reply);
if (!user) return;
const { requestCancel } = await import("./jobs/cancel.js");
const { jobId } = request.params;
const canceled = await requestCancel(jobId);
return reply.send({ canceled });
},
);
// Cancel a job (authenticated; owner or files:all only)
registerJobRoutes(app);
// Serve SPA in production
if (process.env.NODE_ENV === "production") {