feat: add PasswordService with argon2 hash/verify
This commit is contained in:
parent
e543fe2229
commit
e6f5ea7f0c
2 changed files with 52 additions and 0 deletions
20
src/fastapi_oidc_op/authn/password.py
Normal file
20
src/fastapi_oidc_op/authn/password.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from argon2 import PasswordHasher
|
||||||
|
from argon2.exceptions import InvalidHashError, VerifyMismatchError
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordService:
|
||||||
|
"""Argon2 password hashing and verification."""
|
||||||
|
|
||||||
|
def __init__(self, hasher: PasswordHasher | None = None) -> None:
|
||||||
|
self._hasher = hasher or PasswordHasher()
|
||||||
|
|
||||||
|
def hash(self, password: str) -> str:
|
||||||
|
"""Hash a password using argon2id. Returns the full hash string."""
|
||||||
|
return self._hasher.hash(password)
|
||||||
|
|
||||||
|
def verify(self, password_hash: str, password: str) -> bool:
|
||||||
|
"""Verify a password against an argon2 hash. Returns True if valid."""
|
||||||
|
try:
|
||||||
|
return self._hasher.verify(password_hash, password)
|
||||||
|
except (VerifyMismatchError, InvalidHashError):
|
||||||
|
return False
|
||||||
32
tests/test_authn/test_password.py
Normal file
32
tests/test_authn/test_password.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from argon2 import PasswordHasher
|
||||||
|
|
||||||
|
from fastapi_oidc_op.authn.password import PasswordService
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_returns_argon2_string() -> None:
|
||||||
|
service = PasswordService(hasher=PasswordHasher(time_cost=1, memory_cost=8192))
|
||||||
|
result = service.hash("correcthorse")
|
||||||
|
assert result.startswith("$argon2id$")
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_correct_password() -> None:
|
||||||
|
service = PasswordService(hasher=PasswordHasher(time_cost=1, memory_cost=8192))
|
||||||
|
hashed = service.hash("correcthorse")
|
||||||
|
assert service.verify(hashed, "correcthorse") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_wrong_password() -> None:
|
||||||
|
service = PasswordService(hasher=PasswordHasher(time_cost=1, memory_cost=8192))
|
||||||
|
hashed = service.hash("correcthorse")
|
||||||
|
assert service.verify(hashed, "wrongpassword") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_invalid_hash() -> None:
|
||||||
|
service = PasswordService(hasher=PasswordHasher(time_cost=1, memory_cost=8192))
|
||||||
|
assert service.verify("not-a-hash", "password") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_hasher() -> None:
|
||||||
|
service = PasswordService()
|
||||||
|
hashed = service.hash("test")
|
||||||
|
assert service.verify(hashed, "test") is True
|
||||||
Loading…
Add table
Add a link
Reference in a new issue