2a3c4a83fad43e374fd6214b3c6d29811d6fba88
server/app.py: login() now calls ldap_auth.verify(). No password is read from the database on this path any more; the column is still there and unused until T10.3 drops it, which keeps this commit revertible on its own. THE THROTTLE IS THE POINT OF THIS COMMIT, not the bind. AUTH_MAX_ATTEMPTS defaulted to 5, which is exactly this estate's AD lockout threshold. Failures are now domain binds, and the API runs 2 gunicorn workers, so that default would have let up to 10 binds reach a DC - locking the account out of Windows twice over before the app's own lockout engaged. Anyone could have done it to anyone, unauthenticated, from the login form. Now 2, so the worst case is 4 against a threshold of 5, with the arithmetic written down at the constant so the next person to raise it sees the constraint. The persistent counter on `users` cannot cover this alone: under D13 accounts are created on first login, so a real domain account can be attacked before it has a local row. Added a username-keyed pre-account window for exactly that gap - keyed by username rather than client IP, because it is the domain account being protected, not this endpoint's capacity, and IP rotation would walk straight past an IP-keyed limit. Both throttles are checked BEFORE the directory is touched. 503 is now distinct from 401. LDAP unconfigured, unreachable, untrusted, or a required group that does not resolve are OUR faults and return 503, because D13 left no password fallback: reporting "invalid username or password" would send people hunting for a password they no longer have while the real problem is a broken deploy. A rejected credential and a missing group membership both stay a single generic 401 - the useful detail goes to the log, never the response, so account existence is not disclosed. Also added a logger to app.py, which had none. An earlier draft of this commit called log.warning() in a module where `log` was undefined; compileall does not catch a NameError, so it would have surfaced as a 500 on the first failed sign-in. Named wpsuite.api to match wpsuite.auth / .ldap / .notify. Verified: app imports, the route is registered, LOGIN_MAX_ATTEMPTS is 2. An authenticated user with no local row is refused with 403 until T10.4 adds provisioning - deliberately explicit rather than silently admitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Description
No description provided
Languages
Python
49.3%
JavaScript
32.6%
CSS
8.9%
HTML
8.7%
Shell
0.4%