Files
xiaoxia-saas/packages/application/auth/password_handler.py
T

104 lines
3.4 KiB
Python

"""Password hashing implementation (application layer)."""
import bcrypt
class PasswordHasher:
"""Password hashing implementation using bcrypt."""
def __init__(self, rounds: int = 12):
"""
Initialize password hasher.
Args:
rounds: bcrypt cost factor (default: 12)
"""
if rounds < 4 or rounds > 31:
raise ValueError("rounds must be between 4 and 31")
self.rounds = rounds
def hash_password(self, password: str) -> str:
"""Hash a password using bcrypt."""
if not password:
raise ValueError("Password cannot be empty")
password_bytes = password.encode("utf-8")
salt = bcrypt.gensalt(rounds=self.rounds)
hashed = bcrypt.hashpw(password_bytes, salt)
return hashed.decode("utf-8")
def verify_password(self, password: str, hashed_password: str) -> bool:
"""Verify a password against a hash."""
if not password or not hashed_password:
return False
try:
password_bytes = password.encode("utf-8")
hashed_bytes = hashed_password.encode("utf-8")
return bcrypt.checkpw(password_bytes, hashed_bytes)
except Exception:
return False
def needs_rehash(self, hashed_password: str) -> bool:
"""Check if a password hash needs to be rehashed with a different cost factor."""
try:
hashed_bytes = hashed_password.encode("utf-8")
current_rounds = bcrypt.getsalt(hashed_bytes)
return current_rounds != self.rounds
except Exception:
return False
class PasswordValidator:
"""Password validation rules."""
def __init__(
self,
min_length: int = 8,
require_uppercase: bool = True,
require_lowercase: bool = True,
require_digit: bool = True,
require_special: bool = False,
):
self.min_length = min_length
self.require_uppercase = require_uppercase
self.require_lowercase = require_lowercase
self.require_digit = require_digit
self.require_special = require_special
def validate(self, password: str) -> tuple[bool, Optional[str]]:
"""Validate password against rules."""
if not password:
return False, "Password cannot be empty"
if len(password) < self.min_length:
return False, f"Password must be at least {self.min_length} characters"
if self.require_uppercase and not any(c.isupper() for c in password):
return False, "Password must contain at least one uppercase letter"
if self.require_lowercase and not any(c.islower() for c in password):
return False, "Password must contain at least one lowercase letter"
if self.require_digit and not any(c.isdigit() for c in password):
return False, "Password must contain at least one digit"
if self.require_special:
special_chars = "!@#$%^&*()_+-=[]{}|;:',.<>/?"
if not any(c in special_chars for c in password):
return False, "Password must contain at least one special character"
return True, None
# Default instances
password_hasher = PasswordHasher(rounds=12)
password_validator = PasswordValidator(
min_length=8,
require_uppercase=True,
require_lowercase=True,
require_digit=True,
require_special=False,
)