DC-029: skip authLimiter for already-authenticated requests
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

The DC-027 rate limiter on /api/v1/auth/* shipped with skip: () => isTest,
which counted every request — including those from a logged-in TOTP session.
Caddy's forward_auth fires /auth/gate/* on every page-load asset (HTML, JS,
CSS, XHR), so a normal browser session exhausted the 20-req/15-min budget
within ~3 page loads and started getting 429 'Too many auth requests' even
with a valid session cookie.

Fix: extend skip to also return true when req.auth.type is 'session',
'jwt', or 'apikey' (set by jwtApiKeyAuthMiddleware, which runs upstream
of the limiter). The unauthenticated path is still rate-limited — DC-027's
credential-scraping defense is preserved.

Also closes the uncommitted working-tree changes for:
- DC-026: routes/auth/sso-gate.js — pre-auth check in buildLoginPage,
  redirected error fallbacks to status.sami?auth=required&return=...
- DC-022: dashcaddy-api/VERSION bumped to fef7e07
- status/index.html + status/js/tailscale-devices.js — Tailscale device card

4 new regression tests pin the fix:
- skips when req.auth.type === 'session'
- skips when req.auth.type === 'jwt'
- skips when req.auth.type === 'apikey'
- still counts UNAUTHENTICATED requests (defense preserved)

Live verified: 50/50 authenticated /auth/gate/plex calls passed (was
20/30 before fix). plex.sami/dashcaddy-login returns 200 with no redirect
loop. Plex auto-login token round-trips end-to-end.
This commit is contained in:
Krystie
2026-07-02 18:27:03 -07:00
parent 57de3cb8e3
commit a92eeceae5
6 changed files with 532 additions and 12 deletions
+41
View File
@@ -276,6 +276,29 @@
</div>
</div>
<div class="card" data-app="tailscale" data-status="off" id="tailscale-card"
title="Devices on your Tailscale network. Online = connected right now. Offline = known to the network but not currently connected.">
<span id="tailscale-dot" class="dot bad at-bl"></span>
<div class="row">
<div class="logo-wrap">
<svg viewBox="0 0 24 24" class="service-icon" aria-hidden="true">
<path d="M12 2L4 7v6c0 5 3.5 8.5 8 9 4.5-.5 8-4 8-9V7l-8-5z" fill="none" stroke="#7D8FE3" stroke-width="2" stroke-linejoin="round"/>
<circle cx="12" cy="10" r="2.5" fill="#7D8FE3"/>
<path d="M12 13v4M9 19h6" stroke="#7D8FE3" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<span class="name">Tailscale</span>
<span class="spacer"></span>
<span id="tailscale-pill" class="badge off"></span>
</div>
<div class="response-row">
<span id="tailscale-summary" class="response-time" style="font-size: 0.7rem;">Loading…</span>
</div>
<div class="btn-row">
<button id="tailscale-refresh-btn" title="Refresh device list"></button>
</div>
</div>
<div class="card" data-app="ca" data-status="off">
<span id="dot-ca-grid" class="dot bad at-bl"></span>
<div class="row">
@@ -302,6 +325,21 @@
</div>
</div>
<!-- Tailscale Device Panel (collapsible, populated by tailscale-devices.js) -->
<div id="tailscale-panel" style="display: none; margin-bottom: 16px; padding: 16px; background: var(--card-base); border: 1px solid var(--border); border-radius: var(--radius);">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.1rem; font-weight: 600;">Devices on Tailscale</span>
<span id="tailscale-panel-count" style="color: var(--muted); font-size: 0.85rem;"></span>
</div>
<div style="display: flex; gap: 8px;">
<span id="tailscale-tailnet" style="color: var(--muted); font-size: 0.8rem; align-self: center;"></span>
<button id="tailscale-panel-close" class="btn-sm" title="Close panel" style="padding: 4px 10px;"></button>
</div>
</div>
<div id="tailscale-device-list" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px;"></div>
</div>
<!-- Service Filter Bar -->
<div id="service-filter-bar" style="display: flex; gap: 12px; align-items: center; margin-bottom: 16px; padding: 12px 16px; background: var(--card-base); border: 1px solid var(--border); border-radius: var(--radius); flex-wrap: wrap;">
<input type="text" id="service-filter-search" placeholder="🔍 Filter services..." style="flex: 1; min-width: 180px; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9rem;" />
@@ -906,6 +944,9 @@
<script src="/js/xterm.min.js" defer></script>
<script src="/js/xterm-fit.min.js" defer></script>
<!-- Tailscale device list panel (self-contained, polls /api/v1/tailscale/devices) -->
<script src="/js/tailscale-devices.js" defer></script>
<!-- Bundled JS (built with: npm run build) -->
<script src="/dist/core.js" defer></script>
<script src="/dist/features.js" defer></script>