Files
transcriber/openspec/changes/archive/2026-08-12-oidc-login/tasks.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

122 lines
9.9 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.
## 1. Конфигурация
- [x] 1.1 Завести секцию конфига под провайдера: адрес авторизации, адрес обмена
кода, адрес сведений о пользователе, идентификатор клиента, секрет клиента,
адрес возврата
- [x] 1.2 Дописать те же ключи в `config.dist.toml` с пустыми значениями и
комментарием, откуда их брать
- [x] 1.3 Проверить, что незаполненный конфиг роняет старт с внятным
сообщением, а не поднимает сервис с молча выключенным входом
## 2. Провайдер в хранилище
- [x] 2.1 Завести шаг схемы, включающий провайдера `oidc` у коллекции
пользователей; файл шага именуется по правилу проекта и не переписывает
прежние
- [x] 2.2 Тем же шагом закрыть создание записи в коллекции пользователей и
выключить вход по паролю, одноразовый код и восстановление доступа: умолчание
библиотеки оставляет их открытыми
- [x] 2.3 Тем же шагом назначить срок жизни сессии числом вместо умолчания в
пять суток
- [x] 2.4 При подъёме сервиса приводить настройки провайдера к значениям
конфига: адреса, идентификатор клиента, секрет
- [x] 2.5 Убедиться, что секрет не попадает в журнал ни при подъёме, ни при
ошибке настройки
## 3. Вход, возврат, выход
- [x] 3.1 `GET /auth/login`: завести состояние и проверочный код PKCE, положить
во временную куку с теми же признаками, что у сессионной, увести на адрес
авторизации провайдера
- [x] 3.2 `GET /auth/callback`: сверить состояние с выданным, отвергнуть
несовпавшее и уже употреблённое, обменять код средствами хранилища с
таймаутом, поставить куку сессии, убрать временную
- [x] 3.3 Кука сессии зовётся `transcriber_session` и несёт `HttpOnly`,
`SameSite` и `Secure`; последний берётся из конфига с умолчанием «включено»
- [x] 3.4 `POST /auth/logout`: сперва обесценить ключ токенов учётной записи,
затем убрать куку сессии
- [x] 3.5 Промежуточный слой перекладывает значение куки в заголовок
`Authorization`, только когда заголовка нет, и только на адресах приложения
## 4. Закрытие API
- [x] 4.1 `POST /api/audio` и `GET /api/status/{id}` требуют узнанного
отправителя; отказ — код `401`
- [x] 4.2 Отказ по отсутствию сессии наступает раньше чтения тела запроса
- [x] 4.3 `GET /health` и `GET /metrics` остаются доступны без сессии
- [x] 4.4 Отказ без сессии одинаков для заведённой и неизвестной задачи
- [x] 4.5 Пометить поле файла защищённым тем же шагом схемы: ссылка на файл
перестаёт быть правом пройти по ней и требует сессии
- [x] 4.6 Убедиться, что конвейер по-прежнему читает файл из файловой системы, а
панель администратора его по-прежнему скачивает
## 5. Проверки
- [x] 5.1 Тест: оба эндпоинта API без куки отдают `401` и не заводят задачу;
`/health` и `/metrics` без куки отдают `200`
- [x] 5.2 Тест: запрос с прежней кукой проходит после пересоздания сервера
- [x] 5.3 Тест: после выхода запрос с прежней кукой получает отказ
- [x] 5.4 Тест: ни значение секрета, ни значение сессии, ни адрес почты не
встречаются в записанном выводе логгера
- [x] 5.5 Тест: возврат с невыданным состоянием не открывает сессию и не заводит
учётную запись; повторный возврат с уже употреблённым — тоже
- [x] 5.6 Тест: анонимное создание записи в коллекции пользователей и вход по
паролю получают отказ
- [x] 5.7 Тест: запрос с кукой и заголовком разом проходит по заголовку
- [x] 5.8 Тест: ссылка на файл записи без сессии отдаёт отказ, а с сессией —
тот же файл
- [x] 5.9 `task gate` зелёный целиком
## 6. Документация
- [x] 6.1 `docs/security.md`: первая строка периметра переписана под новый
периметр; названо новое место жизни секрета клиента — база; в разделе «Что
разграничивает доступ» записано, что допуск держит правило провайдера вне
репозитория, а сервис своей проверки не делает
- [x] 6.2 `docs/architecture.md`: capability `access` внесена в перечень
- [x] 6.3 `docs/conventions/config.md`: новые ключи конфига и расхождения
образца, если появились
## Критерии приёмки
Перенесены из записи задачи `oidc-login` дословно. Файл задачи закрытие удалит —
критерии обязаны его пережить.
- Запрос к `POST /api/audio` и `GET /api/status/:id` без сессии получает отказ, а
не заводит задачу и не отдаёт текст. Оракул — тест на обоих эндпоинтах без
куки: код ответа 401 либо 302 на вход, тело без данных задачи. Тот же тест
проверяет вторую сторону границы: `GET /health` и `GET /metrics` без куки
отвечают 200.
- Сессия переживает перезапуск приложения. Оракул — тест: запрос с прежней кукой
после пересоздания сервера проходит.
- Выход из сессии закрывает доступ. Оракул — тест: после выхода тот же запрос
получает отказ.
- Секрет провайдера не попадает ни в лог, ни в ответ. Оракул — тест на отсутствие
значения секрета в записанном выводе логгера.
- Первая строка `docs/security.md` описывает новый периметр. Оракул — `task
gate`, шаг `docs.py check`.
**Сужение против исходного критерия, объявленное ревью дизайна:** код отказа —
`401`, без допуска `302`. Оба адреса судят внешнюю программу, а не браузер, и
`302` для программы означает «получил 200 со страницей входа»; `curl -L` при нём
уходит постить тело на страницу входа провайдера. Дельта-спека `intake`
нормирует `401` двумя сценариями.
## Рубрика ревью дизайна
Порождена проходом `rubric` до чтения артефактов; сюда переносятся пункты,
ставшие приёмочными сверх критериев задачи.
- Отказ без сессии наступает раньше чтения тела и раньше обращения к хранилищу.
- Форма отказа одна и та же у существующего и несуществующего ресурса.
- Ни одно значение, дающее доступ, не печатается: код провайдера, секрет
клиента, значение сессии, адрес почты.
- Правило доступа читается как «всё требует сессии, кроме перечня», а перечень
открытого живёт в одном месте.
- Все прочие способы получить сессию к тому же субъекту выключены либо названы
поимённо с обоснованием, почему они не обход.
- Возврат от провайдера отвергается без состояния, с чужим, с истёкшим и с уже
употреблённым — до обмена кода.
- У обращения к провайдеру есть таймаут, и «медленный» отличается от «отказал».
- Исход входа и выхода не зависит от порядка параллельных операций.