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", )