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

9.9 KiB

1. Конфигурация

  • 1.1 Завести секцию конфига под провайдера: адрес авторизации, адрес обмена кода, адрес сведений о пользователе, идентификатор клиента, секрет клиента, адрес возврата
  • 1.2 Дописать те же ключи в config.dist.toml с пустыми значениями и комментарием, откуда их брать
  • 1.3 Проверить, что незаполненный конфиг роняет старт с внятным сообщением, а не поднимает сервис с молча выключенным входом

2. Провайдер в хранилище

  • 2.1 Завести шаг схемы, включающий провайдера oidc у коллекции пользователей; файл шага именуется по правилу проекта и не переписывает прежние
  • 2.2 Тем же шагом закрыть создание записи в коллекции пользователей и выключить вход по паролю, одноразовый код и восстановление доступа: умолчание библиотеки оставляет их открытыми
  • 2.3 Тем же шагом назначить срок жизни сессии числом вместо умолчания в пять суток
  • 2.4 При подъёме сервиса приводить настройки провайдера к значениям конфига: адреса, идентификатор клиента, секрет
  • 2.5 Убедиться, что секрет не попадает в журнал ни при подъёме, ни при ошибке настройки

3. Вход, возврат, выход

  • 3.1 GET /auth/login: завести состояние и проверочный код PKCE, положить во временную куку с теми же признаками, что у сессионной, увести на адрес авторизации провайдера
  • 3.2 GET /auth/callback: сверить состояние с выданным, отвергнуть несовпавшее и уже употреблённое, обменять код средствами хранилища с таймаутом, поставить куку сессии, убрать временную
  • 3.3 Кука сессии зовётся transcriber_session и несёт HttpOnly, SameSite и Secure; последний берётся из конфига с умолчанием «включено»
  • 3.4 POST /auth/logout: сперва обесценить ключ токенов учётной записи, затем убрать куку сессии
  • 3.5 Промежуточный слой перекладывает значение куки в заголовок Authorization, только когда заголовка нет, и только на адресах приложения

4. Закрытие API

  • 4.1 POST /api/audio и GET /api/status/{id} требуют узнанного отправителя; отказ — код 401
  • 4.2 Отказ по отсутствию сессии наступает раньше чтения тела запроса
  • 4.3 GET /health и GET /metrics остаются доступны без сессии
  • 4.4 Отказ без сессии одинаков для заведённой и неизвестной задачи
  • 4.5 Пометить поле файла защищённым тем же шагом схемы: ссылка на файл перестаёт быть правом пройти по ней и требует сессии
  • 4.6 Убедиться, что конвейер по-прежнему читает файл из файловой системы, а панель администратора его по-прежнему скачивает

5. Проверки

  • 5.1 Тест: оба эндпоинта API без куки отдают 401 и не заводят задачу; /health и /metrics без куки отдают 200
  • 5.2 Тест: запрос с прежней кукой проходит после пересоздания сервера
  • 5.3 Тест: после выхода запрос с прежней кукой получает отказ
  • 5.4 Тест: ни значение секрета, ни значение сессии, ни адрес почты не встречаются в записанном выводе логгера
  • 5.5 Тест: возврат с невыданным состоянием не открывает сессию и не заводит учётную запись; повторный возврат с уже употреблённым — тоже
  • 5.6 Тест: анонимное создание записи в коллекции пользователей и вход по паролю получают отказ
  • 5.7 Тест: запрос с кукой и заголовком разом проходит по заголовку
  • 5.8 Тест: ссылка на файл записи без сессии отдаёт отказ, а с сессией — тот же файл
  • 5.9 task gate зелёный целиком

6. Документация

  • 6.1 docs/security.md: первая строка периметра переписана под новый периметр; названо новое место жизни секрета клиента — база; в разделе «Что разграничивает доступ» записано, что допуск держит правило провайдера вне репозитория, а сервис своей проверки не делает
  • 6.2 docs/architecture.md: capability access внесена в перечень
  • 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 до чтения артефактов; сюда переносятся пункты, ставшие приёмочными сверх критериев задачи.

  • Отказ без сессии наступает раньше чтения тела и раньше обращения к хранилищу.
  • Форма отказа одна и та же у существующего и несуществующего ресурса.
  • Ни одно значение, дающее доступ, не печатается: код провайдера, секрет клиента, значение сессии, адрес почты.
  • Правило доступа читается как «всё требует сессии, кроме перечня», а перечень открытого живёт в одном месте.
  • Все прочие способы получить сессию к тому же субъекту выключены либо названы поимённо с обоснованием, почему они не обход.
  • Возврат от провайдера отвергается без состояния, с чужим, с истёкшим и с уже употреблённым — до обмена кода.
  • У обращения к провайдеру есть таймаут, и «медленный» отличается от «отказал».
  • Исход входа и выхода не зависит от порядка параллельных операций.