Fix cookie not accepted in safari
This commit is contained in:
@@ -90,12 +90,49 @@ def _resolve_cookie_domain() -> str | None:
|
||||
return configured_domain
|
||||
|
||||
|
||||
def _resolve_cookie_samesite(secure_cookie: bool) -> str:
|
||||
"""Returns cookie SameSite mode with secure-aware defaults for browser compatibility."""
|
||||
def _resolve_cookie_domains() -> tuple[str | None, ...]:
|
||||
"""Returns cookie domain variants with a host-only cookie first for browser compatibility."""
|
||||
|
||||
configured_domain = _resolve_cookie_domain()
|
||||
if configured_domain is None:
|
||||
return (None,)
|
||||
return (None, configured_domain)
|
||||
|
||||
|
||||
def _request_matches_cookie_domain(request: Request) -> bool:
|
||||
"""Returns whether request and origin hosts both sit under the configured cookie domain."""
|
||||
|
||||
configured_domain = _resolve_cookie_domain()
|
||||
if configured_domain is None:
|
||||
return False
|
||||
|
||||
origin_header = request.headers.get("origin", "").strip()
|
||||
origin_host = urlparse(origin_header).hostname.strip().lower() if origin_header else ""
|
||||
if not origin_host:
|
||||
return False
|
||||
|
||||
request_url = getattr(request, "url", None)
|
||||
request_host = str(getattr(request_url, "hostname", "")).strip().lower() if request_url is not None else ""
|
||||
if not request_host:
|
||||
parsed_public_base_url = urlparse(get_settings().public_base_url.strip())
|
||||
request_host = parsed_public_base_url.hostname.strip().lower() if parsed_public_base_url.hostname else ""
|
||||
if not request_host:
|
||||
return False
|
||||
|
||||
def _matches(candidate: str) -> bool:
|
||||
return candidate == configured_domain or candidate.endswith(f".{configured_domain}")
|
||||
|
||||
return _matches(origin_host) and _matches(request_host)
|
||||
|
||||
|
||||
def _resolve_cookie_samesite(request: Request, secure_cookie: bool) -> str:
|
||||
"""Returns cookie SameSite mode with same-site subdomain compatibility defaults."""
|
||||
|
||||
configured_mode = get_settings().auth_cookie_samesite.strip().lower()
|
||||
if configured_mode in {"strict", "lax", "none"}:
|
||||
if configured_mode in {"strict", "lax"}:
|
||||
return configured_mode
|
||||
if configured_mode == "none":
|
||||
return "lax" if _request_matches_cookie_domain(request) else "none"
|
||||
return "none" if secure_cookie else "lax"
|
||||
|
||||
|
||||
@@ -107,30 +144,39 @@ def _session_cookie_ttl_seconds(expires_at: datetime) -> int:
|
||||
return max(1, ttl)
|
||||
|
||||
|
||||
def _set_session_cookie(response: Response, session_token: str, *, expires_at: datetime, secure: bool) -> None:
|
||||
def _set_session_cookie(
|
||||
response: Response,
|
||||
session_token: str,
|
||||
*,
|
||||
request: Request,
|
||||
expires_at: datetime,
|
||||
secure: bool,
|
||||
) -> None:
|
||||
"""Stores the issued session token in a browser HttpOnly auth cookie."""
|
||||
|
||||
if response is None or not hasattr(response, "set_cookie"):
|
||||
return
|
||||
expires_seconds = _session_cookie_ttl_seconds(expires_at)
|
||||
cookie_domain = _resolve_cookie_domain()
|
||||
same_site_mode = _resolve_cookie_samesite(secure)
|
||||
response.set_cookie(
|
||||
SESSION_COOKIE_NAME,
|
||||
value=session_token,
|
||||
max_age=expires_seconds,
|
||||
httponly=True,
|
||||
secure=secure,
|
||||
samesite=same_site_mode,
|
||||
path="/",
|
||||
domain=cookie_domain,
|
||||
)
|
||||
same_site_mode = _resolve_cookie_samesite(request, secure)
|
||||
for cookie_domain in _resolve_cookie_domains():
|
||||
cookie_kwargs = {
|
||||
"value": session_token,
|
||||
"max_age": expires_seconds,
|
||||
"httponly": True,
|
||||
"secure": secure,
|
||||
"samesite": same_site_mode,
|
||||
"path": "/",
|
||||
}
|
||||
if cookie_domain is not None:
|
||||
cookie_kwargs["domain"] = cookie_domain
|
||||
response.set_cookie(SESSION_COOKIE_NAME, **cookie_kwargs)
|
||||
|
||||
|
||||
def _set_csrf_cookie(
|
||||
response: Response,
|
||||
csrf_token: str,
|
||||
*,
|
||||
request: Request,
|
||||
expires_at: datetime,
|
||||
secure: bool,
|
||||
) -> None:
|
||||
@@ -138,18 +184,19 @@ def _set_csrf_cookie(
|
||||
|
||||
if response is None or not hasattr(response, "set_cookie"):
|
||||
return
|
||||
cookie_domain = _resolve_cookie_domain()
|
||||
same_site_mode = _resolve_cookie_samesite(secure)
|
||||
response.set_cookie(
|
||||
CSRF_COOKIE_NAME,
|
||||
value=csrf_token,
|
||||
max_age=_session_cookie_ttl_seconds(expires_at),
|
||||
httponly=False,
|
||||
secure=secure,
|
||||
samesite=same_site_mode,
|
||||
path="/",
|
||||
domain=cookie_domain,
|
||||
)
|
||||
same_site_mode = _resolve_cookie_samesite(request, secure)
|
||||
for cookie_domain in _resolve_cookie_domains():
|
||||
cookie_kwargs = {
|
||||
"value": csrf_token,
|
||||
"max_age": _session_cookie_ttl_seconds(expires_at),
|
||||
"httponly": False,
|
||||
"secure": secure,
|
||||
"samesite": same_site_mode,
|
||||
"path": "/",
|
||||
}
|
||||
if cookie_domain is not None:
|
||||
cookie_kwargs["domain"] = cookie_domain
|
||||
response.set_cookie(CSRF_COOKIE_NAME, **cookie_kwargs)
|
||||
|
||||
|
||||
def _clear_session_cookies(response: Response) -> None:
|
||||
@@ -157,9 +204,12 @@ def _clear_session_cookies(response: Response) -> None:
|
||||
|
||||
if response is None or not hasattr(response, "delete_cookie"):
|
||||
return
|
||||
cookie_domain = _resolve_cookie_domain()
|
||||
response.delete_cookie(SESSION_COOKIE_NAME, path="/", domain=cookie_domain)
|
||||
response.delete_cookie(CSRF_COOKIE_NAME, path="/", domain=cookie_domain)
|
||||
for cookie_domain in _resolve_cookie_domains():
|
||||
delete_kwargs = {"path": "/"}
|
||||
if cookie_domain is not None:
|
||||
delete_kwargs["domain"] = cookie_domain
|
||||
response.delete_cookie(SESSION_COOKIE_NAME, **delete_kwargs)
|
||||
response.delete_cookie(CSRF_COOKIE_NAME, **delete_kwargs)
|
||||
|
||||
|
||||
@router.post("/login", response_model=AuthLoginResponse)
|
||||
@@ -241,12 +291,14 @@ def login(
|
||||
_set_session_cookie(
|
||||
response,
|
||||
issued_session.token,
|
||||
request=request,
|
||||
expires_at=issued_session.expires_at,
|
||||
secure=secure_cookie,
|
||||
)
|
||||
_set_csrf_cookie(
|
||||
response,
|
||||
csrf_token,
|
||||
request=request,
|
||||
expires_at=issued_session.expires_at,
|
||||
secure=secure_cookie,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user