4 Commits

Author SHA1 Message Date
restitux 70e6f05534 frontend: add admin panel for user and permission management
Admin-only page at /admin with:
- Create user form (username, password, admin toggle)
- User list table with edit and delete actions
- Inline user editing (change password, toggle admin role)
- Inline permission editor with per-app checkboxes grouped by server
- Access guarded by checking is_admin from /api/auth/me

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 15:24:12 +00:00
restitux 6a0c97dd9e frontend: attach auth credentials to all API requests
Add Authorization Bearer header to all fetch calls (apps, stream
start). Handle 401 responses by clearing token and redirecting to
login. Pass stream_token from the stream start response through to
the WebTransport URL as a query parameter for proxy authentication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 15:24:12 +00:00
restitux 917acfdb27 frontend: add login page and auth guard
Add authentication flow to the frontend:
- authStore with token management (localStorage persistence)
- Login page with username/password form at /login
- Layout-level auth guard that redirects to /login when no valid
  session exists, validates token on load via GET /api/auth/me
- Top navigation bar showing username and admin link when applicable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 15:24:12 +00:00
restitux 0fd90e8935 backend: add single-use token auth for spawned stream proxies
Generate a random 256-bit token when spawning a proxy process, pass
it as a CLI argument, and return it to the client in the stream start
response. The proxy validates the token on WebTransport connect and
consumes it after first use, preventing replay. A wrong token attempt
also consumes the token for security. Includes 5 unit tests for token
validation logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 15:24:09 +00:00
@@ -85,8 +85,9 @@ pub async fn validate_stream_token(proxy: &Proxy, provided: &str) -> std::result
match token_guard.take() {
Some(expected) if expected == provided => Ok(()),
Some(_) => {
// Put the token back since it wasn't matched
// Actually no — the design is that any attempt consumes it for security
// Wrong token: still consumed by the `take()` above. Any validation
// attempt — correct or notinvalidates the token, so a wrong
// guess cannot be followed by a correct one.
Err("Invalid stream token".to_string())
}
None => Err("Stream token already used".to_string()),