From a3c92ae8d5ada80270b1e4d10de8c39cc981dfe6 Mon Sep 17 00:00:00 2001
From: patrick-motsch
Date: Tue, 17 Feb 2026 19:58:01 +0100
Subject: [PATCH] fix: load system bot credentials from DB and pass to browser
bot for authenticated join
Co-authored-by: Cursor
---
.../features/teamsbot/routeFeatureTeamsbot.py | 43 +++++++++++++------
modules/features/teamsbot/service.py | 15 ++++---
2 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/modules/features/teamsbot/routeFeatureTeamsbot.py b/modules/features/teamsbot/routeFeatureTeamsbot.py
index 34dfd7f3..b185528a 100644
--- a/modules/features/teamsbot/routeFeatureTeamsbot.py
+++ b/modules/features/teamsbot/routeFeatureTeamsbot.py
@@ -195,20 +195,41 @@ async def startSession(
userId = str(context.user.id)
effectiveConfig = _getEffectiveConfig(instanceId, userId, interface)
- # Determine effective join mode and bot name.
- # NOTE: Authentication is currently disabled. The bot always joins as an anonymous
- # guest with the system bot's display name. See Teamsbot-Auth-Join-Learnings.md.
- # Credentials are NOT sent to the browser bot.
+ # Determine effective join mode, bot name, and credentials
joinMode = body.joinMode or TeamsbotJoinMode.ANONYMOUS
effectiveBotName = body.botName
+ botAccountEmail = None
+ botAccountPassword = None
- # If a system bot exists, use its display name as the bot name (e.g. "Nyla Larsson")
+ # Load system bot from DB (try mandate-specific first, then any active bot)
systemBot = interface.getActiveSystemBot(mandateId)
+ if not systemBot:
+ from .datamodelTeamsbot import TeamsbotSystemBot
+ allBots = interface.db.getRecordset(TeamsbotSystemBot, recordFilter={"isActive": True})
+ if allBots:
+ systemBot = allBots[0]
+ logger.info(f"No mandate-specific system bot, using fallback: {systemBot.get('name')} ({systemBot.get('email')})")
+
if systemBot:
if not effectiveBotName:
effectiveBotName = systemBot.get("name") or effectiveConfig.botName
- logger.info(f"System bot found: {systemBot.get('name')} ({systemBot.get('email')}), using name: {effectiveBotName}")
-
+ logger.info(f"System bot found: {systemBot.get('name')} ({systemBot.get('email')})")
+
+ # Load and decrypt credentials for authenticated join
+ botAccountEmail = systemBot.get("email")
+ encryptedPwd = systemBot.get("encryptedPassword")
+ if botAccountEmail and encryptedPwd:
+ try:
+ from modules.shared.configuration import decryptValue
+ botAccountPassword = decryptValue(encryptedPwd, userId=str(context.user.id), keyName="systemBotPassword")
+ logger.info(f"System bot credentials loaded and decrypted for: {botAccountEmail}")
+ except Exception as e:
+ logger.warning(f"Could not decrypt system bot password: {e} — falling back to anonymous join")
+ botAccountEmail = None
+ botAccountPassword = None
+ else:
+ logger.info("No system bot found in DB — using anonymous join")
+
if not effectiveBotName:
effectiveBotName = effectiveConfig.botName
@@ -216,17 +237,15 @@ async def startSession(
if effectiveBotName != (body.botName or config.botName):
interface.updateSession(sessionId, {"botName": effectiveBotName})
- # Build session config — no credentials sent (auth disabled)
+ # Build session config
sessionConfig = effectiveConfig.model_copy(update={
- "botAccountEmail": None,
- "botAccountPassword": None,
"botName": effectiveBotName,
})
- # Start the bot in background (join meeting via bridge)
+ # Start the bot in background — pass credentials separately (not in config)
service = TeamsbotService(context.user, mandateId, instanceId, sessionConfig)
asyncio.create_task(
- service.joinMeeting(sessionId, cleanMeetingUrl, body.connectionId, gatewayBaseUrl)
+ service.joinMeeting(sessionId, cleanMeetingUrl, body.connectionId, gatewayBaseUrl, botAccountEmail, botAccountPassword)
)
logger.info(f"Teamsbot session {sessionId} created for instance {instanceId}")
diff --git a/modules/features/teamsbot/service.py b/modules/features/teamsbot/service.py
index eddec193..164e8703 100644
--- a/modules/features/teamsbot/service.py
+++ b/modules/features/teamsbot/service.py
@@ -90,14 +90,16 @@ class TeamsbotService:
meetingLink: str,
connectionId: Optional[str] = None,
gatewayBaseUrl: str = "",
+ botAccountEmail: Optional[str] = None,
+ botAccountPassword: Optional[str] = None,
):
"""Send join command to the Browser Bot service.
The browser bot will:
- 1. Launch a headless browser
+ 1. Launch browser (headful if credentials provided, headless otherwise)
2. Navigate to Teams web app
- 3. Join the meeting as anonymous guest
- 4. Enable captions and start scraping
+ 3. Authenticate if credentials provided, otherwise join as anonymous guest
+ 4. Enable captions/audio capture and start scraping
5. Connect back via WebSocket to send transcripts
"""
from . import interfaceFeatureTeamsbot as interfaceDb
@@ -123,6 +125,9 @@ class TeamsbotService:
gatewayHost = gatewayBaseUrl.replace("https://", "").replace("http://", "").rstrip("/")
fullGatewayWsUrl = f"{wsScheme}://{gatewayHost}/api/teamsbot/{self.instanceId}/bot/ws/{sessionId}"
+ hasAuth = bool(botAccountEmail and botAccountPassword)
+ logger.info(f"Joining meeting for session {sessionId}: auth={hasAuth}, email={botAccountEmail or 'N/A'}, transferMode={self.config.transferMode}")
+
result = await self.browserBotConnector.joinMeeting(
sessionId=sessionId,
meetingUrl=meetingLink,
@@ -130,8 +135,8 @@ class TeamsbotService:
instanceId=self.instanceId,
gatewayWsUrl=fullGatewayWsUrl,
language=self.config.language,
- botAccountEmail=self.config.botAccountEmail if hasattr(self.config, 'botAccountEmail') else None,
- botAccountPassword=self.config.botAccountPassword if hasattr(self.config, 'botAccountPassword') else None,
+ botAccountEmail=botAccountEmail,
+ botAccountPassword=botAccountPassword,
transferMode=self.config.transferMode if hasattr(self.config, 'transferMode') else "auto",
)