Учебная роль и границы авторизации UI
Кабинет позволяет наблюдателю читать заявки, а диспетчеру — изменять их. В Angular это выражается выбранной учебной ролью, видимостью кнопки и проверкой save. Разберём, что такое ограничение действительно даёт и где заканчивается. Результат главы — интерфейсная политика, которая помогает человеку понять доступные действия, при этом не обещает безопасной авторизации от одного состояния браузера или route guard.
Role хранится в root Session и по умолчанию равна viewer. Кнопки App позволяют явно переключить её. Это публичный учебный режим: пользователь выбирает заранее известный токен из исходников. Он нужен для самостоятельного изучения отказов 401/403 и согласования клиентского API с Nest. В системе с настоящими пользователями такой переключатель не может выдавать права. Авторитетный источник идентичности и разрешений должен находиться вне изменяемого клиентского состояния.
Видимость действия
Ниже фрагмент template редактора. Session уже внедрён и предоставляет canEdit. Для диспетчера видна кнопка submit, для наблюдателя — пояснение режима просмотра. Title readOnly ограничивает обычное изменение в поле; status в лаборатории можно менять как локальный черновик, но save не отправит его без роли. Эта политика намеренно минимальна. Если продукт требует полностью неизменяемую форму наблюдателя, следует спроектировать отдельное представление чтения или корректно отключить controls.
@if (session.canEdit()) {
<button [disabled]="saving() || loading()">
{{ saving() ? 'Сохраняем…' : 'Сохранить' }}
</button>
} @else {
<p>Режим просмотра. Для записи выберите учебную роль диспетчера.</p>
}
Ожидается, что при viewer пользователь не увидит кнопку сохранения, а при dispatcher увидит её. Изменение роли во время открытой карточки не создаёт новый Ticket и не меняет version. Это лишь изменение доступного действия в UI. Если текущий публичный dispatcher token не совпадает с настройкой Nest, сервер отклонит запрос, несмотря на видимую кнопку. Именно такой пример показывает, что canEdit является удобным отображением клиентского ожидания, а не доказательством серверного разрешения.
Метод save независимо повторяет session.canEdit(). Это удерживает внутреннее правило даже при вызове метода другим участком приложения. Но пользователь всё равно может обойти его внешним HTTP-запросом. Поэтому данная проверка предотвращает случайное неверное действие клиента, а не защищает базу от любого посетителя. Серверный DispatcherGuard проверяет actor, установленный DemoAuthGuard, непосредственно перед обработчиком PATCH. Позднее реальные права на объект потребуют ещё более предметной серверной политики.
Guard маршрута и право записи
Angular route guard может ограничить переход, если страница не имеет смысла без текущей роли. Но карточка нужна и наблюдателю, и диспетчеру, поэтому архив не закрывает весь маршрут /tickets/:id. Условное действие выражается в самой странице. Если закрыть карточку для viewer, чтение станет недоступным, хотя контракт API разрешает GET. Такое решение ухудшило бы задачу пользователя и перепутало право увидеть данные с правом изменить их содержание.
Можно выделить отдельную edit-страницу и guard для неё. Даже тогда сервер обязан проверять PATCH: клиентский guard выполняется в среде, которой управляет человек, и не является границей доверия владельца PostgreSQL. Он полезен для раннего понятного перенаправления, например на страницу входа, но не заменяет authentication и authorization API. Официальная документация Angular прямо рассматривает серверную проверку как необходимую; в нашей модели это видно по возможности внешнего curl без маршрутизации браузера.
В пределах лаборатории все заявки принадлежат одному общему набору. Нет организаций, авторов, назначений и ограничения чтения конкретного Ticket по пользователю. Role dispatcher разрешает изменение любого существующего id. Если добавить tenantId, недостаточно спрятать чужие строки Angular-фильтром. SELECT и UPDATE сервера должны быть ограничены проверенным tenant пользователя, а role проверяется внутри этой области. Такие изменения потребовали бы нового контракта и миграции, поэтому не объявлены реализованными в текущем архиве.
Токен в запросе
AuthInterceptor добавляет Authorization только к путям apiBase. Он читает Session на момент создания операции, поэтому очередной запрос после переключения роли использует новое значение. Уже отправленный PATCH может сохранить прежний токен и завершиться под его actor. Публичный переключатель не отзывает прошлую серверную операцию. В реальной сессии отзыв и истечение токена требуют собственной серверной проверки, а интерфейс должен понимать соответствующие ответы. Эти lifecycle-правила не моделируются простым signal role.
Токен из браузерного исходника не является секретом. Даже сборка и минификация не превращают demoTokens в безопасное хранилище. В README он прямо назван публичным и пригодным только для локальной лаборатории с loopback Nest. Пример не обучает маскировать credentials в JavaScript. При переходе на production понадобится выбрать проверенную модель сессии или IdP, определить хранение, обновление, CSRF для cookie-операций и проверку прав на объект. Такой переход выходит за пределы текущего результата.
Ответ 401 означает, что сервер не принял учебное удостоверение. Ответ 403 означает, что удостоверение определено, но действие не разрешено этой роли. Пользовательские сообщения различают эти случаи. Не стоит автоматически переключать клиента на dispatcher после 403: это обошло бы смысл отказа и скрыло проблему конфигурации или полномочий. В лаборатории человек может самостоятельно выбрать другую учебную роль, потому что такой сценарий открыто разрешён моделью, а не потому, что приложение тайно выдало ему доступ.
Разберите ожидаемый direct PATCH с viewer token. Хотя запрос не проходил форму, Nest должен вернуть 403 и не создать новую version или событие. Затем тот же body с dispatcher token и актуальной основой ожидаемо проходит. Неверный token должен дать 401 раньше предметной записи. Эти сценарии описаны для будущей проверки читателем; они не запускались. Fixture воспроизводит соответствующие статусы в памяти, но не доказывает безопасность настоящей службы заявок и её инфраструктуры.
На следующем шаге соберём договор передачи клиента: версии, источник данных, самостоятельный запуск и непроверенные части. UI уже объясняет роль, отказ и результат записи, а API-контракт задаёт server-side проверку. Это достаточная учебная граница для общего «Диспетчера», если не приписывать ей отсутствующие аккаунты, tenants и безопасный вход. Именно ясное называние этих границ позволяет другому разработчику продолжить проект без скрытых предположений.
Статус примера — not_run. Зависимости, приложение, SQL, HTTP и тесты не запускались. Описанные ответы и изменения интерфейса ожидаемые; чтение документации не является проверкой выполнением. Полная конечная версия находится в архиве исходников, а её ограничения и действия читателя — в README и CONTRACT.md.
Перейдите к оглавлению серии или общему маршруту интерактивной веб-разработки. Механизм этой главы сверяется с официальным первичным источником; конкретные версии и непроверенные части перечислены в договоре курса.