feat: вход, роли и аудит действий (lct-23)
Самое крупное расхождение с ТЗ: входа не было вовсе, экраны открывались ссылкой с номером занятия, и пускало знание адреса. - Таблицы users и audit_log, миграция. Пароль argon2, сессия — подписанная cookie; роль на сокетах читается из той же cookie в момент рукопожатия, отдельного протокола авторизации в канале нет. - Разграничение: control — преподавателю, observe — преподавателю и админу, call и station — обучающемуся и преподавателю. Отказ приходит событием error с кодом forbidden. - Обучающийся не видит чужого: история подменяет фильтр на его собственный идентификатор, разбор и профиль сверяют trainee_id. ТЗ запрещает доступ к чужим результатам, а не только к чужим экранам. - Администратору закрыта правка оценок — ТЗ запрещает это прямо. - make users заводит по записи на роль и печатает случайные пароли один раз: зашитый в репозиторий admin/admin пережил бы сдачу. - Экран входа и проверка роли на каждом маршруте фронта. Наши инструменты не сломались: make lesson и тесты входят через dev-token за флагом dev_auth_bypass, на стенде точка отвечает 404 — выключенной функции не должно быть видно вовсе. У тестов появился conftest.py. Role уехала в домен и в generated.ts через EventCatalog.principal: иначе фронт переписывал бы список ролей руками. 181 тест зелёный (14 новых), make typecheck чистый.
This commit is contained in:
parent
4c2bbfd021
commit
d55c8bc8cd
30 changed files with 984 additions and 21 deletions
|
|
@ -11,6 +11,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
import websockets
|
||||
|
|
@ -19,9 +20,35 @@ BACKEND = "ws://localhost:8000"
|
|||
FRONTEND = "http://localhost:5173"
|
||||
|
||||
|
||||
def dev_cookie() -> str:
|
||||
"""Вход для служебного скрипта.
|
||||
|
||||
Сокеты закрыты ролями (lct-23), а `make lesson` — наш инструмент, а не
|
||||
пользователь. Точка `dev-token` живёт за флагом `DEV_AUTH_BYPASS` и на
|
||||
рабочем стенде отвечает 404 — тогда скрипт честно скажет, что занятие
|
||||
запускается с пульта преподавателя.
|
||||
"""
|
||||
request = urllib.request.Request(
|
||||
f"{BACKEND.replace('ws://', 'http://')}/api/auth/dev-token", method="POST"
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=5) as response:
|
||||
return response.headers.get("set-cookie", "").split(";")[0]
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 404:
|
||||
raise SystemExit(
|
||||
"вход выключен: на стенде занятие запускается с пульта преподавателя.\n"
|
||||
"Для разработки поставь DEV_AUTH_BYPASS=true в backend/.env"
|
||||
) from exc
|
||||
raise
|
||||
|
||||
|
||||
async def main(scenario_id: str, mode: str) -> None:
|
||||
session_id = str(uuid.uuid4())
|
||||
async with websockets.connect(f"{BACKEND}/ws/control/{session_id}") as control:
|
||||
headers = {"Cookie": dev_cookie()}
|
||||
async with websockets.connect(
|
||||
f"{BACKEND}/ws/control/{session_id}", additional_headers=headers
|
||||
) as control:
|
||||
await control.send(json.dumps({
|
||||
"type": "scenario.start",
|
||||
"scenario_id": scenario_id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue