feat: Use secure random tokens for authentication and password handling in tests
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user