Files
esp32-server/app/security/__init__.py
kunthawat e96498b67e fix: resolve fresh-eyes review criticals + majors on the audio path
- C1: stop feeding the unconsumed barge-in TTSQueue in the reply path (deadlocked after 64 chunks on a real long reply)
- C2: encode reply Opus at the TTS/codec sample rate (24 kHz), not hardcoded 16 kHz; encoder now rate-generic
- M3: bound the mic PCM buffer to the 60 s max utterance
- M4: tts_sentence_start per speakable sentence, not per LLM token
- Minor: send tts.stop on a failed turn; constant-time token compare
- Sec: untrack + gitignore config.yaml; add config.example.yaml with placeholder secrets
- Tests: 23 pass (2 new regressions: long-reply deadlock, rate-generic encoder)
2026-10-03 21:55:32 +07:00

38 lines
1.2 KiB
Python

"""Device authentication (REQ-009).
Handshake carries Device-Id + Authorization. A device is allowed only if its
device_id is on the allowlist AND its token hashes match. No device = no
session. This is what keeps the bridge the *only* internet-exposed surface
(MUST-NOT-002): everything behind it is LAN/localhost.
"""
from __future__ import annotations
import hashlib
import hmac
class AuthError(Exception):
pass
class DeviceAuth:
def __init__(self, devices: dict[str, str], *, enabled: bool = True) -> None:
# device_id -> token_hash
self._devices = devices
self.enabled = enabled
@staticmethod
def hash_token(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
def authenticate(self, device_id: str, token: str) -> bool:
if not self.enabled:
return True
expected = self._devices.get(device_id)
if expected is None:
raise AuthError(f"device not allowed: {device_id}")
got = self.hash_token(token)
if not hmac.compare_digest(got.encode(), expected.encode()):
raise AuthError(f"bad token for device: {device_id}")
return True