fix(middleware): split /auth/gate from authLimiter — 20/15min was burning budget on per-asset forward_auth chatter
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

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/<service> 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.
This commit is contained in:
Krystie
2026-07-14 04:22:32 -07:00
parent de3215f704
commit 3cf5980083
+18 -1
View File
@@ -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());