"""Security primitives: Argon2 password hashing + signed tokens for email/reset.""" from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError, InvalidHashError from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired from flask import current_app _ph = PasswordHasher() def hash_password(raw: str) -> str: return _ph.hash(raw) def verify_password(stored_hash: str, raw: str) -> bool: try: return _ph.verify(stored_hash, raw) except (VerifyMismatchError, InvalidHashError, Exception): return False def needs_rehash(stored_hash: str) -> bool: try: return _ph.check_needs_rehash(stored_hash) except Exception: return False # --- Signed tokens (email verify, password reset) --- def _serializer(salt: str) -> URLSafeTimedSerializer: return URLSafeTimedSerializer(current_app.config["SECRET_KEY"], salt=salt) def generate_token(data, salt: str) -> str: return _serializer(salt).dumps(data) def read_token(token: str, salt: str, max_age: int): """Return payload or None if invalid/expired.""" try: return _serializer(salt).loads(token, max_age=max_age) except (BadSignature, SignatureExpired): return None