Files
transcriber/openspec/changes/archive/2026-08-12-oidc-login/specs/storage/spec.md
T
av c44f0e7582 HTTP API закрыт за вход через OIDC у Authelia
- шаг схемы закрывает поверхность, которую хранилище приносит открытой:
  собственную регистрацию, вход по паролю и одноразовый код — без этого
  закрытие приёма обходилось двумя запросами
- продление сессии выключено, срок семь суток: иначе отзыв доступа у
  провайдера до сервиса не доходит никогда
- файл записи отдаётся вошедшему по токену файла — пересмотр
  ADR-2026-08-12-file-link-open-but-not-logged
2026-08-12 17:44:22 +03:00

5.4 KiB

MODIFIED Requirements

Requirement: Файл отдаётся ссылкой

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

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

Отсюда порядок для потребителя: сессия → токен файла → ссылка с этим токеном. Браузер с одной лишь кукой файла не получит, и это свойство хранилища, а не недосмотр.

Ссылка на несуществующую запись MUST отвечать отказом, а не пустым файлом.

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

Защищённое поле сужает это право, но не отменяет запрета: право пройти теперь требует ещё и сессии, а строка журнала со ссылкой по-прежнему собирала бы половину ключа.

Отсюда требование к отказам: сообщение об отказе хранилища MUST не выходить за пределы хранилища дословно. Отказ чтения и отказ укладки называют ключ файла целиком, а отказ выгрузки во внешнее хранилище — полный адрес объекта; и то и другое кончается в журнале и собирает ссылку не хуже успешного пути.

Конвейер расшифровки этим не затронут: он читает файл из файловой системы хранилища, а не по ссылке.

Что именно журнал приёма пишет ради прослеживаемости, нормирует capability intake.

Scenario: Файл забирают по ссылке

  • GIVEN запись принята и её файл лежит в хранилище
  • AND забирающий предъявил сессию и взял по ней токен файла
  • WHEN ссылку на файл запрашивают с этим токеном
  • THEN приходит тот же файл, и его длина совпадает с длиной принятого

Scenario: Без сессии файл не отдаётся

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

Scenario: Ссылка ведёт в никуда

  • WHEN запрашивают ссылку на запись, которой нет
  • THEN приходит отказ, а не пустой ответ

Scenario: По журналу ссылку не собрать

  • GIVEN запись принята и прошла конвейер
  • WHEN читают журнал сервиса целиком
  • THEN имени, под которым файл лёг в хранилище, в нём нет

Scenario: Отказ чтения файла не называет его ключ

  • GIVEN файл записи не читается из хранилища
  • WHEN шаг конвейера берётся за эту запись и отказывает
  • THEN отказ называет запись её идентификатором и не несёт имени файла

Scenario: Конвейер читает файл без сессии

  • GIVEN запись принята и ждёт расшифровки
  • WHEN шаг конвейера берётся за неё
  • THEN файл читается из файловой системы хранилища и шаг проходит