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

19 KiB
Raw Blame History

Критерии приёмки

Дословно из записи задачи trusted-header-login. Файл задачи закрытие удалит — критерии обязаны его пережить.

  1. Обращение к адресу приложения с заголовком от доверенного источника идёт от имени учётной записи, заведённой при первом таком обращении, а повторное с тем же значением попадает в ту же запись. Оракул — тест обработчика: два запроса подряд, в хранилище одна запись пользователя.
  2. Тот же заголовок с недоверенного адреса даёт 401, а не вход под названным именем. Оракул — тест: запрос с адресом источника вне перечня доверенных.
  3. Собственные адреса входа хранилища сессии не выдают и учётную запись не меняют. Оракул — тест по перечню адресов под /api/collections/users/: каждый отвечает отказом.
  4. Механики OIDC в дереве не осталось: корня /auth, кук входа, ApplyProviderSettings, cmd/oidcstub и секрета клиента в конфиге. Оракул — поиск по этим именам плюс зелёный task gate.
  5. Разграничение записей по владельцу работает как прежде: чужая запись неотличима от несуществующей. Оракул — существующие тесты владельца остаются зелёными.

Рубрика ревью дизайна

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

  1. Доверие ограничено источником, и источник берётся у соединения, а не у пересылаемого значения; настройка проверяется на старте, небезопасного умолчания нет.
  2. Заголовок узнаётся однозначно: более одного значения не даёт «первое попавшееся».
  3. Вырожденное значение — пустое, пробельное, сверх предела длины, с управляющими знаками — не узнаёт никого и не заводит ничего.
  4. Ключ учётной записи стабилен, и цена его нестабильности названа в обе стороны — и смена, и переиспользование. Выборка по ключу имеет свой индекс.
  5. Заведение первым обращением идемпотентно и устойчиво к конкуренции; отказ уникальности не по ключевой колонке имеет назначенный исход.
  6. Результат остаётся функцией уже произошедшего: ключевая колонка неизменяема после заведения.
  7. Второго способа стать этим человеком нет, а при двух предъявленных удостоверениях победитель назначен нормой, а не порядком слоёв.
  8. Закрытие поверхности не отменяет законного пути заведения записи самим сервисом.
  9. Значение заголовка — недоверенный вход на всём пути: не в журнал, не в ответ, не в метку метрики, и в хранилище параметром, а не подстановкой в фильтр.
  10. Охват слоя назван, перечень открытых адресов закрыт, посторонний заголовок ответа открытых адресов не меняет.
  11. Срок узнавания назван: всё, что переживает запрос, либо отсутствует, либо имеет назначенный срок, и этот срок и есть задержка отзыва.
  12. Исход узнавания наблюдаем владельцем и неразличим для отправителя.

1. Схема хранилища

  • 1.1 Новый шаг схемы 202608220001_trusted_header_login.go: колонка provider_login в коллекции users с уникальным индексом, почта переведена в необязательную, OAuth2.Enabled = false и OAuth2.Providers = nil. Применённый 202608120001_oidc_login не трогается.
  • 1.2 Тот же шаг снимает все правила доступа коллекции users в пустое — ListRule, ViewRule, CreateRule, UpdateRule, DeleteRule. Сегодня четыре из них библиотечные (id = @request.auth.id), и правка своей записи открыта: это путь захвата чужого имени через ключевую колонку.
  • 1.3 Шаг зарегистрирован в migrations.go, имя ключевой колонки объявлено константой рядом с именами коллекций.
  • 1.4 schema_test.go сторожит users наравне с шестью коллекциями, которые он уже проверяет: все пять правил пусты.
  • 1.5 Откат шага возвращает то, что было до него, кроме открытого создания записи и открытой правки, и не падает на проверке коллекции.
  • 1.6 docs/database.md описывает новую колонку, снятые настройки и снятые правила доступа.

2. Настройка

  • 2.1 Секция входа конфига переписана: адреса провайдера, идентификатор и секрет клиента, адрес возврата и признак защищённой куки убраны, перечень доверенных адресов заведён: секция [auth] остаётся под своим именем, в ней один ключ trusted_proxies (решение человека 2026-08-22).
  • 2.2 Проверка настройки на старте: пустой перечень и нечитаемая строка роняют старт с именем ключа.
  • 2.3 config.example.toml переписан вместе с секцией, раздел про локальный вход заменён на подставной прокси.

3. Узнавание пришедшего

  • 3.1 Слой узнавания в транспорте HTTP: читает заголовок, судит адрес пира по перечню, ставит учётную запись предъявителя только когда её ещё нет. Вешается корневым, но действует на объявленной области — корень приложения плюс адрес выдачи файлового токена; область выводится из перечня адресного пространства, а не пишется вторым списком.
  • 3.2 Приём значения заголовка: пустое и пробельное не узнают никого, более одного значения не узнаёт никого, предел длины и отказ на управляющие знаки, сравнение точное. Значение уходит в хранилище параметром, а не подстановкой в текст фильтра.
  • 3.3 Поиск и заведение учётной записи — методом пакета хранилища, а не куском в транспорте: у правила один дом, и api-tokens возьмёт его же. Имя и почта берутся только при заведении, найденная запись не переписывается; отказ уникальности по ключевой колонке ведёт к повторному поиску, по любой другой — к заведению записи без почты.
  • 3.4 Отказ хранилища при узнавании кончается отказом сервиса, а не молчаливым проходом неузнанным.
  • 3.5 Журнал: исходы «заголовок с недоверенного адреса» и «учётная запись заведена» — с адресом пира и идентификатором записи, без значения заголовка; перечень доверенных адресов называется строкой при подъёме.
  • 3.6 ApplyProviderSettings, ProviderSettings и ProviderName убраны из пакета хранилища; SessionDuration убран вместе со сроком сессии.

4. Снос механики OIDC

  • 4.1 internal/controller/http/auth.go удалён целиком вместе с корнем /auth в перечне адресного пространства.
  • 4.2 SessionFromCookie и BlockSessionRefresh удалены; места их привязки переписаны на новый слой.
  • 4.3 cmd/oidcstub удалён.
  • 4.4 Приложение: адрес, которым экран уводил ко входу, убран; неузнавание показывается строкой и отличается от прочей неудачи. Юнит-тесты приложения обновлены.

5. Способ представиться без прокси

  • 5.1 Заведён cmd/devtools с подкомандой proxy: слушает свой порт, ставит заголовок, переправляет запрос сервису. Подкоманду admin заводит задача dev-run-task. В образ пакет не едет: строка сборки Dockerfile называет точку входа поимённо.
  • 5.2 CLAUDE.md, раздел «Команды» и раздел «Запреты» — про локальный вход без прокси; README.md, если он про это говорит.

6. Проверки

  • 6.1 Тест: первое обращение с доверенного адреса заводит учётную запись, второе с тем же значением попадает в ту же — в хранилище одна запись (критерий 1).
  • 6.2 Тест: тот же заголовок с недоверенного адреса даёт 401, и учётной записи не появляется (критерий 2).
  • 6.3 Тест по перечню собственных адресов входа хранилища под /api/collections/users/: каждый отвечает отказом и учётной записи не меняет (критерий 3).
  • 6.4 Тест: годный собственный токен побеждает заголовок, а протухший узнаванию не мешает.
  • 6.4a Тест: узнанный не правит свою запись в коллекции пользователей запросом к хранилищу и не перечисляет коллекцию — путь захвата чужого имени закрыт.
  • 6.4b Тест: пустой заголовок, два значения одного заголовка и значение сверх предела длины не узнают никого и записи не заводят.
  • 6.4c Тест: два одновременных первых обращения одним значением дают одну запись.
  • 6.4d Тест: занятый адрес почты не мешает завести запись — она заводится без почты.
  • 6.4e Тест: запрос с заголовком на GET /health учётной записи не заводит.
  • 6.4f Тест: прежние адреса под корнем /auth отдают разметку приложения.
  • 6.5 Тест: значение заголовка не попадает в журнал.
  • 6.6 Тест: ответ на успешный запрос не ставит браузеру куки.
  • 6.7 Тест: токен файла выдаётся узнанному по заголовку, и путь к файлу записи проходит целиком.
  • 6.8 Существующие тесты владельца и разграничения переписаны на новый способ представиться и остаются зелёными (критерий 5).
  • 6.9 Тесты входа, обмена кода, куки, продления и выхода удалены вместе с предметом.

7. Документы и закрытие

  • 7.1 docs/security.md: периметр, недоверенный вход, разграничение доступа; четвёртый сдвиг про секрет в базе снят; требование к контуру — прокси ставит заголовок, а не пропускает пришедший — названо поимённо; там же цена переиспользования логина у провайдера.
  • 7.2 CLAUDE.md: изъятие из инварианта о секрете снято.
  • 7.3 docs/architecture.md приведён в соответствие; в «Единые точки проекта» добавлена строка про дом правила узнавания предъявителя.
  • 7.3a docs/passport.md: строки про сессию OIDC у потребителей и в границе «Управление учётными записями» — учётную запись сервис не заводит по своей воле, а зеркалит имя, названное провайдером.
  • 7.4 Поиск по oidc, transcriber_session, transcriber_login, ApplyProviderSettings, /auth/ в дереве не находит живого кода (критерий 4).
  • 7.5 task gate зелёный, кроме унаследованного hadolint DL3066 — отказ воспроизводится на чистом origin/master, объявлен долгом в CLAUDE.md (критерий 4).

8. Отработка ревью кода

  • 8.1 Слой узнавания переехал за ограничитель частоты: отвергнутый запрос больше не заводит учётной записи.
  • 8.2 Заведение учётной записи пишется в журнал; два значения заголовка — предупреждением, а не отладочным уровнем.
  • 8.3 Имя и почта принимаются, а не кладутся как есть: длинное имя больше не запирает человека вечным отказом сервиса.
  • 8.4 Отказ уникальности судится по коду, а не по имени колонки: негодная почта отличима от занятой.
  • 8.5 Предел логина считается в знаках, а не в байтах.
  • 8.6 Уникальный индекс по ключу сделан частичным — как соседний индекс почты; подъём на непустой базе больше не роняет накатку.
  • 8.7 Ключ учётной записи закрыт от правки рукой в панели модельным хуком.
  • 8.8 Хранилищу назван заголовок адреса спрашивающего: бюджет ограничителя перестал быть общим на весь сервис.
  • 8.9 Приложение показывает текст сервера, своего словаря под коды ответа не заводит; текст 401 больше не зовёт ко входу, которого нет.
  • 8.10 Оснастка берёт имена заголовков константами транспорта.
  • 8.11 Три записи конвенций приведены к действительности; словарь полей журнала пополнен, изъятие про вывод оснастки записано.
  • 8.12 Порог отката образа назван в «Эксплуатации»; требование к контуру про X-Forwarded-For и цена ширины перечня — в модели угроз.
  • 8.13 Образец конфига сужен до адреса прокси.
  • 7.6 Поведенческая проверка: сервис поднят локально, приложение открывается, учётная запись заводится первым обращением, запрос без заголовка получает отказ.