feat: serve branded login.html with injected window.MUXPLEX_AUTH

Replace login_page() stub with handler that:
- Reads login.html from _FRONTEND_DIR
- Determines username (PAM mode: current user, else empty string)
- Builds mode_data with json.dumps({mode, user})
- Injects <script>window.MUXPLEX_AUTH = {...};</script> before </head>
- Returns HTMLResponse(html)

Move _FRONTEND_DIR definition above routes so login_page() can reference it.
Keep app.mount() at bottom and /auth/mode endpoint.

Add test: test_get_login_injects_muxplex_auth
  - Asserts status 200, MUXPLEX_AUTH in text, '"mode"' in text

All 45 tests pass.
This commit is contained in:
Brian Krabach
2026-03-28 22:40:36 -07:00
parent cf79d9fc9f
commit fd55a71958
2 changed files with 23 additions and 10 deletions
+15 -10
View File
@@ -9,6 +9,7 @@ Background poll loop reconciles tmux session state every POLL_INTERVAL seconds.
import asyncio
import contextlib
import json
import logging
import os
import pathlib
@@ -266,6 +267,13 @@ class HeartbeatPayload(BaseModel):
last_interaction_at: float
# ---------------------------------------------------------------------------
# Frontend directory
# ---------------------------------------------------------------------------
_FRONTEND_DIR = pathlib.Path(__file__).parent / "frontend"
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@@ -476,16 +484,14 @@ async def terminal_ws_proxy(websocket: WebSocket) -> None:
@app.get("/login", response_class=HTMLResponse)
async def login_page():
"""Stub login page — replaced in Phase 2 with branded login.html."""
return HTMLResponse(
"<html><body>"
"<h1>muxplex login</h1>"
"<form method='POST' action='/login'>"
"<input name='password' type='password' placeholder='Password' autocomplete='current-password'>"
"<button type='submit'>Login</button>"
"</form>"
"</body></html>"
"""Serve branded login.html with injected window.MUXPLEX_AUTH containing auth mode and username."""
html = (_FRONTEND_DIR / "login.html").read_text()
username = pwd.getpwuid(os.getuid()).pw_name if _auth_mode == "pam" else ""
mode_data = json.dumps({"mode": _auth_mode, "user": username})
html = html.replace(
"</head>", f"<script>window.MUXPLEX_AUTH = {mode_data};</script></head>"
)
return HTMLResponse(html)
@app.post("/login")
@@ -545,5 +551,4 @@ async def auth_mode_endpoint():
# Static file serving — MUST come after all API routes (first-match-wins)
# ---------------------------------------------------------------------------
_FRONTEND_DIR = pathlib.Path(__file__).parent / "frontend"
app.mount("/", StaticFiles(directory=str(_FRONTEND_DIR), html=True), name="frontend")
+8
View File
@@ -707,6 +707,14 @@ def test_get_auth_mode_returns_json(client):
assert data["mode"] in ("pam", "password")
def test_get_login_injects_muxplex_auth(client):
"""GET /login returns 200 with MUXPLEX_AUTH injected into HTML."""
response = client.get("/login")
assert response.status_code == 200
assert "MUXPLEX_AUTH" in response.text
assert '"mode"' in response.text
# ---------------------------------------------------------------------------
# POST /login
# ---------------------------------------------------------------------------