43 lines
1.2 KiB
Python
43 lines
1.2 KiB
Python
"""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
|