Commit Graph

18 Commits

Author SHA1 Message Date
Brian Krabach d0eb91dca7 refactor: replace fixed sleeps with deterministic polling in ws proxy tests; document CORS permissiveness 2026-03-30 21:56:05 -07:00
Brian Krabach 0e02181477 feat: add CORS middleware for cross-origin federation 2026-03-30 21:47:17 -07:00
Brian Krabach ca0eb44b88 feat: add GET /api/instance-info endpoint
Implement new public endpoint that returns device name and version without authentication required. The auth exemption was already in place from task 4.
2026-03-30 21:47:17 -07:00
Brian Krabach 17616166bc feat: kill session — DELETE endpoint, hover × button with confirmation 2026-03-30 03:35:39 -07:00
Brian Krabach 19ec5c02a5 feat: add POST /api/sessions endpoint for new session creation 2026-03-29 22:54:23 -07:00
Brian Krabach 6c72bd05cc feat: add GET/PATCH /api/settings endpoints 2026-03-29 22:47:53 -07:00
Brian Krabach 6c0bc80f21 fix(auth): protect WebSocket /terminal/ws with session cookie check
BaseHTTPMiddleware only handles HTTP scope — WebSocket connections
bypassed auth entirely. Inline check before websocket.accept() reads
the muxplex_session cookie and closes with code 4001 if invalid.
Localhost (127.0.0.1, ::1) still bypasses as intended.
2026-03-29 04:23:41 -07:00
Brian Krabach 575cebbcac fix: resolve_auth file password log prints actual path, not module name
Add `get_password_path` to top-level imports in main.py and fix the file
password resolution branch to log the actual file path instead of
`load_password.__module__` (which printed "muxplex.auth").

Also adds 4 tests covering all _resolve_auth logging paths:
- test_resolve_auth_pam_mode_logs_pam
- test_resolve_auth_env_password_logs_env
- test_resolve_auth_file_password_logs_file
- test_resolve_auth_generates_password_as_last_resort
2026-03-28 23:06:27 -07:00
Brian Krabach fd55a71958 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.
2026-03-28 22:40:36 -07:00
Brian Krabach 2ca4420536 feat: add GET /auth/logout route that clears session cookie and redirects to /login
- Add @app.get('/auth/logout') handler in muxplex/main.py after POST /login
- Creates RedirectResponse('/login', status_code=303)
- Calls response.delete_cookie('muxplex_session') to clear the auth cookie
- Add two tests in test_api.py:
  - test_logout_redirects_to_login: verifies 303 status and /login in location header
  - test_logout_clears_session_cookie: verifies Set-Cookie has muxplex_session with max-age=0
- Route is already exempt from AuthMiddleware via _AUTH_EXEMPT_PATHS in auth.py

Co-authored-by: Amplifier <amplifier@sourcegraph.com>
2026-03-28 22:30:39 -07:00
Brian Krabach b5d700214b feat: add POST /login handler with PAM and password mode auth
- Import authenticate_pam and create_session_cookie from muxplex.auth
- Import Form, Request from fastapi; RedirectResponse from starlette.responses
- Add @app.post('/login') handler that reads username and password form fields
- In PAM mode: delegates to authenticate_pam(username, password)
- In password mode: compares password to _auth_password
- On success: creates signed muxplex_session cookie (httponly, samesite=strict),
  redirects to / with 303
- On failure: redirects to /login?error=1 with 303
- Add 4 tests covering all branches (password success/failure, PAM success/failure)

Closes task-2-post-login-handler
2026-03-28 22:20:38 -07:00
Brian Krabach dd0507d8e1 fix: remove unused JSONResponse alias causing ruff F401 lint error 2026-03-28 21:56:59 -07:00
Brian Krabach ba7101292c feat: add /login stub and /auth/mode endpoint 2026-03-28 21:53:00 -07:00
Brian Krabach 88b21832f7 feat: wire auth middleware into FastAPI app
- Add _resolve_auth() with PAM → MUXPLEX_PASSWORD env → password file → auto-generate fallback chain
- MUXPLEX_AUTH=password forces password mode regardless of PAM availability
- MUXPLEX_SESSION_TTL controls session cookie TTL (default 604800 = 7 days)
- app.add_middleware(AuthMiddleware, ...) called after app creation
- Update client fixture to set a valid session cookie for test compatibility
- Add test_non_localhost_without_auth_gets_redirected integration test

Co-authored-by: Amplifier <amplifier@amplified.dev>
2026-03-28 21:47:01 -07:00
Brian Krabach 38c78fb575 fix: WebSocket proxy drops text frames — receive_bytes() silently fails on auth token
terminal.js sends the ttyd auth token as a TEXT WebSocket frame. The proxy's
client_to_ttyd() called receive_bytes() which fails silently on text frames,
swallows the exception, and exits — auth never reached ttyd, ttyd never
started streaming, resulting in permanent black screen and reconnect loop.

Fix: use receive() and dispatch on message type, handling both bytes and text.
2026-03-28 08:50:43 -07:00
Brian Krabach 82ec323aaa fix: address pre-merge review findings — stale identity strings, pyright error, fragile test path, formatting, README accuracy
Required fixes:
- main.py: update module docstring, startup comment, and FastAPI title from
  'tmux-web coordinator' / 'coordinator service' → 'muxplex' (4 locations)
- test_api.py: replace hasattr/BaseRoute pattern with isinstance(r, (APIRoute,
  APIWebSocketRoute)) to satisfy pyright (union-attr error eliminated)

Recommendations applied:
- test_api.py + test_cli.py: auto-formatted with ruff (blank-line normalisation)
- test_cli.py: replace exec(Path('muxplex/__main__.py').read_text()) with
  importlib.util.find_spec() to obtain the absolute path — no longer assumes
  pytest is invoked from a specific working directory
- README.md: fix architecture table — WebSocket proxy moved from ttyd.py
  description to main.py; ttyd.py now correctly described as lifecycle only

All 178 tests pass; python_check clean (0 errors, 0 warnings).
2026-03-28 02:47:18 -07:00
Brian Krabach a70e8f6f52 feat: add WebSocket proxy route to replace Caddy, update bell hook port to configurable SERVER_PORT 2026-03-28 02:11:24 -07:00
Brian Krabach 74b63033d7 refactor: rename coordinator → muxplex package, move frontend inside as package data 2026-03-28 02:02:50 -07:00