feat: throttle failed logins per client IP

PR C of SECURITY_PLAN.md: after 5 failed /login attempts an IP is refused
for 30 s, and each failure after that refuses it for twice as long, up
to 15 min. A success resets it, as does an hour without a failure.

- LoginThrottle.h holds the schedule and 8 IPs, with the time passed in
  so native tests cover it. While all 8 hold recent failures, every
  other IP shares one more slot, so many addresses don't each get five
  free tries.
- A refused IP gets 429 with Retry-After before its credentials are
  checked, so a right guess during a lockout reveals nothing. Home
  Assistant retries a 429 instead of asking for new credentials.
- The web UI's login panel shows a 401's or 429's message, where it
  popped a service error.
- test/e2e --lockout checks it on a Secured device and waits it out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-26 22:08:30 +01:00
parent 1612fd1587
commit bab83313ee
8 changed files with 326 additions and 1 deletions

View file

@ -19,6 +19,7 @@ def pytest_addoption(parser):
parser.addoption("--shade", type=int, default=None, help="with --radio: the id of the shade the commands may move")
parser.addoption("--edit", action="store_true", help="also run the edits that change the shade model, then undo them")
parser.addoption("--reboot", action="store_true", help="also run the test that reboots the device")
parser.addoption("--lockout", action="store_true", help="also run the test that locks this PC out of /login for 30 s")
@pytest.fixture(scope="session")