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
|
|
@ -19,6 +19,7 @@ from pydantic import BaseModel, Field
|
|||
|
||||
from app.domain.classifiers import DDSCode, IncidentType, Level, Outcome
|
||||
from app.domain.kio import KIO, Coords
|
||||
from app.domain.roles import Role
|
||||
from app.domain.statuses import ServiceStatus, StationSnapshot
|
||||
from app.domain.taxonomy import Finding
|
||||
from app.domain.timers import TimerSnapshot
|
||||
|
|
@ -79,6 +80,9 @@ class ErrorKind(StrEnum):
|
|||
DIRECTIVE_NEEDS_NETWORK = "directive_needs_network"
|
||||
SCENARIO_INVALID = "scenario_invalid"
|
||||
UNSUPPORTED_EVENT = "unsupported_event"
|
||||
#: Канал открыт не тем, кому он предназначен (lct-23). Фронт по этому коду
|
||||
#: отправляет на вход, а не показывает «занятие не найдено».
|
||||
FORBIDDEN = "forbidden"
|
||||
INTERNAL = "internal"
|
||||
|
||||
|
||||
|
|
@ -585,9 +589,24 @@ class SessionReport(BaseModel):
|
|||
override_comment: str | None = None
|
||||
|
||||
|
||||
class Principal(BaseModel):
|
||||
"""Кто вошёл. В событиях не участвует, но фронт разводит по роли экраны,
|
||||
и список ролей должен приезжать из домена, а не переписываться руками
|
||||
(lct-23). Полная модель — в `app/api/auth.py`.
|
||||
"""
|
||||
|
||||
login: str
|
||||
full_name: str
|
||||
role: Role
|
||||
service: str | None = None
|
||||
trainee_id: UUID | None = None
|
||||
|
||||
|
||||
class EventCatalog(BaseModel):
|
||||
"""Единственное назначение — собрать все союзы в одну JSON Schema для make types."""
|
||||
|
||||
principal: Principal
|
||||
|
||||
server_to_trainee: ServerToTrainee
|
||||
trainee_to_server: TraineeToServer
|
||||
server_to_observer: ServerToObserver
|
||||
|
|
|
|||
Loading…
Reference in a new issue