- пришедшего называет заголовок Remote-User от прокси, и верят ему только с адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе - учётная запись заводится первым обращением: EnsureUser в пакете хранилища, шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users - cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт унаследованный DL3066 — пользователь образа назван числом
19 KiB
19 KiB
Критерии приёмки
Дословно из записи задачи trusted-header-login. Файл задачи закрытие удалит —
критерии обязаны его пережить.
- Обращение к адресу приложения с заголовком от доверенного источника идёт от имени учётной записи, заведённой при первом таком обращении, а повторное с тем же значением попадает в ту же запись. Оракул — тест обработчика: два запроса подряд, в хранилище одна запись пользователя.
- Тот же заголовок с недоверенного адреса даёт
401, а не вход под названным именем. Оракул — тест: запрос с адресом источника вне перечня доверенных. - Собственные адреса входа хранилища сессии не выдают и учётную запись не
меняют. Оракул — тест по перечню адресов под
/api/collections/users/: каждый отвечает отказом. - Механики OIDC в дереве не осталось: корня
/auth, кук входа,ApplyProviderSettings,cmd/oidcstubи секрета клиента в конфиге. Оракул — поиск по этим именам плюс зелёныйtask gate. - Разграничение записей по владельцу работает как прежде: чужая запись неотличима от несуществующей. Оракул — существующие тесты владельца остаются зелёными.
Рубрика ревью дизайна
Двенадцать свойств, по которым судится узел этого рода — слой узнавания предъявителя плюс заведение учётной записи первым обращением. Порождены до чтения дизайна. Приёмка идёт по одному списку: эти пункты наравне с критериями постановки выше.
- Доверие ограничено источником, и источник берётся у соединения, а не у пересылаемого значения; настройка проверяется на старте, небезопасного умолчания нет.
- Заголовок узнаётся однозначно: более одного значения не даёт «первое попавшееся».
- Вырожденное значение — пустое, пробельное, сверх предела длины, с управляющими знаками — не узнаёт никого и не заводит ничего.
- Ключ учётной записи стабилен, и цена его нестабильности названа в обе стороны — и смена, и переиспользование. Выборка по ключу имеет свой индекс.
- Заведение первым обращением идемпотентно и устойчиво к конкуренции; отказ уникальности не по ключевой колонке имеет назначенный исход.
- Результат остаётся функцией уже произошедшего: ключевая колонка неизменяема после заведения.
- Второго способа стать этим человеком нет, а при двух предъявленных удостоверениях победитель назначен нормой, а не порядком слоёв.
- Закрытие поверхности не отменяет законного пути заведения записи самим сервисом.
- Значение заголовка — недоверенный вход на всём пути: не в журнал, не в ответ, не в метку метрики, и в хранилище параметром, а не подстановкой в фильтр.
- Охват слоя назван, перечень открытых адресов закрыт, посторонний заголовок ответа открытых адресов не меняет.
- Срок узнавания назван: всё, что переживает запрос, либо отсутствует, либо имеет назначенный срок, и этот срок и есть задержка отзыва.
- Исход узнавания наблюдаем владельцем и неразличим для отправителя.
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 Поведенческая проверка: сервис поднят локально, приложение открывается, учётная запись заводится первым обращением, запрос без заголовка получает отказ.