fix: on_behalf_of in WS auth for BFF proxy, debug logging in broadcast
Some checks failed
Deploy Tracker / deploy (push) Failing after 2s

This commit is contained in:
Markov 2026-02-23 19:00:21 +01:00
parent 6a6eaada3e
commit f50182bc52
3 changed files with 47 additions and 7 deletions

View File

@ -36,6 +36,18 @@ async def init_db():
) )
session.add(admin) session.add(admin)
# Create BFF service member with TRACKER_TOKEN
bff_service = Member(
name="BFF Service",
slug="bff",
type="bridge",
role="bridge",
auth_method="token",
token="tb-tracker-dev-token",
status="offline",
)
session.add(bff_service)
# Create lobby chat # Create lobby chat
lobby = Chat( lobby = Chat(
kind="lobby", kind="lobby",
@ -44,7 +56,7 @@ async def init_db():
session.add(lobby) session.add(lobby)
await session.commit() await session.commit()
logger.info("Seed data created: admin user + lobby chat (id=%s)", lobby.id) logger.info("Seed data created: admin user, BFF service, lobby chat (id=%s)", lobby.id)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -29,7 +29,8 @@ async def websocket_endpoint(ws: WebSocket):
return return
token = auth_msg.get("token", "") token = auth_msg.get("token", "")
slug = await _authenticate(ws, token) on_behalf_of = auth_msg.get("on_behalf_of")
slug = await _authenticate(ws, token, on_behalf_of=on_behalf_of)
if not slug: if not slug:
return return
@ -77,8 +78,12 @@ async def websocket_endpoint(ws: WebSocket):
) )
async def _authenticate(ws: WebSocket, token: str) -> str | None: async def _authenticate(ws: WebSocket, token: str, on_behalf_of: str | None = None) -> str | None:
"""Authenticate by token, return slug or None.""" """Authenticate by token, return slug or None.
If on_behalf_of is set and the token belongs to a bridge member,
use on_behalf_of slug instead (BFF proxying for web users).
"""
async with async_session() as db: async with async_session() as db:
result = await db.execute( result = await db.execute(
select(Member).where(Member.token == token).options(selectinload(Member.agent_config)) select(Member).where(Member.token == token).options(selectinload(Member.agent_config))
@ -103,6 +108,26 @@ async def _authenticate(ws: WebSocket, token: str) -> str | None:
await ws.close() await ws.close()
return None return None
# BFF proxy: bridge member can act on behalf of a user
effective_slug = member.slug
effective_type = member.type
if on_behalf_of and member.type == "bridge":
# Look up the actual user
user_result = await db.execute(
select(Member).where(Member.slug == on_behalf_of)
.options(selectinload(Member.agent_config))
)
user_member = user_result.scalar_one_or_none()
if user_member:
effective_slug = user_member.slug
effective_type = user_member.type
member = user_member # use user's settings
logger.info("Bridge %s acting on behalf of %s", member.slug, effective_slug)
else:
# User not found, use a synthetic slug to avoid collisions
effective_slug = on_behalf_of
logger.info("Bridge acting on behalf of unknown user %s", effective_slug)
# Get listen modes # Get listen modes
chat_listen = "all" chat_listen = "all"
task_listen = "all" task_listen = "all"
@ -113,8 +138,8 @@ async def _authenticate(ws: WebSocket, token: str) -> str | None:
# Register connection # Register connection
client = ConnectedClient( client = ConnectedClient(
ws=ws, ws=ws,
member_slug=member.slug, member_slug=effective_slug,
member_type=member.type, member_type=effective_type,
chat_listen=chat_listen, chat_listen=chat_listen,
task_listen=task_listen, task_listen=task_listen,
) )
@ -148,7 +173,7 @@ async def _authenticate(ws: WebSocket, token: str) -> str | None:
await ws.send_json({ await ws.send_json({
"type": "auth.ok", "type": "auth.ok",
"data": { "data": {
"slug": member.slug, "slug": effective_slug,
"lobby_chat_id": str(lobby_chat.id) if lobby_chat else None, "lobby_chat_id": str(lobby_chat.id) if lobby_chat else None,
"projects": project_list, "projects": project_list,
"online": online, "online": online,

View File

@ -44,6 +44,9 @@ class ConnectionManager:
async def broadcast_message(self, project_id: str, message: dict, author_slug: str): async def broadcast_message(self, project_id: str, message: dict, author_slug: str):
"""Broadcast message.new — filtered by chat_listen.""" """Broadcast message.new — filtered by chat_listen."""
logger.info("broadcast_message: project=%s, author=%s, clients=%s", project_id, author_slug, list(self.clients.keys()))
for slug, client in list(self.clients.items()):
logger.info(" checking %s: subscribed=%s, chat_listen=%s", slug, client.subscribed_projects, client.chat_listen)
mentions = message.get("mentions", []) mentions = message.get("mentions", [])
for slug, client in list(self.clients.items()): for slug, client in list(self.clients.items()):
if slug == author_slug: if slug == author_slug: