auth: add Argon2id hashing and password policy

This commit is contained in:
Ivo Oskamp 2026-05-28 15:47:25 +02:00
parent 51b0177f7f
commit e86985743a
2 changed files with 81 additions and 0 deletions

View File

@ -0,0 +1,44 @@
"""Password hashing, password-policy validation, and session-id generation."""
from __future__ import annotations
import uuid
from argon2 import PasswordHasher
from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError
class PasswordPolicyError(ValueError):
"""Raised when a candidate password does not meet the policy."""
_hasher = PasswordHasher()
MIN_LENGTH = 12
def validate_password(pw: str) -> None:
"""Enforce: length >= 12, at least one letter and one digit."""
if len(pw) < MIN_LENGTH:
raise PasswordPolicyError(f"Password must be at least {MIN_LENGTH} characters.")
if not any(c.isalpha() for c in pw):
raise PasswordPolicyError("Password must contain at least one letter.")
if not any(c.isdigit() for c in pw):
raise PasswordPolicyError("Password must contain at least one digit.")
def hash_password(pw: str) -> str:
return _hasher.hash(pw)
def verify_password(pw: str, encoded: str) -> bool:
try:
return _hasher.verify(encoded, pw)
except (VerifyMismatchError, InvalidHashError, VerificationError):
return False
except Exception:
return False
def new_session_id() -> str:
"""Opaque 128-bit session identifier rendered as 32 hex chars."""
return uuid.uuid4().hex

View File

@ -0,0 +1,37 @@
import pytest
from clearview_app.auth.security import (
PasswordPolicyError,
hash_password,
new_session_id,
validate_password,
verify_password,
)
def test_hash_and_verify_roundtrip():
h = hash_password("CorrectHorse42")
assert verify_password("CorrectHorse42", h) is True
assert verify_password("wrong", h) is False
def test_verify_returns_false_on_garbage_hash():
assert verify_password("anything", "not-a-real-hash") is False
@pytest.mark.parametrize("pw", ["short1A", "alllowercase", "ALLUPPERCASE", "12345678901234"])
def test_policy_rejects(pw):
with pytest.raises(PasswordPolicyError):
validate_password(pw)
@pytest.mark.parametrize("pw", ["CorrectHorse42", "abcdefghij12"])
def test_policy_accepts(pw):
validate_password(pw)
def test_new_session_id_unique_and_hex():
a = new_session_id()
b = new_session_id()
assert a != b
assert len(a) == 32 and all(c in "0123456789abcdef" for c in a)