An expired or orphaned session left the app on a half-rendered page instead of the login screen: a properly signed token naming a user who no longer exists answered 404, which the client does not treat as an authentication failure. All failures in get_current_user are 401 now, and the client stops retrying them, so a dead session goes straight back to the login screen. - Clicking an edge names the two nodes it runs between, not just the message. - A node with a problem shows one badge carrying the whole explanation, rather than a corner badge and a status dot saying the same thing twice. The dot is back to what it is good at: whether the node ran. - The sidebar's collapse control sits in the sidebar, where a phone still finds one in the chrome because there is no sidebar on screen to hold it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WzrvW7rjQbynnhF6pxh6i
46 lines
1.4 KiB
Python
46 lines
1.4 KiB
Python
"""A session the server will not accept must say so as an auth failure.
|
|
|
|
The client clears the stored token and returns to the login screen on 401. Any
|
|
other status leaves the browser holding a token that can never work, stuck on a
|
|
page that will not load.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import timedelta
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.core import security
|
|
from app.core.config import settings
|
|
|
|
PROTECTED = [
|
|
f"{settings.API_V1_STR}/users/me",
|
|
f"{settings.API_V1_STR}/flows/",
|
|
]
|
|
|
|
|
|
def token_for(subject: str) -> str:
|
|
return security.create_access_token(subject, timedelta(days=1))
|
|
|
|
|
|
def test_no_token_is_unauthorised(client: TestClient) -> None:
|
|
for path in PROTECTED:
|
|
assert client.get(path).status_code == 401
|
|
|
|
|
|
def test_a_token_we_did_not_sign_is_unauthorised(client: TestClient) -> None:
|
|
headers = {"Authorization": "Bearer not.a.real.token"}
|
|
for path in PROTECTED:
|
|
assert client.get(path, headers=headers).status_code == 401
|
|
|
|
|
|
def test_a_token_for_a_user_who_is_gone_is_unauthorised(
|
|
client: TestClient,
|
|
) -> None:
|
|
# Properly signed, but the user it names no longer exists — which is what a
|
|
# browser holds after the database is reset.
|
|
headers = {"Authorization": f"Bearer {token_for(str(uuid.uuid4()))}"}
|
|
for path in PROTECTED:
|
|
response = client.get(path, headers=headers)
|
|
assert response.status_code == 401, path
|