Dashboard authenticates: token prompt + bearer on REST/WS/recordings
The UI never sent the bearer token, so with API_TOKEN set every data call 401'd and /ws/events was rejected pre-accept (the 403s in the uvicorn log). The API client now keeps the token in localStorage, attaches Authorization to every request, prompts once on a 401 and retries, and appends ?token= to the WebSocket connect — the page's reconnect loop picks the token up after the first prompt. require_token also accepts a ?token= query parameter (same convention as the WebSocket) because <audio> elements fetching recordings can't set headers; recordingUrl() rides the token there. The dashboard header's status call moved to a new authenticated GET /api/v1/status — its old source was the JSON root endpoint that the dashboard itself replaced at /. Two new auth tests (query-param accepted / wrong query-param 401); dashboard rebuilt. Verified live: WS rejected without token and connected with ?token=, status 200, ?token=wrong 401. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
29
main.py
29
main.py
@@ -261,9 +261,13 @@ app = FastAPI(
|
||||
# call_history must register before calls: both live under /api/v1/calls and
|
||||
# calls' GET /{call_id} would otherwise capture the literal path "history".
|
||||
_auth = [Depends(require_token)]
|
||||
app.include_router(call_history.router, prefix="/api/v1/calls", tags=["Call History"], dependencies=_auth)
|
||||
app.include_router(
|
||||
call_history.router, prefix="/api/v1/calls", tags=["Call History"], dependencies=_auth
|
||||
)
|
||||
app.include_router(calls.router, prefix="/api/v1/calls", tags=["Calls"], dependencies=_auth)
|
||||
app.include_router(call_flows.router, prefix="/api/v1/call-flows", tags=["Call Flows"], dependencies=_auth)
|
||||
app.include_router(
|
||||
call_flows.router, prefix="/api/v1/call-flows", tags=["Call Flows"], dependencies=_auth
|
||||
)
|
||||
app.include_router(devices.router, prefix="/api/v1/devices", tags=["Devices"], dependencies=_auth)
|
||||
app.include_router(routing.router, prefix="/api/v1/routing", tags=["Routing"], dependencies=_auth)
|
||||
# WebSocket endpoints check the token themselves (query param or header)
|
||||
@@ -273,6 +277,27 @@ app.include_router(websocket.router, prefix="/ws", tags=["WebSocket"])
|
||||
app.mount("/mcp", mcp_http_app)
|
||||
|
||||
|
||||
@app.get("/api/v1/status", tags=["System"], dependencies=_auth)
|
||||
async def api_status():
|
||||
"""Gateway status summary for the dashboard header."""
|
||||
gateway = getattr(app.state, "gateway", None)
|
||||
if gateway:
|
||||
status = await gateway.status()
|
||||
return {
|
||||
"name": "Hold Slayer Gateway",
|
||||
"version": "0.1.0",
|
||||
"status": "running",
|
||||
"uptime": status["uptime"],
|
||||
"active_calls": status["active_calls"],
|
||||
"trunk": status["trunk"],
|
||||
}
|
||||
return {
|
||||
"name": "Hold Slayer Gateway",
|
||||
"version": "0.1.0",
|
||||
"status": "starting",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health", tags=["System"])
|
||||
async def health():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user