feat: add auth subsystem with WebAuthn passkeys support
New modules: lib/auth, lib/auth_users, lib/db, lib/db_sqlite, lib/password, lib/webauthn, daemon/handlers/auth, scripts/bootstrap_auth, tests/test_auth Frontend: webui/api/auth, hoover/components/auth, pages/login, passkeys, users Updates: daemon/iface and server, lib/common and nginx, pyproject.toml deps, install script, server.py, app.js, and websocket/api clients
This commit is contained in:
+34
-1
@@ -368,8 +368,40 @@ async def _handle_ws(request: web.Request) -> web.Response:
|
||||
|
||||
On connect: sends current versions. On state change: broadcasts
|
||||
updated subsystem versions. Clients disconnect to unsubscribe.
|
||||
|
||||
Authentication: JWT access token passed via:
|
||||
1. WebSocket subprotocol header (Sec-WebSocket-Protocol: "Bearer <token>")
|
||||
2. X-Auth-Token header (nginx-injected)
|
||||
3. token query parameter (dev fallback)
|
||||
"""
|
||||
ws = web.WebSocketResponse()
|
||||
from lib.auth import validate_token
|
||||
|
||||
token_param = None
|
||||
|
||||
# Prefer subprotocol header (client JS sends "Bearer <token>")
|
||||
subprotocols = request.get_subprotocols()
|
||||
for proto in subprotocols or []:
|
||||
if proto and proto.startswith("Bearer "):
|
||||
token_param = proto[7:]
|
||||
break
|
||||
|
||||
if token_param is None:
|
||||
token_param = request.headers.get("X-Auth-Token")
|
||||
|
||||
if token_param is None:
|
||||
token_param = request.query.get("token")
|
||||
|
||||
if token_param is None:
|
||||
return web.json_response(
|
||||
{"ok": False, "error": "authentication required"}, status=401
|
||||
)
|
||||
|
||||
payload = validate_token(token_param, token_type="access")
|
||||
if payload is None:
|
||||
return web.json_response({"ok": False, "error": "unauthorized"}, status=401)
|
||||
|
||||
# Negotiate the subprotocol the client sent
|
||||
ws = web.WebSocketResponse(protocols=request.get_subprotocols())
|
||||
await ws.prepare(request)
|
||||
_ws_subscribers.add(ws)
|
||||
|
||||
@@ -501,6 +533,7 @@ def _register_routes() -> None:
|
||||
"""
|
||||
from daemon.handlers import (
|
||||
acme, # noqa: F401
|
||||
auth, # noqa: F401
|
||||
dnsmasq, # noqa: F401
|
||||
firewall, # noqa: F401
|
||||
logs, # noqa: F401
|
||||
|
||||
Reference in New Issue
Block a user