fix(middleware): split /auth/gate from authLimiter — 20/15min was burning budget on per-asset forward_auth chatter
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:
@@ -507,9 +507,26 @@ module.exports = function configureMiddleware(app, {
|
|||||||
});
|
});
|
||||||
app.use('/api/v1/auth/keys', authLimiter);
|
app.use('/api/v1/auth/keys', authLimiter);
|
||||||
app.use('/api/v1/auth/jwt', authLimiter);
|
app.use('/api/v1/auth/jwt', authLimiter);
|
||||||
app.use('/api/v1/auth/gate', authLimiter);
|
|
||||||
app.use('/api/v1/auth/app-token', 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) ──
|
// ── Audit logging middleware (logs non-GET API requests) ──
|
||||||
app.use(auditLogger.middleware());
|
app.use(auditLogger.middleware());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user