Добавлены учётные записи в БД с ролями «Администратор» и «Пользователь». Администратор на /admin/users создаёт пользователей и администраторов, меняет роль/доступ, выдаёт автоматически сгенерированный временный пароль и сбрасывает пароль. Видны первый/последний вход, дата собственной смены пароля и выдачи временного пароля. Отключение сохраняет историю. Собственную роль/доступ и свой пароль через административный сброс изменить нельзя; для собственного пароля есть личный кабинет.
Пользователь может просматривать служебные страницы и редактировать карточки. Запуск парсинга и управление учётными записями разрешены только администратору: защита на сервере охватывает HTML и API, обновление одного сотрудника и проверку уволенных. Кнопки скрываются по роли. Публичный просмотр диссовета и XLSX сохранён по выбору пользователя; подписи входа обновлены на «Вход в систему».
Временный пароль — 8 символов с обязательной строчной/заглавной буквой, цифрой и символом; генератор использует secrets. Он показывается администратору только в ответе на создание/сброс, не сохраняется открытым текстом и не попадает в историю. При первом успешном входе и после сброса требуется обязательная смена пароля: native dialog с удержанием фокуса, Escape/фон не закрывают, разрешены смена или выход. Без JavaScript доступна обычная форма; сервер блокирует авторизованные операции до смены. Постоянный пароль — 15–128 символов, поддерживаются фразы и Unicode, для смены требуется текущий пароль и совпадающее подтверждение.
/admin/account для обеих ролей показывает первый/последний вход, смену пароля и свои последние действия, включая события своей учётной записи. Администратор видит общую историю либо фильтр по пользователю. Журнал фиксирует успешные входы, смену/сброс пароля, роли/доступ и реальные изменения карточек: сотрудник, статья, год, прежние/новые значения названия, журнала, квартиля и специальностей. Автор и правка сохраняются в одной транзакции; кон��ликт ревизии, ошибка сохранения и неизменённое сохранение не создают запись редактирования. История — ��траницы по 30, индексы для автора и целевой учётной записи.
Пароли — PBKDF2-HMAC-SHA256, 600 000 итераций, случайная соль. Signed cookie имеет срок 12 часов; роль/активность/версия сессии проверяются по БД. Смена/сброс пароля, роль/доступ и выход отзывают предыдущие сессии. POST-формы защищены CSRF-токеном и Origin; JSON-редактор сохраняет custom-header защиту при отключённом CORS. Cookie HttpOnly/SameSite/Secure при HTTPS (включая HTTPS за reverse proxy), приватные страницы и одноразовые пароли — Cache-Control: no-store. После пяти неверных входов новые входы блокируются на 15 минут; сброс снимает ограничение. Изменения доступа сериализованы: параллельные администраторы не могут одновременно лишить систему всех администраторов.
Версия frontend/backend 1.2.1. Новых зависимостей нет. Добавлена migrations/013_system_users.sql для system_users/user_actions; штатный startup создаёт таблицы и первоначального администратора из текущей конфигурации, если записи нет. Его первый реальный вход после обновления также требует смены пароля; затем значение ADMIN_PASSWORD не заменяет пароль в БД и не служит резервным входом. После обновления нужен повторный вход, старые cookie не принимаются. Перед обновлением рабочей БД требуется штатная резервная копия. Первые входы и аудит отсчитываются с этого обновления: прежнюю историю восстановить нельзя. Production не изменялся и не перезапускался.
Проверки: полный pytest — 392 passed, 11 skipped (существующие PostgreSQL tests и новый concurrency-тест без отдельного test URL). Отдельно tests/test_users.py на изолированном PostgreSQL 16 в tmpfs — 17 passed, включая конкурентное понижение ролей; SQL-миграция выполнена, наборы колонок сверены с ORM. Тестовый контейнер удалён, рабочие БД/тома не затрагивались. Три browser-сценария (users, council, editor) passed. Новый users-сценарий использует отдельный локальный HTTP-сервер и синтетическую SQLite, проверяет создание, вход, обязательный попап/клавиатуру, смену, регулярное редактирование, историю, отсутствие кнопок парсинга, reset и размеры 1440/320 px; временные пароли в скриншотах скрыты. Проверены CSRF/cross-origin, истечение и отзыв сессий, неактивные аккаунты, запрет пяти путей парсинга, приватность личной истории, rollback изменения пароля/аудита, отсутствие plaintext в истории и no-op audit. node --check для трёх JS, sh -n backup-скриптов и git diff --cached --check passed; UI detector без замечаний.
Рабочее дерево чистое. Готово к независимому ревью в отдельной сессии; changelog оставлен до предусмотренного шага. Слияние и развёртывание не выполняются.
Исправление замечания P2 из ревью: при первом неверном пароле после окончания блокировки её истёкшая отметка очищается вместе с предыдущим счётчиком. Новые ошибки накапливаются и снова блокируют вход на 15 минут после пяти попыток. Расширен regression-тест: новая серия ошибок, повторная блокировка, отказ с правильным паролем во время неё и успешный вход со сбросом состояния после истечения. Тест воспроизвёл дефект до исправления. Повторные проверки на 1.2.1: pytest — 392 passed, 11 skipped; node --check трёх JS, sh -n двух backup-скриптов, git diff --cached --check passed. PostgreSQL и браузерные сценарии для этого исправления повторно не запускались; результаты выше относятся к исходной реализации 1.2.0.
Добавлены учётные записи в БД с ролями «Администратор» и «Пользователь». Администратор на /admin/users создаёт пользователей и администраторов, меняет роль/доступ, выдаёт автоматически сгенерированный временный пароль и сбрасывает пароль. Видны первый/последний вход, дата собственной смены пароля и выдачи временного пароля. Отключение сохраняет историю. Собственную роль/доступ и свой пароль через административный сброс изменить нельзя; для собственного пароля есть личный кабинет.
Пользователь может просматривать служебные страницы и редактировать карточки. Запуск парсинга и управление учётными записями разрешены только администратору: защита на сервере охватывает HTML и API, обновление одного сотрудника и проверку уволенных. Кнопки скрываются по роли. Публичный просмотр диссовета и XLSX сохранён по выбору пользователя; подписи входа обновлены на «Вход в систему».
Временный пароль — 8 символов с обязательной строчной/заглавной буквой, цифрой и символом; генератор использует secrets. Он показывается администратору только в ответе на создание/сброс, не сохраняется открытым текстом и не попадает в историю. При первом успешном входе и после сброса требуется обязательная смена пароля: native dialog с удержанием фокуса, Escape/фон не закрывают, разрешены смена или выход. Без JavaScript доступна обычная форма; сервер блокирует авторизованные операции до смены. Постоянный пароль — 15–128 символов, поддерживаются фразы и Unicode, для смены требуется текущий пароль и совпадающее подтверждение.
/admin/account для обеих ролей показывает первый/последний вход, смену пароля и свои последние действия, включая события своей учётной записи. Администратор видит общую историю либо фильтр по пользователю. Журнал фиксирует успешные входы, смену/сброс пароля, роли/доступ и реальные изменения карточек: сотрудник, статья, год, прежние/новые значения названия, журнала, квартиля и специальностей. Автор и правка сохраняются в одной транзакции; кон��ликт ревизии, ошибка сохранения и неизменённое сохранение не создают запись редактирования. История — ��траницы по 30, индексы для автора и целевой учётной записи.
Пароли — PBKDF2-HMAC-SHA256, 600 000 итераций, случайная соль. Signed cookie имеет срок 12 часов; роль/активность/версия сессии проверяются по БД. Смена/сброс пароля, роль/доступ и выход отзывают предыдущие сессии. POST-формы защищены CSRF-токеном и Origin; JSON-редактор сохраняет custom-header защиту при отключённом CORS. Cookie HttpOnly/SameSite/Secure при HTTPS (включая HTTPS за reverse proxy), приватные страницы и одноразовые пароли — Cache-Control: no-store. После пяти неверных входов новые входы блокируются на 15 минут; сброс снимает ограничение. Изменения доступа сериализованы: параллельные администраторы не могут одновременно лишить систему всех администраторов.
Версия frontend/backend 1.2.1. Новых зависимостей нет. Добавлена migrations/013_system_users.sql для system_users/user_actions; штатный startup создаёт таблицы и первоначального администратора из текущей конфигурации, если записи нет. Его первый реальный вход после обновления также требует смены пароля; затем значение ADMIN_PASSWORD не заменяет пароль в БД и не служит резервным входом. После обновления нужен повторный вход, старые cookie не принимаются. Перед обновлением рабочей БД требуется штатная резервная копия. Первые входы и аудит отсчитываются с этого обновления: прежнюю историю восстановить нельзя. Production не изменялся и не перезапускался.
Проверки: полный pytest — 392 passed, 11 skipped (существующие PostgreSQL tests и новый concurrency-тест без отдельного test URL). Отдельно tests/test_users.py на изолированном PostgreSQL 16 в tmpfs — 17 passed, включая конкурентное понижение ролей; SQL-миграция выполнена, наборы колонок сверены с ORM. Тестовый контейнер удалён, рабочие БД/тома не затрагивались. Три browser-сценария (users, council, editor) passed. Новый users-сценарий использует отдельный локальный HTTP-сервер и синтетическую SQLite, проверяет создание, вход, обязательный попап/клавиатуру, смену, регулярное редактирование, историю, отсутствие кнопок парсинга, reset и размеры 1440/320 px; временные пароли в скриншотах скрыты. Проверены CSRF/cross-origin, истечение и отзыв сессий, неактивные аккаунты, запрет пяти путей парсинга, приватность личной истории, rollback изменения пароля/аудита, отсутствие plaintext в истории и no-op audit. node --check для трёх JS, sh -n backup-скриптов и git diff --cached --check passed; UI detector без замечаний.
Рабочее дерево чистое. Готово к независимому ревью в отдельной сессии; changelog оставлен до предусмотренного шага. Слияние и развёртывание не выполняются.
Исправление замечания P2 из ревью: при первом неверном пароле после окончания блокировки её истёкшая отметка очищается вместе с предыдущим счётчиком. Новые ошибки накапливаются и снова блокируют вход на 15 минут после пяти попыток. Расширен regression-тест: новая серия ошибок, повторная блокировка, отказ с правильным паролем во время неё и успешный вход со сбросом состояния после истечения. Тест воспроизвёл дефект до исправления. Повторные проверки на 1.2.1: pytest — 392 passed, 11 skipped; node --check трёх JS, sh -n двух backup-скриптов, git diff --cached --check passed. PostgreSQL и браузерные сценарии для этого исправления повторно не запускались; результаты выше относятся к исходной реализации 1.2.0.
Независимое ревью HEAD a82f63d6cb: замечание P2 к повторному включению ограничения неверных входов после истечения блокировки. Подробности в комментарии к security.py.
Воспроизведено на authenticate из текущего PR с синтетической учётной записью, фиксируемыми часами и изолированным DB-объектом: первые 5 неверных паролей дают failed_login_attempts=5 и блокировку. После продвижения времени на 16 минут каждая из следующих 6 неверных попыток сохраняет failed_login_attempts=1 и старый expired locked_until; повторной блокировки нет. Никакие реальные пароли, аккаунты или БД не использовались.
Проверены вход/первоначальная учётная запись, роли HTML/API, обязательная смена пароля, CSRF и Origin, отзыв сессий, административные блокировки, история и транзакция редактирования, новая схема и шаблоны. Изолированные проверки на функциях PR прошли для PBKDF2 с Unicode-фразой, генератора временных паролей (20 значений, без вывода самих паролей), истечения подписанной сессии, изменения session_version, отключённого аккаунта, обязательной смены пароля и отказа обычному пользователю в require_admin. node --check для admin.js/council.js/users.js и git diff --check — успешно.
CI / test (pull_request) для текущего SHA — success (10m8s), production diagnostics штатно skipped. Полный pytest, реальные HTTP/browser и PostgreSQL-concurrency в этой reviewer-сессии не запускались: зависимости приложения/pytest отсутствуют. Существующий тест throttling проверяет успешный вход после истечения, который очищает locked_until; он не проверяет новую последовательность неверных паролей после истечения.
Origin совпадает с admin/miem_workers. Reviewer agent (id=7) независим от автора PR codex (id=6); Git-коммит authored/committed codex. HEAD непосредственно перед публикацией сверён. Правки, push, merge, миграции и операции с production не выполнялись.
Независимое ревью HEAD a82f63d6cb7443b286ce104e982c639b0d96c9b1: замечание P2 к повторному включению ограничения неверных входов после истечения блокировки. Подробности в комментарии к security.py.
Воспроизведено на authenticate из текущего PR с синтетической учётной записью, фиксируемыми часами и изолированным DB-объектом: первые 5 неверных паролей дают failed_login_attempts=5 и блокировку. После продвижения времени на 16 минут каждая из следующих 6 неверных попыток сохраняет failed_login_attempts=1 и старый expired locked_until; повторной блокировки нет. Никакие реальные пароли, аккаунты или БД не использовались.
Проверены вход/первоначальная учётная запись, роли HTML/API, обязательная смена пароля, CSRF и Origin, отзыв сессий, административные блокировки, история и транзакция редактирования, новая схема и шаблоны. Изолированные проверки на функциях PR прошли для PBKDF2 с Unicode-фразой, генератора временных паролей (20 значений, без вывода самих паролей), истечения подписанной сессии, изменения session_version, отключённого аккаунта, обязательной смены пароля и отказа обычному пользователю в require_admin. node --check для admin.js/council.js/users.js и git diff --check — успешно.
CI / test (pull_request) для текущего SHA — success (10m8s), production diagnostics штатно skipped. Полный pytest, реальные HTTP/browser и PostgreSQL-concurrency в этой reviewer-сессии не запускались: зависимости приложения/pytest отсутствуют. Существующий тест throttling проверяет успешный вход после истечения, который очищает locked_until; он не проверяет новую последовательность неверных паролей после истечения.
Origin совпадает с admin/miem_workers. Reviewer agent (id=7) независим от автора PR codex (id=6); Git-коммит authored/committed codex. HEAD непосредственно перед публикацией сверён. Правки, push, merge, миграции и операции с production не выполнялись.
[P2] Очищайте истёкшую блокировку один раз, иначе новые ошибки входа перестают накапливаться. locked_until остаётся заполненным после окончания 15 минут. При каждом неверном пароле эта ветка снова присваивает failed_login_attempts=0 и затем увеличивает его только до 1. Воспроизведение: 5 неверных входов → блокировка; продвинуть время на 16 минут; ещё 6 неверных входов → на каждом failed_login_attempts=1, locked_until всё ещё прежний, повторной блокировки нет. Таким образом, после первой паузы можно продолжать подбор без новой блокировки до успешного входа или административного сброса. При окончании периода нужно однократно очистить user.locked_until и сбросить предыдущую серию, затем накапливать новую серию ошибок под текущей блокировкой строки. Добавьте тест: после истечения блокировки очередные пять неверных паролей создают новую блокировку, а правильный пароль в течение неё также отклоняется. Текущий тест сразу выполняет успешный вход после истечения и не выявляет дефект.
[P2] Очищайте истёкшую блокировку один раз, иначе новые ошибки входа перестают накапливаться. locked_until остаётся заполненным после окончания 15 минут. При каждом неверном пароле эта ветка снова присваивает failed_login_attempts=0 и затем увеличивает его только до 1. Воспроизведение: 5 неверных входов → блокировка; продвинуть время на 16 минут; ещё 6 неверных входов → на каждом failed_login_attempts=1, locked_until всё ещё прежний, повторной блокировки нет. Таким образом, после первой паузы можно продолжать подбор без новой блокировки до успешного входа или административного сброса. При окончании периода нужно однократно очистить user.locked_until и сбросить предыдущую серию, затем накапливать новую серию ошибок под текущей блокировкой строки. Добавьте тест: после истечения блокировки очередные пять неверных паролей создают новую блокировку, а правильный пароль в течение неё также отклоняется. Текущий тест сразу выполняет успешный вход после истечения и не выявляет дефект.
Повторное независимое ревью HEAD 96987e90c9: замечание P2 из review #179 устранено. В исправлении новых блокирующих замечаний нет.
При первой неверной попытке после истечения блокировки user.locked_until очищается вместе со старым счётчиком. Следующие ошибки накапливаются, и пятая снова устанавливает 15-минутную блокировку. Проверка активной блокировки перед изменением состояния сохранена.
Просмотрен весь diff после a82f63d. На authenticate текущего PR в памяти, с синтетическим аккаунтом, управляемым временем и изолированным DB-объектом, прошли три последовательных цикла: пять ошибок → блокировка; правильный пароль во время блокировки отклоняется; после истечения новая серия ошибок накапливается от 1 до 5 и вновь блокирует вход. Успешный вход после последнего истечения обнуляет счётчик и locked_until. Реальные пароли/аккаунты/БД не использовались.
Регрессионный тест в tests/test_users.py теперь проверяет именно повторную серию неверных входов после истечения, отказ правильному паролю в новой блокировке и успешный вход после её окончания. git diff --check — успешно. Версии согласованно повышены до 1.2.1. CI / test (pull_request) текущего SHA — success (10m10s), production diagnostics штатно skipped. Полный pytest/HTTP/PostgreSQL и браузер локально не запускались из-за отсутствия зависимостей; изолированный сценарий проверял логику функции, не реальную блокировку строки БД.
Origin и независимость аккаунта повторно проверены: admin/miem_workers, reviewer agent (id=7), автор PR codex (id=6); новый Git-коммит authored/committed codex. HEAD непосредственно перед публикацией сверён. Правки, push, merge, миграции и операции с production не выполнялись.
Повторное независимое ревью HEAD 96987e90c92bd52cc4be8ebd12ff462f013410a8: замечание P2 из review #179 устранено. В исправлении новых блокирующих замечаний нет.
При первой неверной попытке после истечения блокировки user.locked_until очищается вместе со старым счётчиком. Следующие ошибки накапливаются, и пятая снова устанавливает 15-минутную блокировку. Проверка активной блокировки перед изменением состояния сохранена.
Просмотрен весь diff после a82f63d. На authenticate текущего PR в памяти, с синтетическим аккаунтом, управляемым временем и изолированным DB-объектом, прошли три последовательных цикла: пять ошибок → блокировка; правильный пароль во время блокировки отклоняется; после истечения новая серия ошибок накапливается от 1 до 5 и вновь блокирует вход. Успешный вход после последнего истечения обнуляет счётчик и locked_until. Реальные пароли/аккаунты/БД не использовались.
Регрессионный тест в tests/test_users.py теперь проверяет именно повторную серию неверных входов после истечения, отказ правильному паролю в новой блокировке и успешный вход после её окончания. git diff --check — успешно. Версии согласованно повышены до 1.2.1. CI / test (pull_request) текущего SHA — success (10m10s), production diagnostics штатно skipped. Полный pytest/HTTP/PostgreSQL и браузер локально не запускались из-за отсутствия зависимостей; изолированный сценарий проверял логику функции, не реальную блокировку строки БД.
Origin и независимость аккаунта повторно проверены: admin/miem_workers, reviewer agent (id=7), автор PR codex (id=6); новый Git-коммит authored/committed codex. HEAD непосредственно перед публикацией сверён. Правки, push, merge, миграции и операции с production не выполнялись.
admin
merged commit ccebaddfda into main2026-10-10 18:11:57 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Добавлены учётные записи в БД с ролями «Администратор» и «Пользователь». Администратор на /admin/users создаёт пользователей и администраторов, меняет роль/доступ, выдаёт автоматически сгенерированный временный пароль и сбрасывает пароль. Видны первый/последний вход, дата собственной смены пароля и выдачи временного пароля. Отключение сохраняет историю. Собственную роль/доступ и свой пароль через административный сброс изменить нельзя; для собственного пароля есть личный кабинет.
Пользователь может просматривать служебные страницы и редактировать карточки. Запуск парсинга и управление учётными записями разрешены только администратору: защита на сервере охватывает HTML и API, обновление одного сотрудника и проверку уволенных. Кнопки скрываются по роли. Публичный просмотр диссовета и XLSX сохранён по выбору пользователя; подписи входа обновлены на «Вход в систему».
Временный пароль — 8 символов с обязательной строчной/заглавной буквой, цифрой и символом; генератор использует secrets. Он показывается администратору только в ответе на создание/сброс, не сохраняется открытым текстом и не попадает в историю. При первом успешном входе и после сброса требуется обязательная смена пароля: native dialog с удержанием фокуса, Escape/фон не закрывают, разрешены смена или выход. Без JavaScript доступна обычная форма; сервер блокирует авторизованные операции до смены. Постоянный пароль — 15–128 символов, поддерживаются фразы и Unicode, для смены требуется текущий пароль и совпадающее подтверждение.
/admin/account для обеих ролей показывает первый/последний вход, смену пароля и свои последние действия, включая события своей учётной записи. Администратор видит общую историю либо фильтр по пользователю. Журнал фиксирует успешные входы, смену/сброс пароля, роли/доступ и реальные изменения карточек: сотрудник, статья, год, прежние/новые значения названия, журнала, квартиля и специальностей. Автор и правка сохраняются в одной транзакции; кон��ликт ревизии, ошибка сохранения и неизменённое сохранение не создают запись редактирования. История — ��траницы по 30, индексы для автора и целевой учётной записи.
Пароли — PBKDF2-HMAC-SHA256, 600 000 итераций, случайная соль. Signed cookie имеет срок 12 часов; роль/активность/версия сессии проверяются по БД. Смена/сброс пароля, роль/доступ и выход отзывают предыдущие сессии. POST-формы защищены CSRF-токеном и Origin; JSON-редактор сохраняет custom-header защиту при отключённом CORS. Cookie HttpOnly/SameSite/Secure при HTTPS (включая HTTPS за reverse proxy), приватные страницы и одноразовые пароли — Cache-Control: no-store. После пяти неверных входов новые входы блокируются на 15 минут; сброс снимает ограничение. Изменения доступа сериализованы: параллельные администраторы не могут одновременно лишить систему всех администраторов.
Версия frontend/backend 1.2.1. Новых зависимостей нет. Добавлена migrations/013_system_users.sql для system_users/user_actions; штатный startup создаёт таблицы и первоначального администратора из текущей конфигурации, если записи нет. Его первый реальный вход после обновления также требует смены пароля; затем значение ADMIN_PASSWORD не заменяет пароль в БД и не служит резервным входом. После обновления нужен повторный вход, старые cookie не принимаются. Перед обновлением рабочей БД требуется штатная резервная копия. Первые входы и аудит отсчитываются с этого обновления: прежнюю историю восстановить нельзя. Production не изменялся и не перезапускался.
Проверки: полный pytest — 392 passed, 11 skipped (существующие PostgreSQL tests и новый concurrency-тест без отдельного test URL). Отдельно tests/test_users.py на изолированном PostgreSQL 16 в tmpfs — 17 passed, включая конкурентное понижение ролей; SQL-миграция выполнена, наборы колонок сверены с ORM. Тестовый контейнер удалён, рабочие БД/тома не затрагивались. Три browser-сценария (users, council, editor) passed. Новый users-сценарий использует отдельный локальный HTTP-сервер и синтетическую SQLite, проверяет создание, вход, обязательный попап/клавиатуру, смену, регулярное редактирование, историю, отсутствие кнопок парсинга, reset и размеры 1440/320 px; временные пароли в скриншотах скрыты. Проверены CSRF/cross-origin, истечение и отзыв сессий, неактивные аккаунты, запрет пяти путей парсинга, приватность личной истории, rollback изменения пароля/аудита, отсутствие plaintext в истории и no-op audit. node --check для трёх JS, sh -n backup-скриптов и git diff --cached --check passed; UI detector без замечаний.
Рабочее дерево чистое. Готово к независимому ревью в отдельной сессии; changelog оставлен до предусмотренного шага. Слияние и развёртывание не выполняются.
Исправление замечания P2 из ревью: при первом неверном пароле после окончания блокировки её истёкшая отметка очищается вместе с предыдущим счётчиком. Новые ошибки накапливаются и снова блокируют вход на 15 минут после пяти попыток. Расширен regression-тест: новая серия ошибок, повторная блокировка, отказ с правильным паролем во время неё и успешный вход со сбросом состояния после истечения. Тест воспроизвёл дефект до исправления. Повторные проверки на 1.2.1: pytest — 392 passed, 11 skipped; node --check трёх JS, sh -n двух backup-скриптов, git diff --cached --check passed. PostgreSQL и браузерные сценарии для этого исправления повторно не запускались; результаты выше относятся к исходной реализации 1.2.0.
Независимое ревью HEAD
a82f63d6cb: замечание P2 к повторному включению ограничения неверных входов после истечения блокировки. Подробности в комментарии к security.py.Воспроизведено на authenticate из текущего PR с синтетической учётной записью, фиксируемыми часами и изолированным DB-объектом: первые 5 неверных паролей дают failed_login_attempts=5 и блокировку. После продвижения времени на 16 минут каждая из следующих 6 неверных попыток сохраняет failed_login_attempts=1 и старый expired locked_until; повторной блокировки нет. Никакие реальные пароли, аккаунты или БД не использовались.
Проверены вход/первоначальная учётная запись, роли HTML/API, обязательная смена пароля, CSRF и Origin, отзыв сессий, административные блокировки, история и транзакция редактирования, новая схема и шаблоны. Изолированные проверки на функциях PR прошли для PBKDF2 с Unicode-фразой, генератора временных паролей (20 значений, без вывода самих паролей), истечения подписанной сессии, изменения session_version, отключённого аккаунта, обязательной смены пароля и отказа обычному пользователю в require_admin. node --check для admin.js/council.js/users.js и git diff --check — успешно.
CI / test (pull_request) для текущего SHA — success (10m8s), production diagnostics штатно skipped. Полный pytest, реальные HTTP/browser и PostgreSQL-concurrency в этой reviewer-сессии не запускались: зависимости приложения/pytest отсутствуют. Существующий тест throttling проверяет успешный вход после истечения, который очищает locked_until; он не проверяет новую последовательность неверных паролей после истечения.
Origin совпадает с admin/miem_workers. Reviewer agent (id=7) независим от автора PR codex (id=6); Git-коммит authored/committed codex. HEAD непосредственно перед публикацией сверён. Правки, push, merge, миграции и операции с production не выполнялись.
@@ -47,0 +181,4 @@if not user.is_active or (locked_until and locked_until > now):return Noneif not valid:if locked_until:[P2] Очищайте истёкшую блокировку один раз, иначе новые ошибки входа перестают накапливаться. locked_until остаётся заполненным после окончания 15 минут. При каждом неверном пароле эта ветка снова присваивает failed_login_attempts=0 и затем увеличивает его только до 1. Воспроизведение: 5 неверных входов → блокировка; продвинуть время на 16 минут; ещё 6 неверных входов → на каждом failed_login_attempts=1, locked_until всё ещё прежний, повторной блокировки нет. Таким образом, после первой паузы можно продолжать подбор без новой блокировки до успешного входа или административного сброса. При окончании периода нужно однократно очистить user.locked_until и сбросить предыдущую серию, затем накапливать новую серию ошибок под текущей блокировкой строки. Добавьте тест: после истечения блокировки очередные пять неверных паролей создают новую блокировку, а правильный пароль в течение неё также отклоняется. Текущий тест сразу выполняет успешный вход после истечения и не выявляет дефект.
Повторное независимое ревью HEAD
96987e90c9: замечание P2 из review #179 устранено. В исправлении новых блокирующих замечаний нет.При первой неверной попытке после истечения блокировки user.locked_until очищается вместе со старым счётчиком. Следующие ошибки накапливаются, и пятая снова устанавливает 15-минутную блокировку. Проверка активной блокировки перед изменением состояния сохранена.
Просмотрен весь diff после
a82f63d. На authenticate текущего PR в памяти, с синтетическим аккаунтом, управляемым временем и изолированным DB-объектом, прошли три последовательных цикла: пять ошибок → блокировка; правильный пароль во время блокировки отклоняется; после истечения новая серия ошибок накапливается от 1 до 5 и вновь блокирует вход. Успешный вход после последнего истечения обнуляет счётчик и locked_until. Реальные пароли/аккаунты/БД не использовались.Регрессионный тест в tests/test_users.py теперь проверяет именно повторную серию неверных входов после истечения, отказ правильному паролю в новой блокировке и успешный вход после её окончания. git diff --check — успешно. Версии согласованно повышены до 1.2.1. CI / test (pull_request) текущего SHA — success (10m10s), production diagnostics штатно skipped. Полный pytest/HTTP/PostgreSQL и браузер локально не запускались из-за отсутствия зависимостей; изолированный сценарий проверял логику функции, не реальную блокировку строки БД.
Origin и независимость аккаунта повторно проверены: admin/miem_workers, reviewer agent (id=7), автор PR codex (id=6); новый Git-коммит authored/committed codex. HEAD непосредственно перед публикацией сверён. Правки, push, merge, миграции и операции с production не выполнялись.