| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
- """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
|