diff --git a/muxplex/main.py b/muxplex/main.py index fb2a0da..12070d2 100644 --- a/muxplex/main.py +++ b/muxplex/main.py @@ -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( - "" - "

muxplex login

" - "
" - "" - "" - "
" - "" + """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( + "", f"" ) + 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") diff --git a/muxplex/tests/test_api.py b/muxplex/tests/test_api.py index 189a6df..0a543ea 100644 --- a/muxplex/tests/test_api.py +++ b/muxplex/tests/test_api.py @@ -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 # ---------------------------------------------------------------------------