pounce/backend/app/security.py
Yves Gugger eaa8ad1511
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
fix: improve logout to clear all cookies and storage, fix subscription tier case
2025-12-17 13:44:49 +01:00

82 lines
2.0 KiB
Python

"""
Security helpers (cookies, environment checks).
We use HttpOnly cookies for browser auth to avoid storing JWTs in localStorage/URLs.
"""
from __future__ import annotations
import os
from fastapi import Response
AUTH_COOKIE_NAME = "pounce_access_token"
def cookie_domain() -> str | None:
"""
Optional cookie domain override.
Use with care. Example (share across subdomains): COOKIE_DOMAIN=.pounce.ch
Leave empty in local development (localhost).
"""
value = os.getenv("COOKIE_DOMAIN", "").strip()
return value or None
def should_use_secure_cookies() -> bool:
"""
Determine whether cookies should be marked Secure.
Prefer explicit config via COOKIE_SECURE=true. Otherwise infer from SITE_URL / ENVIRONMENT.
"""
if os.getenv("COOKIE_SECURE", "").lower() == "true":
return True
site_url = os.getenv("SITE_URL", "")
if site_url.startswith("https://"):
return True
env = os.getenv("ENVIRONMENT", "").lower()
return env in {"prod", "production"}
def set_auth_cookie(response: Response, token: str, max_age_seconds: int) -> None:
response.set_cookie(
key=AUTH_COOKIE_NAME,
value=token,
httponly=True,
secure=should_use_secure_cookies(),
samesite="lax",
max_age=max_age_seconds,
path="/",
domain=cookie_domain(),
)
def clear_auth_cookie(response: Response) -> None:
"""Clear auth cookie with explicit expiry to ensure removal."""
# Delete with same settings used when setting (required for proper removal)
response.delete_cookie(
key=AUTH_COOKIE_NAME,
path="/",
domain=cookie_domain(),
secure=True,
httponly=True,
samesite="lax",
)
# Also set with max_age=0 as fallback (some browsers need this)
response.set_cookie(
key=AUTH_COOKIE_NAME,
value="",
max_age=0,
expires=0,
path="/",
domain=cookie_domain(),
secure=True,
httponly=True,
samesite="lax",
)