Authentication helpers: token → session cookie exchange, bearer/cookie checks.
The launcher calls /auth/session?token=… exactly once; the server mints a
random session id, remembers it in app.state.sessions and sets it as an
HttpOnly; SameSite=Strict; Path=/ cookie before redirecting to /.
The cookie never carries the token itself, and a server restart forgets every
session (the UI then gets 401 and returns to the launcher). Every other
/api/* and /ws/* route accepts that cookie or
Authorization: Bearer <token> (scripts). POST /auth/logout forgets
the session and clears the cookie.
new_session_id
Random, unguessable session id (never derived from the token).
Source code in tit/server/auth.py
| def new_session_id() -> str:
"""Random, unguessable session id (never derived from the token)."""
return secrets.token_urlsafe(32)
|
is_authorized
is_authorized(token: str, sessions: set[str], *, cookie: str | None, authorization: str | None) -> bool
True if the cookie names a live session or the bearer credential matches.
Source code in tit/server/auth.py
| def is_authorized(
token: str,
sessions: set[str],
*,
cookie: str | None,
authorization: str | None,
) -> bool:
"""True if the cookie names a live session or the bearer credential matches."""
if cookie and cookie in sessions:
return True
return _token_matches(_bearer(authorization), token)
|
require_auth
require_auth(request: Request) -> None
FastAPI dependency: 401 unless the request carries valid credentials.
A matching Authorization: Bearer is always sufficient. A cookie is sufficient for a
"safe" method outright, and for any other method only once :func:_cookie_csrf_ok also
passes -- otherwise this is a 403, not a 401 (the credential is valid; the request is just
not trusted to have come from this app).
Source code in tit/server/auth.py
| def require_auth(request: Request) -> None:
"""FastAPI dependency: 401 unless the request carries valid credentials.
A matching ``Authorization: Bearer`` is always sufficient. A cookie is sufficient for a
"safe" method outright, and for any other method only once :func:`_cookie_csrf_ok` also
passes -- otherwise this is a 403, not a 401 (the credential is valid; the request is just
not trusted to have come from this app).
"""
state = request.app.state
if _token_matches(
_bearer(request.headers.get("authorization")), state.settings.token
):
return
cookie = request.cookies.get(COOKIE_NAME)
if cookie and cookie in state.sessions:
if request.method.upper() in _SAFE_METHODS or _cookie_csrf_ok(request):
return
raise HTTPException(
status_code=403,
detail="Cross-site request blocked: missing or foreign Origin",
)
raise HTTPException(status_code=401, detail="Unauthorized")
|
origin_allowed
origin_allowed(origin: str | None, host: str | None, dev_origins: tuple[str, ...] = (), *, secure: bool = False) -> bool
WebSocket origin policy.
Accepted: no Origin at all (curl, tests, scripts); an origin whose
scheme matches the connection (http for ws, https for
wss) and whose host[:port] equals the request's Host header
(same origin); or an exact entry of dev_origins (--dev-origin /
TIT_DEV_ORIGINS, e.g. the Vite dev server).
Source code in tit/server/auth.py
| def origin_allowed(
origin: str | None,
host: str | None,
dev_origins: tuple[str, ...] = (),
*,
secure: bool = False,
) -> bool:
"""WebSocket origin policy.
Accepted: no ``Origin`` at all (curl, tests, scripts); an origin whose
scheme matches the connection (``http`` for ``ws``, ``https`` for
``wss``) and whose ``host[:port]`` equals the request's ``Host`` header
(same origin); or an exact entry of *dev_origins* (``--dev-origin`` /
``TIT_DEV_ORIGINS``, e.g. the Vite dev server).
"""
if origin is None:
return True
if origin in dev_origins:
return True
parts = urlsplit(origin)
if parts.scheme != ("https" if secure else "http") or not parts.netloc:
return False
return bool(host) and parts.netloc.lower() == host.lower()
|
websocket_authorized
websocket_authorized(ws: WebSocket) -> bool
Cookie, bearer header or ?token= for WebSocket handshakes.
Source code in tit/server/auth.py
| def websocket_authorized(ws: WebSocket) -> bool:
"""Cookie, bearer header or ``?token=`` for WebSocket handshakes."""
state = ws.app.state
if is_authorized(
state.settings.token,
state.sessions,
cookie=ws.cookies.get(COOKIE_NAME),
authorization=ws.headers.get("authorization"),
):
return True
return _token_matches(ws.query_params.get("token"), state.settings.token)
|