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

207 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Критерии приёмки
Дословно из записи задачи `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. Схема хранилища
- [x] 1.1 Новый шаг схемы `202608220001_trusted_header_login.go`: колонка
`provider_login` в коллекции `users` с уникальным индексом, почта
переведена в необязательную, `OAuth2.Enabled = false` и
`OAuth2.Providers = nil`. Применённый `202608120001_oidc_login` не
трогается.
- [x] 1.2 Тот же шаг снимает **все** правила доступа коллекции `users` в пустое —
`ListRule`, `ViewRule`, `CreateRule`, `UpdateRule`, `DeleteRule`. Сегодня
четыре из них библиотечные (`id = @request.auth.id`), и правка своей записи
открыта: это путь захвата чужого имени через ключевую колонку.
- [x] 1.3 Шаг зарегистрирован в `migrations.go`, имя ключевой колонки объявлено
константой рядом с именами коллекций.
- [x] 1.4 `schema_test.go` сторожит `users` наравне с шестью коллекциями,
которые он уже проверяет: все пять правил пусты.
- [x] 1.5 Откат шага возвращает то, что было до него, кроме открытого создания
записи и открытой правки, и не падает на проверке коллекции.
- [x] 1.6 `docs/database.md` описывает новую колонку, снятые настройки и снятые
правила доступа.
## 2. Настройка
- [x] 2.1 Секция входа конфига переписана: адреса провайдера, идентификатор и
секрет клиента, адрес возврата и признак защищённой куки убраны, перечень
доверенных адресов заведён: секция `[auth]` остаётся под своим именем, в
ней один ключ `trusted_proxies` (решение человека 2026-08-22).
- [x] 2.2 Проверка настройки на старте: пустой перечень и нечитаемая строка
роняют старт с именем ключа.
- [x] 2.3 `config.example.toml` переписан вместе с секцией, раздел про локальный
вход заменён на подставной прокси.
## 3. Узнавание пришедшего
- [x] 3.1 Слой узнавания в транспорте HTTP: читает заголовок, судит адрес пира по
перечню, ставит учётную запись предъявителя только когда её ещё нет.
Вешается корневым, но действует на объявленной области — корень приложения
плюс адрес выдачи файлового токена; область выводится из перечня адресного
пространства, а не пишется вторым списком.
- [x] 3.2 Приём значения заголовка: пустое и пробельное не узнают никого, более
одного значения не узнаёт никого, предел длины и отказ на управляющие
знаки, сравнение точное. Значение уходит в хранилище параметром, а не
подстановкой в текст фильтра.
- [x] 3.3 Поиск и заведение учётной записи — **методом пакета хранилища**, а не
куском в транспорте: у правила один дом, и `api-tokens` возьмёт его же.
Имя и почта берутся только при заведении, найденная запись не
переписывается; отказ уникальности по ключевой колонке ведёт к повторному
поиску, по любой другой — к заведению записи без почты.
- [x] 3.4 Отказ хранилища при узнавании кончается отказом сервиса, а не
молчаливым проходом неузнанным.
- [x] 3.5 Журнал: исходы «заголовок с недоверенного адреса» и «учётная запись
заведена» — с адресом пира и идентификатором записи, без значения
заголовка; перечень доверенных адресов называется строкой при подъёме.
- [x] 3.6 `ApplyProviderSettings`, `ProviderSettings` и `ProviderName` убраны из
пакета хранилища; `SessionDuration` убран вместе со сроком сессии.
## 4. Снос механики OIDC
- [x] 4.1 `internal/controller/http/auth.go` удалён целиком вместе с корнем
`/auth` в перечне адресного пространства.
- [x] 4.2 `SessionFromCookie` и `BlockSessionRefresh` удалены; места их привязки
переписаны на новый слой.
- [x] 4.3 `cmd/oidcstub` удалён.
- [x] 4.4 Приложение: адрес, которым экран уводил ко входу, убран; неузнавание
показывается строкой и отличается от прочей неудачи. Юнит-тесты приложения
обновлены.
## 5. Способ представиться без прокси
- [x] 5.1 Заведён `cmd/devtools` с подкомандой `proxy`: слушает свой порт,
ставит заголовок, переправляет запрос сервису. Подкоманду `admin` заводит
задача `dev-run-task`. В образ пакет не едет: строка сборки `Dockerfile`
называет точку входа поимённо.
- [x] 5.2 `CLAUDE.md`, раздел «Команды» и раздел «Запреты» — про локальный вход
без прокси; `README.md`, если он про это говорит.
## 6. Проверки
- [x] 6.1 Тест: первое обращение с доверенного адреса заводит учётную запись,
второе с тем же значением попадает в ту же — в хранилище одна запись
(критерий 1).
- [x] 6.2 Тест: тот же заголовок с недоверенного адреса даёт `401`, и учётной
записи не появляется (критерий 2).
- [x] 6.3 Тест по перечню собственных адресов входа хранилища под
`/api/collections/users/`: каждый отвечает отказом и учётной записи не
меняет (критерий 3).
- [x] 6.4 Тест: годный собственный токен побеждает заголовок, а протухший
узнаванию не мешает.
- [x] 6.4a Тест: узнанный не правит свою запись в коллекции пользователей
запросом к хранилищу и не перечисляет коллекцию — путь захвата чужого имени
закрыт.
- [x] 6.4b Тест: пустой заголовок, два значения одного заголовка и значение
сверх предела длины не узнают никого и записи не заводят.
- [x] 6.4c Тест: два одновременных первых обращения одним значением дают одну
запись.
- [x] 6.4d Тест: занятый адрес почты не мешает завести запись — она заводится
без почты.
- [x] 6.4e Тест: запрос с заголовком на `GET /health` учётной записи не заводит.
- [x] 6.4f Тест: прежние адреса под корнем `/auth` отдают разметку приложения.
- [x] 6.5 Тест: значение заголовка не попадает в журнал.
- [x] 6.6 Тест: ответ на успешный запрос не ставит браузеру куки.
- [x] 6.7 Тест: токен файла выдаётся узнанному по заголовку, и путь к файлу
записи проходит целиком.
- [x] 6.8 Существующие тесты владельца и разграничения переписаны на новый способ
представиться и остаются зелёными (критерий 5).
- [x] 6.9 Тесты входа, обмена кода, куки, продления и выхода удалены вместе с
предметом.
## 7. Документы и закрытие
- [x] 7.1 `docs/security.md`: периметр, недоверенный вход, разграничение доступа;
четвёртый сдвиг про секрет в базе снят; требование к контуру — прокси
**ставит** заголовок, а не пропускает пришедший — названо поимённо; там же
цена переиспользования логина у провайдера.
- [x] 7.2 `CLAUDE.md`: изъятие из инварианта о секрете снято.
- [x] 7.3 `docs/architecture.md` приведён в соответствие; в «Единые точки
проекта» добавлена строка про дом правила узнавания предъявителя.
- [x] 7.3a `docs/passport.md`: строки про сессию OIDC у потребителей и в границе
«Управление учётными записями» — учётную запись сервис не заводит по своей
воле, а зеркалит имя, названное провайдером.
- [x] 7.4 Поиск по `oidc`, `transcriber_session`, `transcriber_login`,
`ApplyProviderSettings`, `/auth/` в дереве не находит живого кода
(критерий 4).
- [x] 7.5 `task gate` зелёный, кроме унаследованного `hadolint DL3066` — отказ
воспроизводится на чистом `origin/master`, объявлен долгом в `CLAUDE.md`
(критерий 4).
## 8. Отработка ревью кода
- [x] 8.1 Слой узнавания переехал за ограничитель частоты: отвергнутый запрос
больше не заводит учётной записи.
- [x] 8.2 Заведение учётной записи пишется в журнал; два значения заголовка —
предупреждением, а не отладочным уровнем.
- [x] 8.3 Имя и почта принимаются, а не кладутся как есть: длинное имя больше не
запирает человека вечным отказом сервиса.
- [x] 8.4 Отказ уникальности судится по коду, а не по имени колонки: негодная
почта отличима от занятой.
- [x] 8.5 Предел логина считается в знаках, а не в байтах.
- [x] 8.6 Уникальный индекс по ключу сделан частичным — как соседний индекс
почты; подъём на непустой базе больше не роняет накатку.
- [x] 8.7 Ключ учётной записи закрыт от правки рукой в панели модельным хуком.
- [x] 8.8 Хранилищу назван заголовок адреса спрашивающего: бюджет ограничителя
перестал быть общим на весь сервис.
- [x] 8.9 Приложение показывает текст сервера, своего словаря под коды ответа не
заводит; текст `401` больше не зовёт ко входу, которого нет.
- [x] 8.10 Оснастка берёт имена заголовков константами транспорта.
- [x] 8.11 Три записи конвенций приведены к действительности; словарь полей
журнала пополнен, изъятие про вывод оснастки записано.
- [x] 8.12 Порог отката образа назван в «Эксплуатации»; требование к контуру про
`X-Forwarded-For` и цена ширины перечня — в модели угроз.
- [x] 8.13 Образец конфига сужен до адреса прокси.
- [x] 7.6 Поведенческая проверка: сервис поднят локально, приложение открывается,
учётная запись заводится первым обращением, запрос без заголовка получает
отказ.