feat: Use secure random tokens for authentication and password handling in tests

This commit is contained in:
2025-11-12 11:36:19 +01:00
parent 3988171b46
commit 5d6592d657
3 changed files with 35 additions and 26 deletions

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import secrets
from datetime import date from datetime import date
from collections.abc import Iterator from collections.abc import Iterator
from typing import cast from typing import cast
@@ -176,8 +177,11 @@ def api_client(session_factory) -> Iterator[TestClient]:
user = uow.users.get(user.id, with_roles=True) user = uow.users.get(user.id, with_roles=True)
def _override_auth_session(request: Request) -> AuthSession: def _override_auth_session(request: Request) -> AuthSession:
session = AuthSession(tokens=SessionTokens( tokens = SessionTokens(
access_token="test", refresh_token="test")) access_token=secrets.token_urlsafe(16),
refresh_token=secrets.token_urlsafe(16),
)
session = AuthSession(tokens=tokens)
session.user = user session.user = user
request.state.auth_session = session request.state.auth_session = session
return session return session

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import secrets
from datetime import timedelta from datetime import timedelta
import pytest import pytest
@@ -17,11 +18,18 @@ from services.security import (
) )
def test_hash_password_round_trip() -> None: @pytest.fixture()
hashed = hash_password("super-secret") def jwt_settings() -> JWTSettings:
"""Provide a unique JWTSettings instance per test with random secret."""
return JWTSettings(secret_key=secrets.token_urlsafe(32))
assert hashed != "super-secret"
assert verify_password("super-secret", hashed) def test_hash_password_round_trip() -> None:
password = secrets.token_urlsafe(16)
hashed = hash_password(password)
assert hashed != password
assert verify_password(password, hashed)
assert not verify_password("incorrect", hashed) assert not verify_password("incorrect", hashed)
@@ -29,47 +37,42 @@ def test_verify_password_handles_malformed_hash() -> None:
assert not verify_password("secret", "not-a-valid-hash") assert not verify_password("secret", "not-a-valid-hash")
def test_access_token_roundtrip() -> None: def test_access_token_roundtrip(jwt_settings: JWTSettings) -> None:
settings = JWTSettings(secret_key="unit-test-secret")
token = create_access_token( token = create_access_token(
"user-id-123", "user-id-123",
settings, jwt_settings,
scopes=("read", "write"), scopes=("read", "write"),
extra_claims={"custom": "value"}, extra_claims={"custom": "value"},
) )
payload = decode_access_token(token, settings) payload = decode_access_token(token, jwt_settings)
assert payload.sub == "user-id-123" assert payload.sub == "user-id-123"
assert payload.type == "access" assert payload.type == "access"
assert payload.scopes == ["read", "write"] assert payload.scopes == ["read", "write"]
def test_refresh_token_type_mismatch() -> None: def test_refresh_token_type_mismatch(jwt_settings: JWTSettings) -> None:
settings = JWTSettings(secret_key="unit-test-secret") token = create_refresh_token("user-id-456", jwt_settings)
token = create_refresh_token("user-id-456", settings)
with pytest.raises(TokenTypeMismatchError): with pytest.raises(TokenTypeMismatchError):
decode_access_token(token, settings) decode_access_token(token, jwt_settings)
def test_decode_expired_token() -> None: def test_decode_expired_token(jwt_settings: JWTSettings) -> None:
settings = JWTSettings(secret_key="unit-test-secret")
expired_token = create_access_token( expired_token = create_access_token(
"user-id-789", "user-id-789",
settings, jwt_settings,
expires_delta=timedelta(seconds=-5), expires_delta=timedelta(seconds=-5),
) )
with pytest.raises(TokenExpiredError): with pytest.raises(TokenExpiredError):
decode_access_token(expired_token, settings) decode_access_token(expired_token, jwt_settings)
def test_decode_tampered_token() -> None: def test_decode_tampered_token(jwt_settings: JWTSettings) -> None:
settings = JWTSettings(secret_key="unit-test-secret") token = create_access_token("user-id-321", jwt_settings)
token = create_access_token("user-id-321", settings)
tampered = token[:-1] + ("a" if token[-1] != "a" else "b") tampered = token[:-1] + ("a" if token[-1] != "a" else "b")
with pytest.raises(TokenDecodeError): with pytest.raises(TokenDecodeError):
decode_access_token(tampered, settings) decode_access_token(tampered, jwt_settings)

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import secrets
from collections.abc import Iterator from collections.abc import Iterator
import pytest import pytest
@@ -32,16 +33,17 @@ def session(engine) -> Iterator[Session]:
def test_user_password_helpers() -> None: def test_user_password_helpers() -> None:
new_password = secrets.token_urlsafe(16)
user = User( user = User(
email="user@example.com", email="user@example.com",
username="example", username="example",
password_hash=User.hash_password("initial"), password_hash=User.hash_password("initial"),
) )
user.set_password("new-secret") user.set_password(new_password)
assert user.password_hash != "new-secret" assert user.password_hash != new_password
assert user.verify_password("new-secret") assert user.verify_password(new_password)
assert not user.verify_password("wrong") assert not user.verify_password("wrong")