Проверил PR #51 на коммите dcf62c882f. Найдено блокирующее замечание по безопасности: при одновременном запросе восстановления пароля и смене email старый адрес получает новый действующий токен после отзыва прежних. Сценарий воспроизведён на PostgreSQL 16.13. После исправления прошу добавить регрессионный тест на эту последовательность. Backend: build и 38/38 тестов прошли; frontend: build и tsc --noEmit прошли.
Проверил PR #51 на коммите dcf62c882f72802b8a7abe1ec67f9920594e932f. Найдено блокирующее замечание по безопасности: при одновременном запросе восстановления пароля и смене email старый адрес получает новый действующий токен после отзыва прежних. Сценарий воспроизведён на PostgreSQL 16.13. После исправления прошу добавить регрессионный тест на эту последовательность. Backend: build и 38/38 тестов прошли; frontend: build и tsc --noEmit прошли.
P1 — отзыв ссылок при смене email не защищён от параллельного запроса восстановления. requestPasswordReset() сначала читает пользователя по старому адресу через findUserByEmail(), затем в отдельной транзакции вызывает createResetToken(). Если чтение произошло до этого PATCH, а создание токена — после COMMIT, строка 132 не отзовёт новый токен: ссылка будет отправлена на старый адрес и сможет сменить пароль аккаунта с новым email. На PostgreSQL 16.13 воспроизвёл состояние email=race-new@example.com, email_verified_at=NULL, active_reset_tokens=1 после такой последовательности. Аналогичная гонка есть у resendVerification(): старый адрес может получить действующий токен подтверждения нового email. Нужно сериализовать выбор пользователя и выпуск токена с этой сменой email (например, блокировка строки users с повторной проверкой адреса внутри транзакции) и проверить оба сценария тестами.
P1 — отзыв ссылок при смене email не защищён от параллельного запроса восстановления. `requestPasswordReset()` сначала читает пользователя по старому адресу через `findUserByEmail()`, затем в отдельной транзакции вызывает `createResetToken()`. Если чтение произошло до этого PATCH, а создание токена — после COMMIT, строка 132 не отзовёт новый токен: ссылка будет отправлена на старый адрес и сможет сменить пароль аккаунта с новым email. На PostgreSQL 16.13 воспроизвёл состояние `email=race-new@example.com`, `email_verified_at=NULL`, `active_reset_tokens=1` после такой последовательности. Аналогичная гонка есть у `resendVerification()`: старый адрес может получить действующий токен подтверждения нового email. Нужно сериализовать выбор пользователя и выпуск токена с этой сменой email (например, блокировка строки users с повторной проверкой адреса внутри транзакции) и проверить оба сценария тестами.
Дополнение после проверки всей админки в Chromium: обнаружен дефект мобильной вёрстки. Доступ и обычный отзыв уже выданных сессий проверены отдельно, работают; блокировки аккаунта как операции в PR нет.
Дополнение после проверки всей админки в Chromium: обнаружен дефект мобильной вёрстки. Доступ и обычный отзыв уже выданных сессий проверены отдельно, работают; блокировки аккаунта как операции в PR нет.
P2 — на мобильном экране таблица растягивает всю страницу вместо локальной прокрутки. В Chromium при viewport 390 px document.documentElement.scrollWidth = 1108 px, контейнер .admin-users__table-scroll = 1026 px, поэтому overflow-x: auto не срабатывает: весь интерфейс, шапка и футер уезжают вправо. Причина — минимальная ширина таблицы 64rem в сочетании с автоматической минимальной шириной grid-элемента .app-shell__main. Проверка с grid-template-columns: minmax(0,1fr) у .app-shell и min-width:0 у main/header возвращает ширину документа к 390 px, таблица прокручивается внутри контейнера. Нужен эквивалентный фикс мобильной раскладки.
P2 — на мобильном экране таблица растягивает всю страницу вместо локальной прокрутки. В Chromium при viewport 390 px `document.documentElement.scrollWidth` = 1108 px, контейнер `.admin-users__table-scroll` = 1026 px, поэтому `overflow-x: auto` не срабатывает: весь интерфейс, шапка и футер уезжают вправо. Причина — минимальная ширина таблицы 64rem в сочетании с автоматической минимальной шириной grid-элемента `.app-shell__main`. Проверка с `grid-template-columns: minmax(0,1fr)` у `.app-shell` и `min-width:0` у main/header возвращает ширину документа к 390 px, таблица прокручивается внутри контейнера. Нужен эквивалентный фикс мобильной раскладки.
P2 — заявленный production fallback https://run.vakanaut.ru не сработает при документированном развёртывании. README и docker-compose.stack.yml предлагают скопировать .env.example в .env, а эта строка задаёт APP_BASE_URL=http://localhost:5173; config.ts берёт заданное значение раньше production fallback. В результате ссылки подтверждения email и сброса пароля в письмах будут вести на localhost, если оператор следует инструкции и меняет только перечисленные DB_* / CORS_ORIGIN. Нужно убрать значение из шаблона (оставить закомментированным) или явно требовать сменить APP_BASE_URL в шагах production-развёртывания.
P2 — заявленный production fallback `https://run.vakanaut.ru` не сработает при документированном развёртывании. README и docker-compose.stack.yml предлагают скопировать `.env.example` в `.env`, а эта строка задаёт `APP_BASE_URL=http://localhost:5173`; `config.ts` берёт заданное значение раньше production fallback. В результате ссылки подтверждения email и сброса пароля в письмах будут вести на localhost, если оператор следует инструкции и меняет только перечисленные DB_* / CORS_ORIGIN. Нужно убрать значение из шаблона (оставить закомментированным) или явно требовать сменить APP_BASE_URL в шагах production-развёртывания.
P2 — при SMTP_HOST без значения в development/test sendMail() теперь возвращает успех, не доставив и не сохранив ссылку. Регистрация отвечает 202, но новый пользователь остаётся без подтверждённого email, а loginUser() ему отказывает. Ранее этот путь выводил ссылку в dev-логи, поэтому локальный сценарий регистрации и ручной проверки админки регрессировал. Нужен работающий безопасный dev mail sink или явная ошибка конфигурации до принятия регистрации; токены при этом не должны попадать в production-логи.
P2 — при `SMTP_HOST` без значения в development/test `sendMail()` теперь возвращает успех, не доставив и не сохранив ссылку. Регистрация отвечает 202, но новый пользователь остаётся без подтверждённого email, а `loginUser()` ему отказывает. Ранее этот путь выводил ссылку в dev-логи, поэтому локальный сценарий регистрации и ручной проверки админки регрессировал. Нужен работающий безопасный dev mail sink или явная ошибка конфигурации до принятия регистрации; токены при этом не должны попадать в production-логи.
Проверил diff PR #51 на коммите 1ce4349: блокирующих замечаний нет. Смена email сериализуется с выдачей reset/resend токенов через блокировку user row; регрессионный тест покрывает этот сценарий. Проверил mail logging, mobile CSS, APP_BASE_URL, версии и changelog. Backend integration tests не запускались: в среде нет подключения к PostgreSQL.
Проверил diff PR #51 на коммите 1ce4349: блокирующих замечаний нет. Смена email сериализуется с выдачей reset/resend токенов через блокировку user row; регрессионный тест покрывает этот сценарий. Проверил mail logging, mobile CSS, APP_BASE_URL, версии и changelog. Backend integration tests не запускались: в среде нет подключения к PostgreSQL.
Повторно проверил PR #51 на коммите 1ce43495dd. Все четыре прежних замечания устранены: выдача reset/resend токенов сериализована со сменой email, мобильная таблица прокручивается внутри страницы, APP_BASE_URL в шаблоне не перекрывает production fallback, ссылка доступна в development без SMTP. Проверки: backend build; 39/39 интеграционных тестов на PostgreSQL 16; frontend production build и TypeScript; Chromium 1280/390 px, обычный пользователь перенаправляется с /admin. Новых блокирующих замечаний нет.
Повторно проверил PR #51 на коммите 1ce43495dd08db87902a6ccb9a5241464fa82492. Все четыре прежних замечания устранены: выдача reset/resend токенов сериализована со сменой email, мобильная таблица прокручивается внутри страницы, APP_BASE_URL в шаблоне не перекрывает production fallback, ссылка доступна в development без SMTP. Проверки: backend build; 39/39 интеграционных тестов на PostgreSQL 16; frontend production build и TypeScript; Chromium 1280/390 px, обычный пользователь перенаправляется с /admin. Новых блокирующих замечаний нет.
admin
merged commit 0cf4459a6d into main2026-09-25 17:22:40 +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.
Adds an admin user management dashboard and supporting backend APIs, authentication updates, and migration.
Includes tests and documentation updates.
Проверил PR #51 на коммите
dcf62c882f. Найдено блокирующее замечание по безопасности: при одновременном запросе восстановления пароля и смене email старый адрес получает новый действующий токен после отзыва прежних. Сценарий воспроизведён на PostgreSQL 16.13. После исправления прошу добавить регрессионный тест на эту последовательность. Backend: build и 38/38 тестов прошли; frontend: build и tsc --noEmit прошли.@@ -0,0 +129,4 @@if (emailChanged) {await client.query("UPDATE sessions SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL", [id]);await client.query("UPDATE password_reset_tokens SET used_at = NOW() WHERE user_id = $1 AND used_at IS NULL", [id]);P1 — отзыв ссылок при смене email не защищён от параллельного запроса восстановления.
requestPasswordReset()сначала читает пользователя по старому адресу черезfindUserByEmail(), затем в отдельной транзакции вызываетcreateResetToken(). Если чтение произошло до этого PATCH, а создание токена — после COMMIT, строка 132 не отзовёт новый токен: ссылка будет отправлена на старый адрес и сможет сменить пароль аккаунта с новым email. На PostgreSQL 16.13 воспроизвёл состояниеemail=race-new@example.com,email_verified_at=NULL,active_reset_tokens=1после такой последовательности. Аналогичная гонка есть уresendVerification(): старый адрес может получить действующий токен подтверждения нового email. Нужно сериализовать выбор пользователя и выпуск токена с этой сменой email (например, блокировка строки users с повторной проверкой адреса внутри транзакции) и проверить оба сценария тестами.Дополнение после проверки всей админки в Chromium: обнаружен дефект мобильной вёрстки. Доступ и обычный отзыв уже выданных сессий проверены отдельно, работают; блокировки аккаунта как операции в PR нет.
@@ -2229,0 +2247,4 @@color: var(--color-error);}.admin-users__table-scroll {P2 — на мобильном экране таблица растягивает всю страницу вместо локальной прокрутки. В Chromium при viewport 390 px
document.documentElement.scrollWidth= 1108 px, контейнер.admin-users__table-scroll= 1026 px, поэтомуoverflow-x: autoне срабатывает: весь интерфейс, шапка и футер уезжают вправо. Причина — минимальная ширина таблицы 64rem в сочетании с автоматической минимальной шириной grid-элемента.app-shell__main. Проверка сgrid-template-columns: minmax(0,1fr)у.app-shellиmin-width:0у main/header возвращает ширину документа к 390 px, таблица прокручивается внутри контейнера. Нужен эквивалентный фикс мобильной раскладки.Дополнение к проверке конфигурации поставки.
@@ -31,2 +31,3 @@# APP_BASE_URL is the only source for verify/reset email links.# Базовый URL ссылок подтверждения и сброса пароля. В production по умолчанию# используется https://run.vakanaut.ru; задайте URL явно для другого домена.APP_BASE_URL=http://localhost:5173P2 — заявленный production fallback
https://run.vakanaut.ruне сработает при документированном развёртывании. README и docker-compose.stack.yml предлагают скопировать.env.exampleв.env, а эта строка задаётAPP_BASE_URL=http://localhost:5173;config.tsберёт заданное значение раньше production fallback. В результате ссылки подтверждения email и сброса пароля в письмах будут вести на localhost, если оператор следует инструкции и меняет только перечисленные DB_* / CORS_ORIGIN. Нужно убрать значение из шаблона (оставить закомментированным) или явно требовать сменить APP_BASE_URL в шагах production-развёртывания.Дополнение по локальному сценарию регистрации.
@@ -7,1 +7,4 @@throw new Error("SMTP is required in production to send email");}console.info("[mail] SMTP is not configured; email skipped.", { to, subject });return;P2 — при
SMTP_HOSTбез значения в development/testsendMail()теперь возвращает успех, не доставив и не сохранив ссылку. Регистрация отвечает 202, но новый пользователь остаётся без подтверждённого email, аloginUser()ему отказывает. Ранее этот путь выводил ссылку в dev-логи, поэтому локальный сценарий регистрации и ручной проверки админки регрессировал. Нужен работающий безопасный dev mail sink или явная ошибка конфигурации до принятия регистрации; токены при этом не должны попадать в production-логи.Проверил diff PR #51 на коммите
1ce4349: блокирующих замечаний нет. Смена email сериализуется с выдачей reset/resend токенов через блокировку user row; регрессионный тест покрывает этот сценарий. Проверил mail logging, mobile CSS, APP_BASE_URL, версии и changelog. Backend integration tests не запускались: в среде нет подключения к PostgreSQL.Повторно проверил PR #51 на коммите
1ce43495dd. Все четыре прежних замечания устранены: выдача reset/resend токенов сериализована со сменой email, мобильная таблица прокручивается внутри страницы, APP_BASE_URL в шаблоне не перекрывает production fallback, ссылка доступна в development без SMTP. Проверки: backend build; 39/39 интеграционных тестов на PostgreSQL 16; frontend production build и TypeScript; Chromium 1280/390 px, обычный пользователь перенаправляется с /admin. Новых блокирующих замечаний нет.