Both password and WebAuthn login wrote the authenticated identity onto the existing pre-auth session, so a fixed/planted session could be elevated to an authenticated one. Add _establish_authenticated_session() which clears the session (preserving only a pending OIDC authorization request) before setting the identity, used by both login paths. Tests that reused a pre-login CSRF token now re-fetch it after login, matching real client behavior. Refs: porchlight-vxr Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
from datetime import UTC, datetime
|
|
|
|
from httpx import AsyncClient
|
|
|
|
from porchlight.authn.password import PasswordHasher, PasswordService
|
|
from porchlight.models import PasswordCredential, User
|
|
from tests.conftest import get_csrf_token
|
|
|
|
|
|
async def _create_active_user(client: AsyncClient) -> None:
|
|
app = client._transport.app # type: ignore[union-attr]
|
|
user = User(
|
|
userid="login-01",
|
|
username="loginuser",
|
|
created_at=datetime.now(UTC),
|
|
updated_at=datetime.now(UTC),
|
|
)
|
|
await app.state.user_repo.create(user)
|
|
svc = PasswordService(hasher=PasswordHasher(time_cost=1, memory_cost=8192))
|
|
await app.state.credential_repo.create_password(
|
|
PasswordCredential(user_id=user.userid, password_hash=svc.hash("password123!Secure"))
|
|
)
|
|
|
|
|
|
async def test_login_resets_session_to_prevent_fixation(client: AsyncClient) -> None:
|
|
"""Logging in must reset the pre-auth session so an attacker-fixed session
|
|
cannot become authenticated. We observe this via the CSRF token (stored in
|
|
the session) changing across login."""
|
|
await _create_active_user(client)
|
|
|
|
pre_login_token = await get_csrf_token(client)
|
|
|
|
res = await client.post(
|
|
"/login/password",
|
|
data={"username": "loginuser", "password": "password123!Secure"},
|
|
headers={"HX-Request": "true", "X-CSRF-Token": pre_login_token},
|
|
)
|
|
assert res.status_code == 200
|
|
assert res.headers.get("HX-Redirect") # logged in
|
|
|
|
post_login_token = await get_csrf_token(client)
|
|
assert post_login_token != pre_login_token
|