aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api/revocation.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/revocation.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/revocation.py13
1 files changed, 12 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/revocation.py b/packages/meshbay-hub/src/meshbay_hub/api/revocation.py
index 60b0c88..1f1f5c5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/revocation.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/revocation.py
@@ -56,6 +56,11 @@ _connected_nodes: dict[str, WebSocket] = {} # node_id → websocket
_node_groups: dict[str, list[str]] = {} # node_id → [group_id, ...]
_punch_events: dict[str, asyncio.Event] = {} # node_id → signaling event
+# How long an unauthenticated socket may stay open before saying who it is. The
+# node sends its auth message the moment the connection opens; anything that
+# has not spoken by now is holding a socket and a task for nothing.
+NODE_WS_AUTH_TIMEOUT = 10.0
+
def is_node_connected(node_id: str) -> bool:
return node_id in _connected_nodes
@@ -336,7 +341,13 @@ async def node_websocket(ws: WebSocket):
try:
# Auth: expect {"type": "auth", "token": "<jwt>", "node_id": "..."}
- raw = await ws.receive_text()
+ # Bounded: the socket is accepted before anyone is authenticated, so an
+ # unbounded wait is a connection any stranger can hold open for ever.
+ try:
+ raw = await asyncio.wait_for(ws.receive_text(), NODE_WS_AUTH_TIMEOUT)
+ except TimeoutError:
+ await _reject(ws, "Authentication timed out", 4001)
+ return
msg = json.loads(raw)
if msg.get("type") != "auth" or "token" not in msg:
await _reject(ws, "Send auth first", 4001)