"""Argon2id 密码散列、校验与复杂度策略。""" from __future__ import annotations from argon2 import PasswordHasher, Type from argon2.exceptions import Argon2Error _PASSWORD_HASHER = PasswordHasher(type=Type.ID) _DUMMY_PASSWORD_HASH = _PASSWORD_HASHER.hash( "fixed-invalid-credential-placeholder" ) PASSWORD_MIN_LENGTH = 8 PASSWORD_MAX_LENGTH = 64 PASSWORD_MIN_CHAR_CLASSES = 3 def validate_password_policy(value: str) -> None: """密码复杂度策略:长度8~64,且包含大写/小写/数字/特殊字符中至少三类。 适用于建户初始密码、管理员重置密码、门户首次设密; 抛出 ValueError(消息可直接面向用户),调用方负责转译为接口错误。 """ if len(value) < PASSWORD_MIN_LENGTH or len(value) > PASSWORD_MAX_LENGTH: raise ValueError("密码长度必须在8~64个字符之间") char_classes = sum( ( any(c.islower() for c in value), any(c.isupper() for c in value), any(c.isdigit() for c in value), any(not c.isalnum() for c in value), ) ) if char_classes < PASSWORD_MIN_CHAR_CLASSES: raise ValueError( "密码必须包含大写字母、小写字母、数字、特殊字符中的至少三类" ) def hash_password(password: str) -> str: if not password: raise ValueError("密码不能为空") return _PASSWORD_HASHER.hash(password) def verify_password(password_hash: str | None, password: str) -> bool: try: return _PASSWORD_HASHER.verify( password_hash or _DUMMY_PASSWORD_HASH, password, ) except Argon2Error: return False