"""Клиенты и API-ключи (#4). Ключ показывается один раз при выпуске, в базе — только sha256.""" import hashlib import secrets from sqlalchemy import select, update from db.engine import Database from db.models import Client, utcnow KEY_PREFIX = "rbx_" def hash_key(key: str) -> str: return hashlib.sha256(key.encode()).hexdigest() async def create_client(db: Database, name: str) -> tuple[Client, str]: key = KEY_PREFIX + secrets.token_urlsafe(32) client = Client(name=name, key_hash=hash_key(key), key_prefix=key[:12]) async with db.session() as session, session.begin(): session.add(client) return client, key async def authenticate(db: Database, key: str | None) -> Client | None: """Клиент по ключу, если ключ не отозван; отмечает время последнего обращения.""" if not key: return None async with db.session() as session, session.begin(): client = await session.scalar( select(Client).where(Client.key_hash == hash_key(key), Client.revoked_at.is_(None)) ) if client: client.last_access_at = utcnow() return client async def revoke(db: Database, client_id: int) -> bool: async with db.session() as session, session.begin(): result = await session.execute( update(Client).where(Client.id == client_id, Client.revoked_at.is_(None)).values(revoked_at=utcnow()) ) return result.rowcount > 0 async def list_clients(db: Database) -> list[Client]: async with db.session() as session: return list(await session.scalars(select(Client).order_by(Client.id)))