chore: lift bcrypt<5 cap, enforce 72-byte password limit at API layer (#854)
Final cleanup of the auth-deps modernization sequence (#842 → #853). Lifts the bcrypt<5 cap that #851/#853 left in place. The cap was a defensive measure against bcrypt 5's `ValueError on >72 bytes` behaviour change vs. bcrypt 4's silent truncation. Removing the cap requires three coordinated changes so users with unusually long passwords don't suddenly hit a bcrypt-level 500: 1. Schema-layer enforcement (the right place to fail). - UserInput.password max_length 256 → 72 - PasswordReset.new_password max_length 256 → 72 - PasswordResetToken.validate_password 256 → 72 - Password.length le=128 → le=72 - Password.generate rejects length>72 explicitly Long passwords are now rejected at request validation with a 422 ValidationError ("Value is longer than maximum length.") instead of crashing in bcrypt with an opaque 500. 2. Defensive truncation safety net (matches bcrypt 4 silent-truncate behaviour, so passwords hashed under bcrypt 4 still verify). - AuthHandler.get_password_hash and verify_password now slice `password.encode("utf-8")[:72]` before passing to bcrypt. - Prevents bcrypt 5 ValueError on any path that bypasses schema validation (internal callers, future code, edge-case Unicode where char count <72 but byte count >72). - Existing user passwords whose original input was >72 bytes were truncated when first hashed under bcrypt 4; the new code applies the same truncation at verify time, so the same hash matches. 3. Pre-existing bug surfaced by the test scenario, fixed here. `validation_exception_handler` did `ErrorType(error["type"])`, raising ValueError for any pydantic 2 error code the legacy ErrorType enum didn't list. This had been broken since #849 (pydantic 1→2 migration) but no test path was hitting long-string validation. With the new 72-byte cap that path fires. - Handler: try/except, fall back to ErrorType.GENERAL on unknown codes so unknown error types still produce a 422 with a sane message instead of a 400 + "X is not a valid ErrorType". - ErrorType: added the v2 codes that map to existing concepts — string_too_short, string_too_long, string_pattern_mismatch, int_parsing, greater_than{,_equal}, less_than{,_equal}, datetime_parsing, date_parsing, enum, missing. 4. requirements.in: bcrypt<5 → bcrypt; pip-compile resolves to 5.0.0. Verified locally: - bcrypt 5.0.0 installed in the rebuilt image - admin login: HTTP 200, bearer token issued - registering a 104-char password: HTTP 422 with structured response {"error_type":"string_too_long", "message":"Value is longer than maximum length."} - registering a normal 10-char password: HTTP 201 - 2FA setup: HTTP 200 with QR data URI + 8 backup codes - existing-hash verification: implicit in admin login (admin pwd is generated and hashed under the same bcrypt 5 the verify path uses) Roadmap state: with this PR the auth-deps modernization sequence is fully done. The auth/ stack has no unmaintained deps, no log-noise warnings, clear errors for malformed env vars, schema-level rejection of overlong passwords, and bcrypt 5 with proper 72-byte handling. Co-authored-by: taylor_socfortress <taylor.walton@socfortress.co> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>