Files
transcriber/openspec/changes/archive/2026-08-22-trusted-header-login/specs/storage/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

10 KiB

MODIFIED Requirements

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

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

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

Токен файла хранилище выдаёт на предъявителя, а не на файл, и о файле при выдаче не спрашивает. Значит владельца судит переход по ссылке, а не выдача токена: отказ наступает там, и требовать его от выдачи значит требовать механизма, которого нет.

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

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

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

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

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

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

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

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

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

Scenario: Неузнанному файл не отдаётся

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

Scenario: Токен файла выдаётся узнанному по заголовку

  • GIVEN запрос идёт с доверенного адреса с заголовком Remote-User
  • WHEN он просит у хранилища токен файла
  • THEN токен выдаётся

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

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

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

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

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

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

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

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

Requirement: Файл записи сужается владельцем наравне с задачей

Хранилище SHALL держать владельца и у файла записи — той же связью с учётной записью, — и правило просмотра файлов MUST пускать к файлу только его владельца.

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

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

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

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

Scenario: Чужой файл не отдаётся

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

Scenario: Свой файл отдаётся

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

Scenario: Файл без владельца не сохраняется

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

Scenario: Приведённая копия получает владельца записи

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