Skip to content

feat(server): admin UI to view and reset login rate-limit locks#75

Merged
dvcdsys merged 2 commits into
developfrom
feat/admin-login-lock-reset
Jun 5, 2026
Merged

feat(server): admin UI to view and reset login rate-limit locks#75
dvcdsys merged 2 commits into
developfrom
feat/admin-login-lock-reset

Conversation

@dvcdsys
Copy link
Copy Markdown
Owner

@dvcdsys dvcdsys commented Jun 5, 2026

Summary

The login limiter (loginlimiter.go) blocks brute-force attempts per IP and per (IP, email), but until now the only way to lift a lock early was a server restart (which wipes all in-memory state). A legitimate user who fat-fingered their password from a shared office IP was stuck for the full 15-minute window with no admin recourse.

This adds observability + targeted clearing to the limiter, plus an admin surface to act on it.

What's in here

Backend

  • loginLimiter.locks() — snapshot of every counter currently at/over its limit (prune-on-read, so the maps don't grow between logins). clearIP / clearKey lift one specific lock.
  • GET /api/v1/admin/login-locks — list active locks (Admin).
  • POST /api/v1/admin/login-locks/reset — clear the exact row the admin picked (type=ip | ip_email); Admin-only, idempotent 204.
  • Spec-first: doc/openapi.yaml updated → openapi.gen.go regenerated (the large gen diff is the re-encoded embedded spec blob, not logic).

Dashboard

  • New Login security admin module (src/modules/login-locks/): table of active locks (type / IP / email / attempts / when it frees) with a per-row Reset button, 15s polling, empty state. Registered admin-only (requiredRole: 'admin').

Design notes

  • Limiter state is in-memory, per-process, wiped on restart and self-heals as the sliding windows expire (5/15min per pair, 60/min per IP). This is an operational convenience, not a persisted store.
  • Reset deliberately acts on the exact row surfaced in the list (it never scans "all IPs for an email"), so an admin can't clear more than they can see.

Auth

Both endpoints are Admin (mustBeAdmin); docs/AUTH_REVIEW.md matrix updated locally (that file is gitignored).

Tests

loginlocks_admin_test.go:

  • non-admin → 403 on both endpoints
  • full cycle: 5 failed logins → 429 → list shows one ip_email lock → admin resets → login no longer throttled
  • reset validation (missing ip, bad type, ip_email without email, idempotent no-op)

All server tests green; dashboard typecheck + build pass.

🤖 Generated with Claude Code

dvcdsys and others added 2 commits June 5, 2026 15:41
The login limiter (loginlimiter.go) blocks brute-force attempts per IP and
per (IP, email), but the only way to lift a lock early was a server restart
(which wipes all in-memory state). A legitimate user who fat-fingered their
password from a shared office IP was stuck for the full 15-minute window with
no admin recourse.

Add observability + targeted clear to the limiter and an admin surface:

- loginLimiter.locks() snapshots every counter currently at/over its limit
  (prune-on-read so the maps don't grow); clearIP/clearKey lift one lock.
- GET /api/v1/admin/login-locks lists active locks; POST .../reset clears the
  exact row the admin picked (type=ip | ip_email). Both Admin-only, idempotent
  reset returns 204.
- Dashboard "Login security" admin module: table of active locks with a
  per-row Reset button, 15s polling, empty state.

Spec-first: doc/openapi.yaml + regenerated openapi.gen.go. Gating tests cover
non-admin 403, the full lock→list→reset→unthrottled cycle, and validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review feedback on the login-lock admin feature:

- ResetLoginLock now emits an explicit structured audit line (actor id/email,
  lock type/ip/email) on every clear. Lifting a lock weakens the brute-force
  defence, so it deserves its own audit record, not just the generic request
  log. Nil-guarded; actor fields blank under AuthDisabled.
- clearKey delegates to reset instead of duplicating the delete, so the key
  derivation lives in one place and the two paths can't drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dvcdsys dvcdsys merged commit d8ed012 into develop Jun 5, 2026
1 check passed
@dvcdsys dvcdsys deleted the feat/admin-login-lock-reset branch June 5, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant