mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(admin): rate-limit /api/admin/backups/run to 3/min per admin
pg-boss does not deduplicate by payload, so each click on the manual "Backup now" button enqueues a fresh data-backup job. Without a rate-limit, an impatient admin (or compromised admin session via XSS or token theft) can stack dozens of identical fan-out-per-user jobs on the queue. Three runs per minute per admin is plenty for ad-hoc snapshots — the weekly cron in `reminder-worker` already carries the regular cadence. Closes H3 from .planning/phase-6-code-review-findings.md (also addresses M-1 from .planning/phase-6-security-findings.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b3f282adab
commit
8e17f227e4
1 file changed
+15
-1
@@ -12,15 +12,29 @@ import { apiHandler, requireAdmin, HttpError } from "@/lib/api-handler";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const DATA_BACKUP_QUEUE = "data-backup";
|
||||
|
||||
export const POST = apiHandler(async () => {
|
||||
await requireAdmin();
|
||||
const { user: admin } = await requireAdmin();
|
||||
annotate({ action: { name: "admin.backups.run" } });
|
||||
|
||||
// pg-boss does not dedupe by payload; without a rate-limit, an
|
||||
// impatient admin (or compromised admin session) can stack dozens of
|
||||
// identical jobs on the queue. Three runs/min per admin is plenty
|
||||
// for ad-hoc snapshots — the weekly cron carries the regular cadence.
|
||||
const rl = await checkRateLimit(
|
||||
`admin-backups-run:${admin.id}`,
|
||||
3,
|
||||
60 * 1000,
|
||||
);
|
||||
if (!rl.allowed) {
|
||||
throw new HttpError(429, "Too many backup runs");
|
||||
}
|
||||
|
||||
const boss = getGlobalBoss();
|
||||
if (!boss) {
|
||||
// 503 instead of 500 — the request is well-formed; the worker just
|
||||
|
||||
Reference in new issue
Block a user