Spaces:
Running
Running
Download sinetti_api/browser_session.py from Couscouskabyle/iscc-app: direct link, hf CLI and curl.
- Browser
- Download file 5.87 kB
-
https://huggingface.co/spaces/Couscouskabyle/iscc-app/resolve/main/sinetti_api/browser_session.py
- Command line
-
hf download hf://spaces/Couscouskabyle/iscc-app/sinetti_api/browser_session.py
-
curl -L -o browser_session.py https://huggingface.co/spaces/Couscouskabyle/iscc-app/resolve/main/sinetti_api/browser_session.py
5.87 kB
| """Browser transport only; session_service remains the registered-session authority.""" | |
| from __future__ import annotations | |
| import hashlib | |
| import hmac | |
| import os | |
| import time | |
| from urllib.parse import urlsplit | |
| from starlette.requests import Request | |
| from starlette.responses import JSONResponse, Response | |
| from sinetti_services import session_token | |
| from sinetti_api.runtime_security import local_loopback_request | |
| _SAFE = frozenset({"GET", "HEAD", "OPTIONS"}) | |
| _PREAUTH = frozenset({ | |
| "/api/auth/login", "/api/auth/email-code/verify", "/api/auth/email-code/resend", | |
| "/api/auth/oidc/complete", "/api/auth/password-reset/request", | |
| "/api/auth/password-reset/confirm", "/api/auth/browser-session", | |
| }) | |
| def secure_cookies(request: Request) -> bool: | |
| return request.url.scheme != "http" or not local_loopback_request(request) | |
| def cookie_names(request: Request) -> tuple[str, str]: | |
| prefix = "__Host-" if secure_cookies(request) else "" | |
| return prefix + "sinetti_session", prefix + "sinetti_csrf" | |
| def request_token(request: Request, authorization: str | None = None) -> str: | |
| # Header presence, including an empty/malformed header, has absolute precedence. | |
| if "authorization" in request.headers or authorization is not None: | |
| value = request.headers.get("authorization", "") if authorization is None else authorization | |
| return value[7:].strip() if value.lower().startswith("bearer ") else "" | |
| return request.cookies.get(cookie_names(request)[0], "") | |
| def _csrf(token: str) -> str: | |
| secret = os.environ.get("SINETTI_SESSION_SECRET", "") | |
| if not secret: | |
| raise RuntimeError("Sinetti session secret is not configured") | |
| return hmac.new(secret.encode(), b"browser-csrf-v1\0" + token.encode(), hashlib.sha256).hexdigest() | |
| def set_session_cookies(response: Response, request: Request, token: str, *, now: float | None = None) -> None: | |
| checked_at = time.time() if now is None else now | |
| claims = session_token.verify(token, now=checked_at) | |
| if claims is None: | |
| raise RuntimeError("Cannot install an invalid session") | |
| remaining = max(0, int(claims["exp"] - checked_at)) | |
| session_name, csrf_name = cookie_names(request) | |
| options = dict(max_age=remaining, path="/", secure=secure_cookies(request), samesite="lax") | |
| response.set_cookie(session_name, token, httponly=True, **options) | |
| response.set_cookie(csrf_name, _csrf(token), httponly=False, **options) | |
| response.headers["Cache-Control"] = "no-store" | |
| def clear_session_cookies(response: Response, request: Request) -> None: | |
| for name in cookie_names(request): | |
| response.delete_cookie(name, path="/", secure=secure_cookies(request), httponly=name.endswith("session"), samesite="lax") | |
| response.headers["Cache-Control"] = "no-store" | |
| def _origin(value: str) -> tuple[str, str, int] | None: | |
| try: | |
| parsed = urlsplit(value) | |
| if parsed.scheme not in {"https", "http"} or not parsed.hostname or parsed.username or parsed.password: | |
| return None | |
| if parsed.path not in {"", "/"} or parsed.query or parsed.fragment: | |
| return None | |
| return parsed.scheme, parsed.hostname.lower(), parsed.port or (443 if parsed.scheme == "https" else 80) | |
| except ValueError: | |
| return None | |
| def _foreign_origin(request: Request) -> bool: | |
| fetch_site = request.headers.get("sec-fetch-site", "").lower() | |
| if fetch_site not in {"", "same-origin", "none"}: | |
| return True | |
| supplied = request.headers.get("origin") | |
| if supplied is None: | |
| return False # CLI clients have no Origin; cookie writes still require signed CSRF. | |
| actual = _origin(supplied) | |
| configured = os.environ.get("SINETTI_PUBLIC_BASE_URL", "").strip().rstrip("/") | |
| if not secure_cookies(request): | |
| # A real loopback desk may inherit the production URL used in outbound mail. | |
| configured = "" | |
| expected = _origin(configured) if configured else _origin(str(request.base_url).rstrip("/")) | |
| if configured and secure_cookies(request) and expected and expected[0] != "https": | |
| return True | |
| if not configured and secure_cookies(request) and expected: | |
| # HF terminates TLS outside ASGI. Only the same hostname on HTTPS is accepted. | |
| expected = ("https", expected[1], 443 if expected[2] == 80 else expected[2]) | |
| return actual is None or expected is None or actual != expected | |
| class BrowserSessionMiddleware: | |
| def __init__(self, app): | |
| self.app = app | |
| async def __call__(self, scope, receive, send): | |
| if scope["type"] == "http" and scope["method"].upper() not in _SAFE and scope["path"].startswith("/api/"): | |
| request = Request(scope) | |
| cookie_token = request.cookies.get(cookie_names(request)[0], "") | |
| cookie_auth = bool(cookie_token) and "authorization" not in request.headers | |
| reason = None | |
| if (cookie_auth or scope["path"].startswith("/api/auth/")) and _foreign_origin(request): | |
| reason = "Request origin is not allowed" | |
| elif cookie_auth and scope["path"] not in _PREAUTH: | |
| supplied = request.headers.get("x-sinetti-csrf", "") | |
| try: | |
| valid = bool(supplied) and hmac.compare_digest(supplied, _csrf(cookie_token)) | |
| except (RuntimeError, ValueError, TypeError): | |
| valid = False | |
| if not valid: | |
| reason = "Session request verification failed" | |
| if reason: | |
| response = JSONResponse({"ok": False, "error": {"code": "csrf_rejected", "message": reason}}, status_code=403) | |
| await response(scope, receive, send) | |
| return | |
| await self.app(scope, receive, send) | |