Files
transcriber/openspec/changes/archive/2026-08-22-trusted-header-login/specs/webapp/spec.md
T
av 7f33c957e5 вход переехал на доверенный заголовок Authelia вместо собственного OIDC
- пришедшего называет заголовок Remote-User от прокси, и верят ему только с
  адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни
  корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе
- учётная запись заводится первым обращением: EnsureUser в пакете хранилища,
  шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users
- cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт
  унаследованный DL3066 — пользователь образа назван числом
2026-08-22 20:24:22 +03:00

9.3 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Неизвестный путь вне корней открывает приложение

Сервис SHALL отдавать разметку приложения на всяком пути, который не принадлежит ни одному корню сервиса и не совпадает с отдельным адресом наблюдения. Корни перечислены поимённо — /api у хранилища, /app у приложения, /_ у панели, — отдельными адресами стоят /health и /metrics.

Корня /auth в перечне больше нет: собственного входа у сервиса не осталось, и адресов под этим корнем не существует. Прежние адреса входа поэтому отвечают тем же, чем отвечает всякий путь вне корней, — разметкой приложения. Резервировать имя за отказом сервис не берётся: имя, за которым ничего не стоит, ничем не отличается от любого другого свободного имени, а второй перечень «когда-то занятых корней» разошёлся бы с первым молча.

Путь принадлежит корню, когда совпадает с ним точно либо начинается им вместе с косой чертой. Оба условия обязательны: по одному лишь префиксу корню /app достался бы посторонний /apple, а по одному лишь префиксу с косой чертой голый /api не достался бы никому и уехал бы разметкой.

Путь, принадлежащий корню, MUST не проваливаться в приложение никогда: отказ контракта остаётся отказом контракта и уходит той формой, которой этот корень отвечает и сегодня. Иначе программа, ошибшаяся адресом под корнем приложения, получила бы разметку с кодом 200 вместо отказа с машиночитаемым кодом — и приняла бы её за ответ.

Путь под каталогом ресурсов — тем, который наполняет сборщик, — разметкой не подменяется: не совпавший с файлом, он MUST отвечать 404. Иначе разметка прежней сборки, назвавшая ресурс, которого в новой сборке уже нет, получает на него 200 и разметку вместо ресурса: браузер отвергнет её по типу содержимого, человек увидит пустой экран, а в кодах ответов сервиса не останется ничего.

Открывающими страницу считаются GET и HEAD, и только они; прочие методы MUST отвечать 405.

Scenario: Обновление страницы посреди приложения открывает тот же экран

  • GIVEN приложение открыто на своём маршруте
  • WHEN браузер спрашивает этот путь заново
  • THEN ответ имеет код 200
  • AND тело ответа — разметка приложения

Scenario: Голый корень разметкой не подменяется

  • WHEN запрос приходит на путь, совпадающий с корнем сервиса точно и без косой черты
  • THEN тело ответа — не разметка приложения

Scenario: Посторонний путь, начинающийся именем корня, открывает приложение

  • WHEN запрос приходит на путь, который начинается именем корня, но не отделён от него косой чертой
  • THEN ответ имеет код 200
  • AND тело ответа — разметка приложения

Scenario: Прежний адрес входа открывает приложение

  • WHEN запрос приходит на путь под прежним корнем входа
  • THEN ответ имеет код 200
  • AND тело ответа — разметка приложения

Scenario: Неизвестный путь под корнем приложения отвечает отказом

  • GIVEN запрос идёт с заголовком, поставленным прокси
  • WHEN он спрашивает неизвестный путь под корнем приложения
  • THEN ответ имеет код 404
  • AND тело ответа — отказ приложения с машиночитаемым кодом, а не разметка

Scenario: Неизвестный путь под корнем приложения неузнанному отвечает как все прочие его адреса

  • WHEN запрос приходит на неизвестный путь под корнем приложения без заголовка
  • THEN ответ имеет код 401
  • AND тело ответа — не разметка приложения

Scenario: Неизвестный путь под корнем хранилища отвечает отказом

  • WHEN запрос приходит на неизвестный путь под корнем хранилища
  • THEN тело ответа — не разметка приложения

Scenario: Несуществующий ресурс отвечает отсутствием, а не разметкой

  • WHEN браузер спрашивает под каталогом ресурсов файл, которого в сборке нет
  • THEN ответ имеет код 404
  • AND тело ответа — не разметка приложения

Requirement: Открытое приложение показывает вошедшего

Приложение SHALL спрашивать сервис, кто пришёл, и показывать его имя. Отказ 401 MUST показываться строкой о том, что сервис его не узнал, и MUST никуда не уводить: своего входа у сервиса нет, а вести человека некуда — заголовок ставит обратный прокси, и человек, которого прокси не назвал, до приложения дошёл бы только мимо него.

Всякий иной отказ и сорванный запрос MUST показываться строкой о неудаче. Разделять их приложение обязано: «сервис вас не узнал» и «сервис не отвечает» — разные состояния, и человек по ним делает разное. Прежде отказ 401 уводил ко входу; уводить стало некуда, и различие сохраняется ради текста, а не ради перехода.

Имя пришедшего берётся ответом сервиса, а не заголовком запроса: заголовок ставит прокси, и приложение его не видит вовсе. Имени в ответе может не быть — тогда приложение MUST показать, что человек узнан, и MUST не подставлять вместо имени адрес почты: его в ответе нет по норме access.

Scenario: Узнанный виден

  • GIVEN запрос приложения идёт с заголовком, поставленным прокси
  • WHEN он открывает приложение
  • THEN приложение показывает его имя

Scenario: Неузнанному показывают, что его не узнали

  • GIVEN сервис отвечает на вопрос о пришедшем кодом 401
  • WHEN человек открывает приложение
  • THEN приложение показывает строку о том, что его не узнали
  • AND никуда его не уводит

Scenario: Отказ сервиса от неузнавания отличается

  • GIVEN сервис отвечает на вопрос о пришедшем отказом, который не является неузнаванием
  • WHEN человек открывает приложение
  • THEN приложение показывает строку о неудаче
  • AND эта строка не та, которой оно сообщает о неузнавании