Node page Discord picker: org's servers + voice channels, not raw IDs (#213 item 3) #220

Merged
logan merged 4 commits from feat/213-node-join-picker into main 2026-10-04 22:05:50 -04:00
2 changed files with 17 additions and 17 deletions
Showing only changes of commit 2457d2d734 - Show all commits
+4 -4
View File
@@ -20,9 +20,9 @@ from app.internal.auth import (
from app.routers import nodes, systems, calls, upload, tokens, incidents, alerts, admin, trips, places, links, users
from app.routers import enrollment, media, org, waitlist, telemetry, replay, traffic, cameras
from app.routers import discord as discord_router
from app.routers import discord_web
from app.internal import dynsec
from app.internal import firestore as fstore
from app.routers import discord_web
async def _clear_legacy_token_flags():
@@ -156,12 +156,12 @@ app.include_router(waitlist.router) # public — no auth, source-IP rate limi
# public by necessity — an <audio src> can't send a bearer token, so the
# short-lived HMAC in the URL is the credential. Checked inline in media.py.
app.include_router(media.router)
# auth is per-endpoint: bot routes take the service key (or a platform admin);
# the token-fetch route takes the calling node's own API key.
app.include_router(discord_router.router)
# auth is per-endpoint: directory push takes the service key; the node-page
# picker takes a Firebase token scoped to the node's org (server-26#213).
app.include_router(discord_web.router)
# auth is per-endpoint: bot routes take the service key (or a platform admin);
# the token-fetch route takes the calling node's own API key.
app.include_router(discord_router.router)
# NOTE: there used to be an app.routers.mqtt_auth router here (an HTTP
# backend for the mosquitto-go-auth plugin). That plugin's upstream project
# is archived (no CVE patches) and was rejected for an internet-facing
@@ -30,6 +30,19 @@ class C2Client:
return {"guild_id": str(guild_id), "org_id": None, "systems": [], "nodes": [],
"streams": [], "bots": [], "error": True}
async def put_guild_directory(self, guild_id: int | str, payload: dict) -> bool:
"""This server's name + voice channels, for the web node-page picker
(server-26#213). Names only — no members, no messages, no tokens."""
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.put(f"{self.base}/discord/guilds/{guild_id}/directory",
json=payload, headers=self._headers())
r.raise_for_status()
return True
except Exception as e:
logger.error(f"C2 put_guild_directory({guild_id}) failed: {e}")
return False
async def start_stream(self, payload: dict) -> tuple[bool, dict]:
"""Returns (ok, body). On a refusal body carries detail/code/invite."""
try:
@@ -68,19 +81,6 @@ class C2Client:
logger.error(f"C2 stop_guild failed: {e}")
return None
async def put_guild_directory(self, guild_id: int | str, payload: dict) -> bool:
"""This server's name + voice channels, for the web node-page picker
(server-26#213). Names only — no members, no messages, no tokens."""
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.put(f"{self.base}/discord/guilds/{guild_id}/directory",
json=payload, headers=self._headers())
r.raise_for_status()
return True
except Exception as e:
logger.error(f"C2 put_guild_directory({guild_id}) failed: {e}")
return False
# ------------------------------------------------------------------
# Trips
# ------------------------------------------------------------------