feat(auth): switch to bcrypt primary scheme, keep sha256_crypt for migration
- database.py: CryptContext = ['bcrypt', 'sha256_crypt'] with sha256_crypt deprecated - database.py: add verify_and_update_password() returning (ok, new_hash) - main.py: login uses verify_and_update; on successful verify transparently commits new bcrypt hash when old hash is sha256_crypt - requirements.txt: passlib[bcrypt]==1.7.4 + bcrypt==4.0.1 (compat with passlib 1.7.4) Migration: zero-touch. Admin's first login after this deploy will auto-upgrade the stored hash from $5$... to $2b$... Avoids the passlib sha256_crypt cross-version verify bug that bit us this morning. bcrypt is OpenSSL-native and stable across passlib versions.
This commit is contained in:
+13
-2
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
from typing import Optional, List
|
||||
import json
|
||||
|
||||
from database import get_db, create_access_token, verify_password, get_password_hash, init_db, Base, engine
|
||||
from database import get_db, create_access_token, verify_password, verify_and_update_password, get_password_hash, init_db, Base, engine
|
||||
from models import User, AttendanceRecord, Shift, Accident, Holiday, LeaveRecord
|
||||
from sqlalchemy import func as sqlfunc
|
||||
from schemas import *
|
||||
@@ -254,9 +254,20 @@ async def startup():
|
||||
@app.post("/api/auth/login", response_model=TokenResponse)
|
||||
async def login(form_data: LoginRequest, db: Session = Depends(get_db)):
|
||||
user = db.query(User).filter(User.email == form_data.email).first()
|
||||
if not user or not verify_password(form_data.password, user.password_hash):
|
||||
if not user:
|
||||
logger.warning("login failed", extra={"context": {"email": form_data.email, "reason": "user_not_found"}})
|
||||
raise HTTPException(status_code=401, detail="Invalid email or password")
|
||||
# verify_and_update returns (ok, new_hash_if_needs_rehash)
|
||||
ok, new_hash = verify_and_update_password(form_data.password, user.password_hash)
|
||||
if not ok:
|
||||
logger.warning("login failed", extra={"context": {"email": form_data.email, "reason": "invalid_credentials"}})
|
||||
raise HTTPException(status_code=401, detail="Invalid email or password")
|
||||
# Migrate hash scheme in place (e.g. sha256_crypt -> bcrypt) without forcing logout
|
||||
if new_hash:
|
||||
user.password_hash = new_hash
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
logger.info("password hash upgraded", extra={"context": {"user_id": user.id, "email": form_data.email}})
|
||||
set_user_id(user.id)
|
||||
access_token = create_access_token({"sub": str(user.id)})
|
||||
logger.info("login success", extra={"context": {"user_id": user.id, "email": form_data.email}})
|
||||
|
||||
Reference in New Issue
Block a user