"""Authentication service — credential validation and password management.""" from __future__ import annotations from sqlalchemy.orm import Session from app.auth import hash_password, verify_password from app.domain.errors import BusinessRuleViolation, PermissionViolation from app.models.user import User _DUMMY_HASH = "$2b$12$LJ3m4ys3Lg3dMO/NpNmOaeVwFpWJMxlB2FLmEAo9fZr.S8H1vC4Wy" def authenticate_user(db: Session, *, username: str, password: str) -> User: """Validate credentials and return the User. Raises BusinessRuleViolation for invalid credentials. Raises PermissionViolation for disabled account. Uses constant-time comparison to prevent timing attacks. """ user = db.query(User).filter(User.username == username).first() hashed = user.hashed_password if user else _DUMMY_HASH password_valid = verify_password(password, hashed) if user is None or not password_valid: raise BusinessRuleViolation("Incorrect username or password") if not user.is_active: raise PermissionViolation("Account is disabled. Contact an administrator.") return user def change_password( db: Session, user: User, *, current_password: str, new_password: str, ) -> None: """Change a user's password. Does NOT commit. Raises BusinessRuleViolation if current password is wrong. """ if not verify_password(current_password, user.hashed_password): raise BusinessRuleViolation("Current password is incorrect") if verify_password(new_password, user.hashed_password): raise BusinessRuleViolation( "New password must be different from the current password" ) user.hashed_password = hash_password(new_password) user.must_change_password = False