feat: postMessage auth token relay for cross-origin federation login

- Backend: AuthMiddleware.dispatch() checks X-Muxplex-Token header
  after cookie auth, allowing cross-origin federation requests to
  authenticate using a session token sent as a header instead of a
  SameSite=Strict cookie.

- Backend: GET /api/auth/token endpoint returns the current session
  token for authenticated requests. Used by login popups to relay
  the token back to the opener window via postMessage.

- Frontend (app.js): api() injects X-Muxplex-Token header for
  cross-origin requests when a token is stored in localStorage under
  muxplex.federation_tokens keyed by origin.

- Frontend (app.js): storeFederationToken() helper stores tokens.
  window.addEventListener('message') handler catches muxplex-auth-token
  postMessages and stores the token, then retriggers a poll so the
  auth_required source transitions to authenticated immediately.

- Frontend (app.js): _saveRemoteInstances() prunes stale tokens for
  URLs no longer in the remote instances list.

- Frontend (index.html): Popup relay script fetches /api/auth/token
  and sends the token to window.opener via postMessage when loaded
  as a login popup (window.opener present).

Tests added:
  test_auth.py: X-Muxplex-Token header authenticates / invalid rejected
  test_api.py: GET /api/auth/token returns token / 401 when unauthed
  test_app.mjs: api() header injection, storeFederationToken storage,
                index.html popup script presence
This commit is contained in:
Brian Krabach
2026-04-01 08:17:28 -07:00
parent 062a815f83
commit cb264bbaaa
23 changed files with 237 additions and 1 deletions
+24
View File
@@ -460,3 +460,27 @@ def test_middleware_instance_info_path_excluded():
client = TestClient(app, base_url="http://192.168.1.1")
response = client.get("/api/instance-info")
assert response.status_code == 200
# ---------------------------------------------------------------------------
# X-Muxplex-Token header auth (cross-origin federation)
# ---------------------------------------------------------------------------
def test_middleware_valid_token_header_passes():
"""Non-localhost request with a valid X-Muxplex-Token header passes through."""
app = _make_test_app()
token = create_session_cookie("test-secret", ttl_seconds=3600)
client = TestClient(app, base_url="http://192.168.1.1")
response = client.get("/protected", headers={"X-Muxplex-Token": token})
assert response.status_code == 200
assert response.text == "OK"
def test_middleware_invalid_token_header_falls_through_to_redirect():
"""Non-localhost request with an invalid X-Muxplex-Token header falls through to redirect."""
app = _make_test_app()
client = TestClient(app, base_url="http://192.168.1.1", follow_redirects=False)
response = client.get("/protected", headers={"X-Muxplex-Token": "bad.token.value"})
# Invalid token: should NOT pass auth, falls through to redirect or 401
assert response.status_code in (307, 401)