"""Token QR bàn — ký `itsdangerous`, payload (tenant_id, table_id). Token KHÔNG có hạn dùng, cố ý: QR in ra dán lên bàn là vật lý vĩnh viễn, "hết hạn" thuộc về guest_session (TTL 24h, bảng `guest_sessions`), không thuộc về token. Thu hồi QR = đổi POOLCOACH_SECRET (mọi token cũ chết cùng lúc). Secret từ env `POOLCOACH_SECRET`; không set → default dev + warning lúc boot (main.py in). Default dev để chạy local không cần cấu hình gì — nhưng token ký bằng nó KHÔNG có giá trị bảo mật. """ from __future__ import annotations import os from itsdangerous import BadSignature, URLSafeSerializer DEV_SECRET = "poolcoach-dev-secret-khong-dung-cho-prod" _SALT = "poolcoach-table-qr" def secret_is_default() -> bool: return not os.environ.get("POOLCOACH_SECRET") def _serializer() -> URLSafeSerializer: # Đọc env MỖI LẦN thay vì cache lúc import: test đổi secret bằng # monkeypatch.setenv phải có tác dụng ngay. secret = os.environ.get("POOLCOACH_SECRET") or DEV_SECRET return URLSafeSerializer(secret, salt=_SALT) def make_table_token(tenant_id: int, table_id: int) -> str: return _serializer().dumps([tenant_id, table_id]) def read_table_token(token: str) -> tuple[int, int] | None: """Trả (tenant_id, table_id), hoặc None nếu chữ ký sai / payload lạ.""" try: data = _serializer().loads(token) except BadSignature: return None # Chữ ký đúng nhưng payload không đúng shape vẫn là token rác (ví dụ # token ký cho mục đích khác cùng secret) — từ chối nốt. if (not isinstance(data, (list, tuple)) or len(data) != 2 or not all(isinstance(v, int) for v in data)): return None return int(data[0]), int(data[1])