feature: API-ключи клиентов и журнал использования в Postgres (#4, #17)

- Postgres в compose; пакет db (SQLAlchemy async): clients, windows, requests.
- Все эндпоинты, кроме /health, — только с ключом в X-API-Key, иначе 401; без
  выпущенных ключей доступа нет. В базе — только sha256 ключа.
- Задача и окно сессии привязаны к клиенту: чужой ключ их не видит.
- Воркер пишет журнал: окно (открыто, последнее обращение, закрыто) и задачу
  (в очереди, начало, конец, итог, данные без секретов). Сбой записи не роняет задачу.
- python -m db.cli create|list|revoke|stats; make key-create/key-list/key-revoke/stats.
- smoke и loadtest с ключом.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-13 21:48:48 +04:00
1 parent 9dc2c0b619
commit 1a4bd8d672
34 files changed
+857 -81

No files matched your search

+15 -6
View File
@@ -1,7 +1,9 @@
"""Проверка запущенного стенда (make up): api, очередь, воркер с Chrome, удержание окна.
make smoke # http://localhost:8000
make smoke API_URL=https://app.robotex.sokolab.xyz
make smoke API_KEY=rbx_... # http://localhost:8000
make smoke API_KEY=rbx_... API_URL=https://app.robotex.sokolab.xyz
Ключ: make key-create name=smoke.
"""
import sys
import time
@@ -32,8 +34,13 @@ def windows(client: httpx.Client) -> int:
return sum(w["windows"] for w in client.get("/health").json()["workers"])
def main(base_url: str) -> None:
client = httpx.Client(base_url=base_url, timeout=300)
def main(base_url: str, api_key: str) -> None:
headers = {"X-API-Key": api_key}
client = httpx.Client(base_url=base_url, timeout=300, headers=headers)
print("auth")
resp = httpx.post(f"{base_url}/v1/solve", json={"url": URL, "actions": []}, timeout=30)
check(resp.status_code == 401, f"no key -> {resp.status_code}")
print("health")
health = client.get("/health").json()
@@ -46,7 +53,7 @@ def main(base_url: str) -> None:
check(data["actions"][0]["result"] == "Example Domain", "title = Example Domain")
print("v2 window held between tasks")
v2 = httpx.Client(base_url=base_url, timeout=60) # своя сессия
v2 = httpx.Client(base_url=base_url, timeout=60, headers=headers) # своя сессия
started = time.monotonic()
created = v2.post("/v2/tasks", json={"url": URL, "actions": READ_TITLE, "browser_idle_timeout": 2})
check(created.status_code == 202, f"POST /v2/tasks -> {created.status_code}")
@@ -73,4 +80,6 @@ def main(base_url: str) -> None:
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8000")
if len(sys.argv) < 3 or not sys.argv[2]:
sys.exit("usage: smoke.py <api url> <api key> (make smoke API_KEY=...)")
main(sys.argv[1], sys.argv[2])