feat(bot): support random token in signature verification

- Enhanced signature verification by adding support for a `random` token included in webhook headers.
- Introduced logging to display signature variants for debugging purposes.
- Improved webhook handling to process new `X-Nextcloud-Talk-Random` header.
This commit is contained in:
Harald Hoyer 2026-02-03 16:26:37 +01:00
parent 33937ab115
commit 77cf4a0aed

View file

@ -56,25 +56,37 @@ conversations: dict[str, list[tuple[datetime, str, str]]] = {}
MAX_HISTORY = 10 # Keep last N exchanges per user
def verify_signature(body: bytes, signature: str) -> bool:
def verify_signature(body: bytes, signature: str, random: Optional[str] = None) -> bool:
"""Verify Nextcloud webhook signature."""
if not BOT_SECRET:
log.warning("No bot secret configured, skipping signature verification")
return True
expected = hmac.new(
BOT_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
# Nextcloud sends: sha256=<hex>
if signature.startswith("sha256="):
signature = signature[7:]
log.info(f"Signature verification: secret_len={len(BOT_SECRET)}, expected={expected[:16]}..., received={signature[:16]}...")
# Try different signature computation methods
# Method 1: Just body
expected1 = hmac.new(BOT_SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
# Method 2: random + body (if random header present)
if random:
expected2 = hmac.new(BOT_SECRET.encode(), (random.encode() + body), hashlib.sha256).hexdigest()
else:
expected2 = None
log.info(f"Signature verification: received={signature[:16]}...")
log.info(f" Method 1 (body only): {expected1[:16]}...")
if expected2:
log.info(f" Method 2 (random+body): {expected2[:16]}...")
if hmac.compare_digest(expected1, signature):
return True
if expected2 and hmac.compare_digest(expected2, signature):
return True
return False
def build_prompt(user_id: str, message: str) -> str:
@ -182,12 +194,16 @@ async def send_reply(conversation_token: str, message: str, reply_to: int = None
async def handle_webhook(
request: Request,
x_nextcloud_talk_signature: Optional[str] = Header(None, alias="X-Nextcloud-Talk-Signature"),
x_nextcloud_talk_random: Optional[str] = Header(None, alias="X-Nextcloud-Talk-Random"),
):
"""Handle incoming webhook from Nextcloud Talk."""
body = await request.body()
log.info(f"Headers: signature={x_nextcloud_talk_signature}, random={x_nextcloud_talk_random}")
log.info(f"Body (first 200): {body[:200]}")
# Verify signature
if x_nextcloud_talk_signature and not verify_signature(body, x_nextcloud_talk_signature):
if x_nextcloud_talk_signature and not verify_signature(body, x_nextcloud_talk_signature, x_nextcloud_talk_random):
log.warning("Invalid webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")