feat(admin): super-admin-gated rate limit and IP ban management #424
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/BUNYIP-413-super-admin-rate-limit-ip-ban-management"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The rate limits screen could only reset an active throttle and the IP bans screen could only lift a ban, so neither rate limiting nor manual blocking could be configured from a running system. Both screens now support the missing half, restricted to the super admin, because a mis-set cap or a careless ban can lock the platform out for everybody.
Super admin is the "first setup account": a new
users.is_super_admincolumn, set when the BOOTSTRAP_ADMIN_EMAIL account is promoted and when SETUP_DEFAULT_ADMIN seeds the first admin, and backfilled by the migration onto the earliest-created admin so existing deployments have one. The flag is read per request by a newSuperAdminUserextractor rather than carried as a JWT claim, so revoking it takes effect immediately and existing sessions need no re-issue; only the low-traffic management endpoints pay the lookup.Rate-limit configuration is now persisted per action in
rate_limit_configs, with precedence const ->RATE_LIMIT_{ACTION}_MAX_REQUESTS/_WINDOW_SECONDSenv -> persisted row. A fresh install keeps the built-in defaults, an override applies on the next request with no restart, and deleting the override reverts to the default. Resolution happens insideRateLimitRepository::check_and_increment/check/get_retry_after, so every one of the enforcement call sites picks an override up unchanged. The four places that compared a count against an unresolved cap (the sharedcheck_rate_limitlog-once gate, the 2FA per-account lockout, the OCI failure caps, and the admin active-throttle view) now resolve the effective config first, and therate_limitsretention horizon is derived from the longest window in force instead of a hard-coded hour, which a longer configured window would have invalidated.Overrides are managed for the known enforced actions rather than free-form names: an action with no enforcement call site would be stored but inert, so
PUT/DELETE /v1/admin/rate-limit-configs/{action}reject an unknown action.IP bans gain a create path (
POST /v1/admin/ip-bans) throughAutoBanService::ban, which updates the in-memory map first (effective on the address's next request) and persists theip_bansrow synchronously (surviving a restart). The auto-ban middleware'senabledflag now governs strike recording only, not enforcement, so a manual ban still holds on a deployment with auto-banning switched off.Reading the limit configuration stays open to any admin; every mutation is super-admin-only and audited via three new audit actions.
#BUNYIP-413
The rate limits screen could only reset an active throttle and the IP bans screen could only lift a ban, so neither rate limiting nor manual blocking could be configured from a running system. Both screens now support the missing half, restricted to the super admin, because a mis-set cap or a careless ban can lock the platform out for everybody. Super admin is the "first setup account": a new `users.is_super_admin` column, set when the BOOTSTRAP_ADMIN_EMAIL account is promoted and when SETUP_DEFAULT_ADMIN seeds the first admin, and backfilled by the migration onto the earliest-created admin so existing deployments have one. The flag is read per request by a new `SuperAdminUser` extractor rather than carried as a JWT claim, so revoking it takes effect immediately and existing sessions need no re-issue; only the low-traffic management endpoints pay the lookup. Rate-limit configuration is now persisted per action in `rate_limit_configs`, with precedence const -> `RATE_LIMIT_{ACTION}_MAX_REQUESTS` / `_WINDOW_SECONDS` env -> persisted row. A fresh install keeps the built-in defaults, an override applies on the next request with no restart, and deleting the override reverts to the default. Resolution happens inside `RateLimitRepository::check_and_increment` / `check` / `get_retry_after`, so every one of the enforcement call sites picks an override up unchanged. The four places that compared a count against an unresolved cap (the shared `check_rate_limit` log-once gate, the 2FA per-account lockout, the OCI failure caps, and the admin active-throttle view) now resolve the effective config first, and the `rate_limits` retention horizon is derived from the longest window in force instead of a hard-coded hour, which a longer configured window would have invalidated. Overrides are managed for the known enforced actions rather than free-form names: an action with no enforcement call site would be stored but inert, so `PUT`/`DELETE /v1/admin/rate-limit-configs/{action}` reject an unknown action. IP bans gain a create path (`POST /v1/admin/ip-bans`) through `AutoBanService::ban`, which updates the in-memory map first (effective on the address's next request) and persists the `ip_bans` row synchronously (surviving a restart). The auto-ban middleware's `enabled` flag now governs strike recording only, not enforcement, so a manual ban still holds on a deployment with auto-banning switched off. Reading the limit configuration stays open to any admin; every mutation is super-admin-only and audited via three new audit actions. #BUNYIP-413