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)