feat(admin): maintenance-mode UI toggle + 503 gate (LC-92) #152

Merged
nrupard merged 2 commits from feat/lc-92-maintenance-mode into main 2026-05-19 18:45:22 +02:00
Owner

Summary

Implements LC-92: a standalone admin toggle for maintenance_mode plus the enforcement plumbing.

  • POST /admin/maintenance (standalone-only) flips the flag and the operator-facing message together; the section renders at the top of /admin/settings with a clear "ON" banner when active.
  • New enforce_maintenance_mode middleware sits between inject_user and the routes. Admins always pass; /login, /logout, /version, /assets/*, /avatars/* are exempt before the setting is even read; everyone else gets a 503 rendering templates/maintenance.html with the operator's message.
  • WebSocket upgrades hit the same HTTP middleware first, so non-admin upgrades during maintenance are rejected with 503 before reaching ws_handler. Existing live sessions are allowed to drain.
  • Each toggle writes a mod_actions audit row (maintenance_on / maintenance_off, actor = admin, metadata = message).
  • SaaS maintenance_webhook is untouched; the two paths share settings.maintenance_mode.

Test plan

  • just check (server + desktop, both feature builds, clippy -D warnings, fmt --check)
  • just test - 50 binaries green, including the new routes_maintenance (6 cases: non-admin POST 403, admin enable+disable writes settings and audit log, non-admin GET 503 with embedded message, admin bypass, /login reachable during maintenance, non-admin WS upgrade 503)
  • just test-saas - green, the new file is #![cfg(feature = "standalone")] at file scope so it is skipped under saas
  • Manual smoke: log in as admin, toggle on, open second browser as non-admin, confirm 503 + message, toggle off from admin tab, confirm non-admin recovers
## Summary Implements LC-92: a standalone admin toggle for `maintenance_mode` plus the enforcement plumbing. - `POST /admin/maintenance` (standalone-only) flips the flag and the operator-facing message together; the section renders at the top of `/admin/settings` with a clear "ON" banner when active. - New `enforce_maintenance_mode` middleware sits between `inject_user` and the routes. Admins always pass; `/login`, `/logout`, `/version`, `/assets/*`, `/avatars/*` are exempt before the setting is even read; everyone else gets a 503 rendering `templates/maintenance.html` with the operator's message. - WebSocket upgrades hit the same HTTP middleware first, so non-admin upgrades during maintenance are rejected with 503 before reaching `ws_handler`. Existing live sessions are allowed to drain. - Each toggle writes a `mod_actions` audit row (`maintenance_on` / `maintenance_off`, actor = admin, metadata = message). - SaaS `maintenance_webhook` is untouched; the two paths share `settings.maintenance_mode`. ## Test plan - [x] `just check` (server + desktop, both feature builds, clippy -D warnings, fmt --check) - [x] `just test` - 50 binaries green, including the new `routes_maintenance` (6 cases: non-admin POST 403, admin enable+disable writes settings and audit log, non-admin GET 503 with embedded message, admin bypass, `/login` reachable during maintenance, non-admin WS upgrade 503) - [x] `just test-saas` - green, the new file is `#![cfg(feature = "standalone")]` at file scope so it is skipped under saas - [ ] Manual smoke: log in as admin, toggle on, open second browser as non-admin, confirm 503 + message, toggle off from admin tab, confirm non-admin recovers
feat(admin): maintenance-mode UI toggle + 503 gate (LC-92)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 1m9s
2ba923c070
Standalone admin form at /admin/maintenance flips a global maintenance_mode flag plus a customizable operator message. A new enforce_maintenance_mode tower middleware gates every non-exempt request: admins always pass so they can flip it back off, /login, /logout, /version, /assets and /avatars stay reachable, and everyone else gets a 503 with the maintenance page. WebSocket upgrades go through the same middleware, so non-admin upgrade attempts during maintenance get the same 503 before reaching the upgrade handler. Each toggle is audited via mod_actions (action=maintenance_on/_off, actor=admin, metadata=message).

The SaaS webhook surface in routes/saas_auth.rs is untouched; the two paths converge on the same settings.maintenance_mode key.
fix(admin): exempt password-reset surface from maintenance gate
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 3m49s
f63e9298fc
A locked-out admin could not reach /forgot or /reset/{token} during maintenance because the middleware 503'd them. The 2FA enrollment middleware already enumerates these as recovery paths; mirror that subset here so an admin can recover without an out-of-band DB write. Registration and email verification stay 503'd on purpose.
nrupard deleted branch feat/lc-92-maintenance-mode 2026-05-19 18:45:22 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!152
No description provided.