fix: secure whatsapp bridge with automatic local auth token

This commit is contained in:
Xubin Ren
2026-04-04 14:16:46 +00:00
parent cf56d15bdf
commit 1c1eee523d
4 changed files with 182 additions and 36 deletions
+41 -10
View File
@@ -4,6 +4,7 @@ import asyncio
import json
import mimetypes
import os
import secrets
import shutil
import subprocess
from collections import OrderedDict
@@ -29,6 +30,29 @@ class WhatsAppConfig(Base):
group_policy: Literal["open", "mention"] = "open" # "open" responds to all, "mention" only when @mentioned
def _bridge_token_path() -> Path:
from nanobot.config.paths import get_runtime_subdir
return get_runtime_subdir("whatsapp-auth") / "bridge-token"
def _load_or_create_bridge_token(path: Path) -> str:
"""Load a persisted bridge token or create one on first use."""
if path.exists():
token = path.read_text(encoding="utf-8").strip()
if token:
return token
path.parent.mkdir(parents=True, exist_ok=True)
token = secrets.token_urlsafe(32)
path.write_text(token, encoding="utf-8")
try:
path.chmod(0o600)
except OSError:
pass
return token
class WhatsAppChannel(BaseChannel):
"""
WhatsApp channel that connects to a Node.js bridge.
@@ -51,6 +75,18 @@ class WhatsAppChannel(BaseChannel):
self._ws = None
self._connected = False
self._processed_message_ids: OrderedDict[str, None] = OrderedDict()
self._bridge_token: str | None = None
def _effective_bridge_token(self) -> str:
"""Resolve the bridge token, generating a local secret when needed."""
if self._bridge_token is not None:
return self._bridge_token
configured = self.config.bridge_token.strip()
if configured:
self._bridge_token = configured
else:
self._bridge_token = _load_or_create_bridge_token(_bridge_token_path())
return self._bridge_token
async def login(self, force: bool = False) -> bool:
"""
@@ -60,8 +96,6 @@ class WhatsAppChannel(BaseChannel):
authentication flow. The process blocks until the user scans the QR code
or interrupts with Ctrl+C.
"""
from nanobot.config.paths import get_runtime_subdir
try:
bridge_dir = _ensure_bridge_setup()
except RuntimeError as e:
@@ -69,9 +103,8 @@ class WhatsAppChannel(BaseChannel):
return False
env = {**os.environ}
if self.config.bridge_token:
env["BRIDGE_TOKEN"] = self.config.bridge_token
env["AUTH_DIR"] = str(get_runtime_subdir("whatsapp-auth"))
env["BRIDGE_TOKEN"] = self._effective_bridge_token()
env["AUTH_DIR"] = str(_bridge_token_path().parent)
logger.info("Starting WhatsApp bridge for QR login...")
try:
@@ -97,11 +130,9 @@ class WhatsAppChannel(BaseChannel):
try:
async with websockets.connect(bridge_url) as ws:
self._ws = ws
# Send auth token if configured
if self.config.bridge_token:
await ws.send(
json.dumps({"type": "auth", "token": self.config.bridge_token})
)
await ws.send(
json.dumps({"type": "auth", "token": self._effective_bridge_token()})
)
self._connected = True
logger.info("Connected to WhatsApp bridge")