- шаг схемы закрывает поверхность, которую хранилище приносит открытой: собственную регистрацию, вход по паролю и одноразовый код — без этого закрытие приёма обходилось двумя запросами - продление сессии выключено, срок семь суток: иначе отзыв доступа у провайдера до сервиса не доходит никогда - файл записи отдаётся вошедшему по токену файла — пересмотр ADR-2026-08-12-file-link-open-but-not-logged
122 lines
9.9 KiB
Markdown
122 lines
9.9 KiB
Markdown
## 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` до чтения артефактов; сюда переносятся пункты,
|
||
ставшие приёмочными сверх критериев задачи.
|
||
|
||
- Отказ без сессии наступает раньше чтения тела и раньше обращения к хранилищу.
|
||
- Форма отказа одна и та же у существующего и несуществующего ресурса.
|
||
- Ни одно значение, дающее доступ, не печатается: код провайдера, секрет
|
||
клиента, значение сессии, адрес почты.
|
||
- Правило доступа читается как «всё требует сессии, кроме перечня», а перечень
|
||
открытого живёт в одном месте.
|
||
- Все прочие способы получить сессию к тому же субъекту выключены либо названы
|
||
поимённо с обоснованием, почему они не обход.
|
||
- Возврат от провайдера отвергается без состояния, с чужим, с истёкшим и с уже
|
||
употреблённым — до обмена кода.
|
||
- У обращения к провайдеру есть таймаут, и «медленный» отличается от «отказал».
|
||
- Исход входа и выхода не зависит от порядка параллельных операций.
|