From 3cf59800834f3767ca79a609c0ed1e5859e62067 Mon Sep 17 00:00:00 2001 From: Krystie Date: Tue, 14 Jul 2026 04:22:32 -0700 Subject: [PATCH] =?UTF-8?q?fix(middleware):=20split=20/auth/gate=20from=20?= =?UTF-8?q?authLimiter=20=E2=80=94=2020/15min=20was=20burning=20budget=20o?= =?UTF-8?q?n=20per-asset=20forward=5Fauth=20chatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptoms: - Open 4-5 service tabs (Plex, Torrent, Radarr, etc.) + dashboard polling - Each page-load fires Caddy forward_auth on every asset (HTML, JS, CSS, XHR) - /api/v1/auth/gate/ counted each call against the 20/15min STRICT budget - Within a minute or two of normal browsing, every gated service flips to 'down' with statusCode 429, because Caddy bounces the 429 to a 'auth required' redirect to status.sami Fix: - Split /auth/gate into its own limiter: 600/15min (40/min average) — comfortably accommodates ~6 service tabs each polling every 15s - Keep /auth/keys, /auth/jwt, /auth/app-token on the original 20/15min STRICT (those actually mint credentials — gate just hands Caddy pre-existing auth) - Same skip clause preserved: req.auth.type in {session, jwt, apikey} bypasses the limit, so a properly-logged-in user never hits either limit This is the same class of bug as the DC-044 / P21 health-check probe false negative (probe chatter exhausting the auth budget). Adding to BACKLOG. --- dashcaddy-api/src/utilities/middleware.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/dashcaddy-api/src/utilities/middleware.js b/dashcaddy-api/src/utilities/middleware.js index 9620198..f871dcd 100644 --- a/dashcaddy-api/src/utilities/middleware.js +++ b/dashcaddy-api/src/utilities/middleware.js @@ -507,9 +507,26 @@ module.exports = function configureMiddleware(app, { }); app.use('/api/v1/auth/keys', authLimiter); app.use('/api/v1/auth/jwt', authLimiter); - app.use('/api/v1/auth/gate', authLimiter); app.use('/api/v1/auth/app-token', authLimiter); + // Separate, much higher limit for /auth/gate/* — Caddy's forward_auth + // fires this on EVERY page-load asset (HTML, JS, CSS, XHR, image refs) + // for every gated service. With multiple service tabs open + dashboard + // health probes, 20/15min burns in under a minute. Real brute-force + // risk is on /auth/keys + /auth/jwt + /auth/app-token (above); gate + // doesn't mint or return secrets directly (Caddy uses the response + // headers to inject Basic Auth / X-Api-Key into the upstream call, + // which still requires a valid auth cookie upstream). + const authGateLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 600, // 40/min average — accommodates ~6 service tabs each polling every 15s + standardHeaders: true, + legacyHeaders: false, + skip: (req) => isTest || req.auth?.type === 'session' || req.auth?.type === 'jwt' || req.auth?.type === 'apikey', + message: { success: false, error: 'Too many auth requests, please try again later' } + }); + app.use('/api/v1/auth/gate', authGateLimiter); + // ── Audit logging middleware (logs non-GET API requests) ── app.use(auditLogger.middleware());