114 lines
3.7 KiB
Python
114 lines
3.7 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel
|
||
from sqlalchemy import select
|
||
|
||
from app.core.database import async_session
|
||
from app.models.api_key import ApiKeyModel
|
||
from app.api.deps import require_master
|
||
|
||
# Все операции с ключами доступны только мастер-ключу.
|
||
router = APIRouter(dependencies=[Depends(require_master)])
|
||
|
||
|
||
class KeyActionRequest(BaseModel):
|
||
"""Тело запроса для операций с ключом (чтобы токен не летел в URL)."""
|
||
|
||
key: str
|
||
|
||
|
||
async def _find_key_by_secret_or_public_id(
|
||
session, key_or_id: str
|
||
) -> ApiKeyModel | None:
|
||
result = await session.execute(
|
||
select(ApiKeyModel).where(ApiKeyModel.key == key_or_id)
|
||
)
|
||
api_key = result.scalar_one_or_none()
|
||
if api_key:
|
||
return api_key
|
||
|
||
result = await session.execute(select(ApiKeyModel))
|
||
for candidate in result.scalars().all():
|
||
if candidate.public_id == key_or_id:
|
||
return candidate
|
||
|
||
return None
|
||
|
||
|
||
@router.get("")
|
||
async def list_keys():
|
||
"""
|
||
Список всех гостевых ключей.
|
||
|
||
В ответе поле `key` содержит публичный идентификатор, а не сам секрет.
|
||
Это сохраняет совместимость с текущим UI и не раскрывает токены повторно.
|
||
"""
|
||
async with async_session() as session:
|
||
result = await session.execute(select(ApiKeyModel))
|
||
keys = result.scalars().all()
|
||
|
||
return [
|
||
{
|
||
"key": k.public_id,
|
||
"key_id": k.public_id,
|
||
"display_key": k.preview,
|
||
"name": k.name,
|
||
"is_admin": k.is_admin,
|
||
"active": k.active,
|
||
"created_at": k.created_at,
|
||
}
|
||
for k in keys
|
||
]
|
||
|
||
|
||
@router.post("")
|
||
async def create_key(name: str, is_admin: bool = False):
|
||
"""Создать гостевой ключ. Возвращает сгенерированный токен."""
|
||
new_key = ApiKeyModel(
|
||
key=ApiKeyModel.generate_key(),
|
||
name=name,
|
||
is_admin=is_admin,
|
||
)
|
||
|
||
async with async_session() as session:
|
||
session.add(new_key)
|
||
await session.commit()
|
||
|
||
return {
|
||
"key": new_key.key,
|
||
"key_id": new_key.public_id,
|
||
"display_key": new_key.preview,
|
||
"name": new_key.name,
|
||
"is_admin": new_key.is_admin,
|
||
"message": "Сохраните ключ -- он больше не будет показан полностью",
|
||
}
|
||
|
||
|
||
@router.post("/revoke")
|
||
async def revoke_key(body: KeyActionRequest):
|
||
"""Деактивировать (отозвать) гостевой ключ. Ключ передаётся в body, не в URL."""
|
||
async with async_session() as session:
|
||
api_key = await _find_key_by_secret_or_public_id(session, body.key)
|
||
if not api_key:
|
||
raise HTTPException(status_code=404, detail="Ключ не найден")
|
||
|
||
api_key.active = False
|
||
session.add(api_key)
|
||
await session.commit()
|
||
|
||
return {"status": "revoked", "name": api_key.name, "key_id": api_key.public_id}
|
||
|
||
|
||
@router.post("/activate")
|
||
async def activate_key(body: KeyActionRequest):
|
||
"""Повторно активировать ключ. Ключ передаётся в body, не в URL."""
|
||
async with async_session() as session:
|
||
api_key = await _find_key_by_secret_or_public_id(session, body.key)
|
||
if not api_key:
|
||
raise HTTPException(status_code=404, detail="Ключ не найден")
|
||
|
||
api_key.active = True
|
||
session.add(api_key)
|
||
await session.commit()
|
||
|
||
return {"status": "activated", "name": api_key.name, "key_id": api_key.public_id}
|