mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 09:14:58 +03:00
Бренд, API-схема, TOTP issuer, письма, тексты бота уведомлений, загрузчик виджета (window.ChatballsChat, типы postMessage), ключи localStorage, id/параметры виджета помощи, health-service, README, скрипты, демо (пароль Chatballs-Demo-2026). Макеты в design/baseline не тронуты — это файлы владельца. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
50 lines
1.5 KiB
Python
50 lines
1.5 KiB
Python
import base64
|
|
import hashlib
|
|
import hmac
|
|
import os
|
|
import struct
|
|
import time
|
|
|
|
from hub_platform.identity.models import HumanUser
|
|
|
|
TOTP_SESSION_KEY = "identity_pending_totp_user_id"
|
|
TOTP_ISSUER = "Chatballs"
|
|
TOTP_PERIOD_SECONDS = 30
|
|
|
|
|
|
def _generate_totp_secret() -> str:
|
|
return base64.b32encode(os.urandom(20)).decode("ascii").rstrip("=")
|
|
|
|
|
|
def _decode_totp_secret(secret: str) -> bytes:
|
|
normalized = secret.strip().replace(" ", "").upper()
|
|
padding = "=" * ((8 - len(normalized) % 8) % 8)
|
|
return base64.b32decode(normalized + padding)
|
|
|
|
|
|
def _totp_code(secret: str, for_time: int | None = None) -> str:
|
|
timestamp = int(time.time() if for_time is None else for_time)
|
|
counter = timestamp // TOTP_PERIOD_SECONDS
|
|
digest = hmac.new(_decode_totp_secret(secret), struct.pack(">Q", counter), hashlib.sha1).digest()
|
|
offset = digest[-1] & 0x0F
|
|
code = struct.unpack(">I", digest[offset : offset + 4])[0] & 0x7FFFFFFF
|
|
return f"{code % 1_000_000:06d}"
|
|
|
|
|
|
def _verify_totp(secret: str, code: str) -> bool:
|
|
normalized = "".join(character for character in code if character.isdigit())
|
|
if len(normalized) != 6:
|
|
return False
|
|
now = int(time.time())
|
|
return any(
|
|
hmac.compare_digest(_totp_code(secret, now + (offset * TOTP_PERIOD_SECONDS)), normalized)
|
|
for offset in (-1, 0, 1)
|
|
)
|
|
|
|
|
|
def _ensure_totp_secret(user: HumanUser) -> str:
|
|
if not user.totp_secret:
|
|
user.totp_secret = _generate_totp_secret()
|
|
user.save(update_fields=["totp_secret"])
|
|
return user.totp_secret
|