Split 3/4 of the companion bridge (#863, #871 landed 1/4 and 2/4). Adds admin-only device pairing to the companion router. - GET /api/companion/pair -- renders a form; never mints (a GET must not mint a credential: SameSite=Lax session cookies ride top-level GET navigations, so GET-minting would be CSRF-triggerable via a link/<img>) - POST /api/companion/pair -- mints a one-time chat-scoped token. Admin-cookie only; CSRF-safe because a SameSite=Lax cookie is not sent on a cross-site POST, the same protection POST /api/tokens relies on. ?format=json returns the pairing payload for an in-app screen. Minting invalidates the auth middleware's token cache so the code works on the next request with no restart. companion/pairing.py holds the mint/LAN/QR helpers; the token is shown once and stored only as a bcrypt hash + prefix (mirrors routes/api_token_routes.py). Tests (tests/test_companion_pairing.py): - a bearer/'api' caller and a non-admin user are rejected by require_admin (403); an admin passes - the token is returned once and persisted only as a hash - minting invalidates the cache (works without restart) - minting is exposed on POST, never GET (CSRF)
12 lines
457 B
Python
12 lines
457 B
Python
"""Odysseus companion bridge — additive LAN endpoints.
|
|
|
|
Read endpoints (/api/companion/ping, /info, owner-scoped /models) so a LAN
|
|
client can discover what a server offers, plus admin-only pairing
|
|
(/api/companion/pair) that mints a one-time chat-scoped token on POST. No new LLM
|
|
logic; auth is enforced by the existing AuthMiddleware. See companion/README.md.
|
|
"""
|
|
|
|
from companion.routes import setup_companion_routes
|
|
|
|
__all__ = ["setup_companion_routes"]
|