deploy: sync backend to Space root (learn-lesson HF cache fix)
Browse files- app/routes/auth.py +5 -5
- tests/test_media_auth_cookie.py +69 -9
app/routes/auth.py
CHANGED
|
@@ -269,14 +269,14 @@ def _auth_redirect_for_user(user: User, next_path: str) -> RedirectResponse:
|
|
| 269 |
def _is_production_environment(settings: object | None = None) -> bool:
|
| 270 |
"""Resolve production even if an older Settings build lacks is_production."""
|
| 271 |
active = settings or get_settings()
|
| 272 |
-
|
| 273 |
-
flag = getattr(active, "is_production", None)
|
| 274 |
-
except AttributeError:
|
| 275 |
-
flag = None
|
| 276 |
if isinstance(flag, bool):
|
| 277 |
production = flag
|
| 278 |
else:
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
| 280 |
logger.debug("[FIX] media-auth cookie production=%s", production)
|
| 281 |
return production
|
| 282 |
|
|
|
|
| 269 |
def _is_production_environment(settings: object | None = None) -> bool:
|
| 270 |
"""Resolve production even if an older Settings build lacks is_production."""
|
| 271 |
active = settings or get_settings()
|
| 272 |
+
flag = getattr(active, "is_production", None)
|
|
|
|
|
|
|
|
|
|
| 273 |
if isinstance(flag, bool):
|
| 274 |
production = flag
|
| 275 |
else:
|
| 276 |
+
environment = str(getattr(active, "environment", "") or "").strip().casefold()
|
| 277 |
+
# Unknown environment fails closed so a missing flag cannot mint a
|
| 278 |
+
# non-Secure cookie that browsers would still send.
|
| 279 |
+
production = True if not environment else environment == "production"
|
| 280 |
logger.debug("[FIX] media-auth cookie production=%s", production)
|
| 281 |
return production
|
| 282 |
|
tests/test_media_auth_cookie.py
CHANGED
|
@@ -11,6 +11,25 @@ from types import SimpleNamespace
|
|
| 11 |
from fastapi import Response
|
| 12 |
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
def test_settings_exposes_is_production_from_environment() -> None:
|
| 15 |
from app.core.config import Settings
|
| 16 |
|
|
@@ -20,7 +39,7 @@ def test_settings_exposes_is_production_from_environment() -> None:
|
|
| 20 |
assert Settings(environment=" staging ").is_production is False
|
| 21 |
|
| 22 |
|
| 23 |
-
def
|
| 24 |
response = auth_client.post(
|
| 25 |
"/auth/signup",
|
| 26 |
json={
|
|
@@ -31,12 +50,35 @@ def test_signup_sets_media_cookie_without_crashing(auth_client) -> None:
|
|
| 31 |
)
|
| 32 |
assert response.status_code == 201, response.text
|
| 33 |
assert response.json()["access_token"]
|
| 34 |
-
cookie = response.headers.get("set-cookie", "")
|
| 35 |
-
assert "
|
| 36 |
-
assert "
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
from app.routes.auth import _set_media_auth_cookie
|
| 41 |
|
| 42 |
response = Response()
|
|
@@ -46,7 +88,25 @@ def test_media_cookie_is_secure_when_environment_is_production() -> None:
|
|
| 46 |
3600,
|
| 47 |
settings=SimpleNamespace(environment="production"),
|
| 48 |
)
|
| 49 |
-
cookie = response.headers.get("set-cookie", "")
|
| 50 |
-
assert "
|
| 51 |
-
assert "
|
| 52 |
-
assert "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
from fastapi import Response
|
| 12 |
|
| 13 |
|
| 14 |
+
def _media_cookie_flags(header: str) -> dict[str, object]:
|
| 15 |
+
"""Parse Set-Cookie attributes so JWT contents cannot fake flags."""
|
| 16 |
+
parts = [part.strip() for part in header.split(";") if part.strip()]
|
| 17 |
+
assert parts, f"empty Set-Cookie header: {header!r}"
|
| 18 |
+
name, _, value = parts[0].partition("=")
|
| 19 |
+
flags: dict[str, str | bool] = {}
|
| 20 |
+
for part in parts[1:]:
|
| 21 |
+
key, separator, raw = part.partition("=")
|
| 22 |
+
flags[key.casefold()] = raw if separator else True
|
| 23 |
+
return {
|
| 24 |
+
"name": name,
|
| 25 |
+
"value": value,
|
| 26 |
+
"httponly": "httponly" in flags,
|
| 27 |
+
"secure": "secure" in flags,
|
| 28 |
+
"path": str(flags.get("path", "")),
|
| 29 |
+
"samesite": str(flags.get("samesite", "")).casefold(),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
def test_settings_exposes_is_production_from_environment() -> None:
|
| 34 |
from app.core.config import Settings
|
| 35 |
|
|
|
|
| 39 |
assert Settings(environment=" staging ").is_production is False
|
| 40 |
|
| 41 |
|
| 42 |
+
def test_signup_sets_dev_media_cookie_flags(auth_client) -> None:
|
| 43 |
response = auth_client.post(
|
| 44 |
"/auth/signup",
|
| 45 |
json={
|
|
|
|
| 50 |
)
|
| 51 |
assert response.status_code == 201, response.text
|
| 52 |
assert response.json()["access_token"]
|
| 53 |
+
cookie = _media_cookie_flags(response.headers.get("set-cookie", ""))
|
| 54 |
+
assert cookie["name"] == "docdoe_media_token"
|
| 55 |
+
assert cookie["httponly"] is True
|
| 56 |
+
assert cookie["secure"] is False
|
| 57 |
+
assert cookie["path"] == "/generated"
|
| 58 |
+
assert cookie["samesite"] == "lax"
|
| 59 |
|
| 60 |
|
| 61 |
+
def test_media_cookie_uses_settings_is_production() -> None:
|
| 62 |
+
from app.core.config import Settings
|
| 63 |
+
from app.routes.auth import _set_media_auth_cookie
|
| 64 |
+
|
| 65 |
+
response = Response()
|
| 66 |
+
_set_media_auth_cookie(
|
| 67 |
+
response,
|
| 68 |
+
"token-value",
|
| 69 |
+
3600,
|
| 70 |
+
settings=Settings(environment="production"),
|
| 71 |
+
)
|
| 72 |
+
cookie = _media_cookie_flags(response.headers.get("set-cookie", ""))
|
| 73 |
+
assert cookie["name"] == "docdoe_media_token"
|
| 74 |
+
assert cookie["value"] == "token-value"
|
| 75 |
+
assert cookie["httponly"] is True
|
| 76 |
+
assert cookie["secure"] is True
|
| 77 |
+
assert cookie["path"] == "/generated"
|
| 78 |
+
assert cookie["samesite"] == "none"
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def test_media_cookie_falls_back_to_environment_when_flag_missing() -> None:
|
| 82 |
from app.routes.auth import _set_media_auth_cookie
|
| 83 |
|
| 84 |
response = Response()
|
|
|
|
| 88 |
3600,
|
| 89 |
settings=SimpleNamespace(environment="production"),
|
| 90 |
)
|
| 91 |
+
cookie = _media_cookie_flags(response.headers.get("set-cookie", ""))
|
| 92 |
+
assert cookie["httponly"] is True
|
| 93 |
+
assert cookie["secure"] is True
|
| 94 |
+
assert cookie["path"] == "/generated"
|
| 95 |
+
assert cookie["samesite"] == "none"
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def test_unknown_environment_fails_closed_to_secure_cookie() -> None:
|
| 99 |
+
from app.routes.auth import _set_media_auth_cookie
|
| 100 |
+
|
| 101 |
+
response = Response()
|
| 102 |
+
_set_media_auth_cookie(
|
| 103 |
+
response,
|
| 104 |
+
"token-value",
|
| 105 |
+
3600,
|
| 106 |
+
settings=SimpleNamespace(),
|
| 107 |
+
)
|
| 108 |
+
cookie = _media_cookie_flags(response.headers.get("set-cookie", ""))
|
| 109 |
+
assert cookie["secure"] is True
|
| 110 |
+
assert cookie["samesite"] == "none"
|
| 111 |
+
assert cookie["httponly"] is True
|
| 112 |
+
assert cookie["path"] == "/generated"
|