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:
+14
-1
@@ -15,7 +15,11 @@ engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
pwd_context = CryptContext(schemes=["sha256_crypt"], deprecated="auto")
|
||||
# bcrypt is the primary scheme (cross-version stable, OpenSSL-native).
|
||||
# sha256_crypt is kept as deprecated fallback so legacy hashes still
|
||||
# verify; on first successful login the password is re-hashed to bcrypt
|
||||
# via verify_and_update().
|
||||
pwd_context = CryptContext(schemes=["bcrypt", "sha256_crypt"], deprecated=["sha256_crypt"])
|
||||
|
||||
# JWT Settings
|
||||
SECRET_KEY = secret.JWT_SECRET if hasattr(secret, 'JWT_SECRET') else "aars-dev-secret-change-in-production"
|
||||
@@ -34,6 +38,15 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def verify_and_update_password(plain_password: str, hashed_password: str) -> tuple[bool, str | None]:
|
||||
"""Verify password and return new hash if rehash is needed.
|
||||
|
||||
Returns (ok, new_hash_or_none). Caller is responsible for committing
|
||||
the new hash to the DB when new_hash is not None.
|
||||
"""
|
||||
return pwd_context.verify_and_update(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user