fix: pin bcrypt<5 — passlib 1.7.4 incompatibility breaks login (#851)
#848 bumped bcrypt 4.0.1 → 5.0.0 as part of Tier 2. That broke login for all users because passlib 1.7.4 (last released August 2020) cannot detect the bcrypt 5 backend version — bcrypt removed the `bcrypt.__about__.__version__` attribute passlib was reading. With an unknown backend, passlib's bcrypt handler skips the pre-hashing / truncation it normally applies and sends raw bytes to bcrypt, which in 5.x strictly errors instead of silently truncating. The visible symptoms (production deployment, post-Tier-2 image): (trapped) error reading bcrypt version AttributeError: module 'bcrypt' has no attribute '__about__' Error: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72]) POST /api/auth/token HTTP/1.0 401 Unauthorized passlib is unmaintained (no release since 2020). The proper long-term fix is to replace passlib with direct bcrypt usage (we already use bcrypt.hashpw/checkpw directly in app/auth/models/users.py — the rest of the auth path goes through CryptContext in 4 places: utils.py, routes/auth.py, services/sso.py, services/totp.py). That migration is deferred to a follow-up PR; this PR is a hotfix to unblock the production deployment. Pins bcrypt<5; pip-compile resolves to 4.3.0 (latest 4.x). Verified locally: - fresh-deploy backend boot is clean (no `trapped error reading bcrypt version`, no `72 bytes` error) - POST /api/auth/token with the freshly-generated admin credentials returns HTTP 200 with a valid bearer token - all 50 routers still import; data preserved across rebuild Co-authored-by: taylor_socfortress <taylor.walton@socfortress.co> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>