feat(server): admin UI to view and reset login rate-limit locks#75
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/clearKeylift 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, idempotent204.doc/openapi.yamlupdated →openapi.gen.goregenerated (the large gen diff is the re-encoded embedded spec blob, not logic).Dashboard
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
Auth
Both endpoints are Admin (
mustBeAdmin);docs/AUTH_REVIEW.mdmatrix updated locally (that file is gitignored).Tests
loginlocks_admin_test.go:403on both endpoints429→ list shows oneip_emaillock → admin resets → login no longer throttledip_emailwithout email, idempotent no-op)All server tests green; dashboard typecheck + build pass.
🤖 Generated with Claude Code