Skip to content

fix(customer): бессрочная блокировка, отзыв токенов и password_reset - #734

Merged
biz87 merged 2 commits into
betafrom
fix/issue-732-733-customer-block-token
Sep 17, 2026
Merged

biz87 merged 2 commits into
betafrom
fix/issue-732-733-customer-block-token

Conversation

@Ibochkarev

@Ibochkarev Ibochkarev commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Описание

Ручная блокировка из менеджера (is_blocked=1, пустой blocked_until) больше не снимается при входе. AuthManager и витринный checkAuth используют общий предикат CustomerAccess.

API-токен после блокировки или отключения не проходит resolveApiToken (401 на /api/v1/customer/*, token/refresh не выдаёт новый). PUT покупателя в менеджере при закрытии доступа отзывает все типы токенов и обнуляет blocked_until только если is_blocked реально меняется (форма шлёт весь объект).

Сброс пароля: в ENUM ms3_customer_tokens.type добавлен password_reset, константа TYPE_PASSWORD_RESET. ResetPassword снимает только timed lockout, не постоянную блокировку менеджера.

Тип изменений

  • Исправление бага (non-breaking change)
  • Новая функциональность (non-breaking change)
  • Breaking change (изменение, ломающее обратную совместимость)
  • Рефакторинг (без изменения функциональности)
  • Документация
  • Другое (опишите):

Связанные Issues

Closes #732
Closes #733
Closes #744

Как это было протестировано?

cd core/components/minishop3
php -l src/Services/Customer/CustomerAccess.php
php -l src/Services/Customer/AuthManager.php
php -l src/Controllers/Api/Manager/CustomersController.php
php -l src/Processors/Api/Customer/ForgotPassword.php
php -l src/Processors/Api/Customer/ResetPassword.php
php -l migrations/20260916120000_add_password_reset_to_customer_token_type.php
# exit 0

./vendor/bin/phpunit \
  tests/Unit/Services/Customer/CustomerAccessTest.php \
  tests/Unit/Controllers/Api/Manager/CustomersControllerUpdateTest.php \
  tests/Integration/Customer/AuthManagerLifecycleTest.php \
  tests/Integration/Customer/ResolveApiTokenTest.php
# exit 0 (45 tests, 148 assertions; после rebase повтор 36/130 на subset Access+Update+Lifecycle)

MySQL live (@group mysql / testbench): CustomerAuthPdoStore создаёт type как ENUM с password_reset; AuthManagerMysqlLifecycleTest наследует testCreateAndValidatePasswordResetToken.

  • Ручное тестирование
  • Автоматические тесты (composer ci:php / composer test, npm run lint:ci, composer stan / GitHub Actions CI)
  • Тестирование на разных версиях PHP/MODX

Конфигурация тестирования:

  • MiniShop3: fix/issue-732-733-customer-block-token от beta
  • MODX: n/a (PHPUnit)
  • PHP: 8.4

Скриншоты (если применимо)

До После
n/a n/a

Чеклист

  • Код соответствует стилю проекта
  • Добавлены/обновлены комментарии в сложных местах
  • Изменения не ломают существующую функциональность
  • Лексиконы добавлены на двух языках (ru/en) — не требуются
  • PHPStan проходит без новых ошибок (composer stan / CI job PHPStan)
  • ESLint проходит без ошибок (npm run lint:ci для Vue) — Vue не трогали
  • Обновлён CHANGELOG.md (для значимых изменений) — на релизе

Дополнительные заметки

Ответ на review @biz87: blocked_until не обнуляется при повторном PUT с тем же is_blocked; revoke без фильтра типа; ResetPassword → CustomerAccess::liftTimedLockout(). Phinx: 20260916120000_add_password_reset_to_customer_token_type.php.

@Ibochkarev Ibochkarev added priority: high Важно исправить в ближайшее время bug Something isn't working labels Sep 15, 2026
@Ibochkarev
Ibochkarev requested a review from biz87 September 15, 2026 10:51
@Ibochkarev
Ibochkarev force-pushed the fix/issue-732-733-customer-block-token branch from 10be867 to 73bb8e3 Compare September 15, 2026 16:39
@Ibochkarev

Copy link
Copy Markdown
Member Author

Перебазировал на beta после merge #677.

Конфликт только в imports CustomersController.php: оставлены msCustomerGroup (#677), msCustomerToken + CustomerAccess (#734). В update() сохранены и customer_group_id, и revoke через CustomerAccess.

PHPUnit: 39 tests, 135 assertions. Merge-tree против beta чистый.

@biz87

biz87 commented Sep 16, 2026

Copy link
Copy Markdown
Member

Проверил на dev (MariaDB, всё в транзакции с откатом): AuthManager, TokenService и CustomersController::update() из этого PR на живой базе. #732 и #733 закрыты:

  • ручная блокировка (is_blocked = 1, пустой blocked_until) не пускает и при попытке входа не снимается; временная блокировка с blocked_until в будущем не пускает; истёкшая — пускает и снимает флаги; неактивный покупатель не входит;
  • API-токен после блокировки и после отключения даёт missing, rotateApiToken() возвращает null, гостевой токен (customer_id = 0) работает;
  • блокировка из менеджера отзывает API-токены (2 → 0).

Возвращаю из-за одного побочного эффекта.

Правка покупателя делает временную блокировку бессрочной

update() обнуляет blocked_until, если в запросе есть ключ is_blocked:

if (array_key_exists('is_blocked', $data)) {
    $customer->set('blocked_until', null);

Форма менеджера (CustomersGrid.vue, saveCustomer()) отправляет покупателя целиком — { ...editingCustomer.value }, так что is_blocked приходит при каждом сохранении. Покупатель, которого AuthManager временно заблокировал после неудачных попыток входа, у менеджера отображается с отмеченным «Заблокирован». Менеджер правит ему телефон и сохраняет — blocked_until становится null, и по новым правилам блокировка уже бессрочная. Проверил на dev: is_blocked = 1, blocked_until через 15 минут, сохранение формы с новым телефоном → blocked_until = null.

Обнулять blocked_until стоит только когда is_blocked действительно меняется: при снятии блокировки (как сейчас) и при установке менеджером (чтобы остаток от прошлой временной блокировки не открыл доступ). Если значение не изменилось — не трогать.

Не блокер, но стоит закрыть сейчас

При блокировке из менеджера отзываются только API-токены. ResetPassword после смены пароля ставит is_blocked = false — то есть ссылка на сброс пароля, выданная до блокировки, сняла бы ручную блокировку. Сейчас этого не случится по другой причине: токены password_reset вообще не создаются — в ENUM ms3_customer_tokens.type нет такого значения, это #744. Но как только сброс пароля починят, обход заработает. Варианты: при блокировке менеджером отзывать токены всех типов, или в ResetPassword снимать только временную блокировку (CustomerAccess::isLockoutExpired() / непустой blocked_until), не трогая ручную.

Empty blocked_until is a permanent block and must not clear on login.
Token resolve and manager updates now share CustomerAccess so inactive
or blocked customers cannot keep or refresh API access.
ENUM lacked password_reset so ForgotPassword could not persist tokens
under STRICT_TRANS_TABLES. Clear blocked_until only when is_blocked
changes, revoke all token types on manager deny, and lift only timed
lockouts on successful ResetPassword.
@Ibochkarev Ibochkarev changed the title fix(customer): бессрочная блокировка и отзыв API-токена fix(customer): бессрочная блокировка, отзыв токенов и password_reset Sep 16, 2026
@Ibochkarev
Ibochkarev force-pushed the fix/issue-732-733-customer-block-token branch from 73bb8e3 to 20f919f Compare September 16, 2026 11:42
@Ibochkarev

Copy link
Copy Markdown
Member Author

@biz87 Спасибо за проверку на dev.

Побочный эффект с blocked_until исправлен: обнуляем только когда is_blocked реально меняется (0→1 или 1→0). Повторное сохранение формы с тем же is_blocked=1 timed lockout не превращает в бессрочный.

По обходу через reset после #744:

  1. при CustomerAccess::isAccessDenied после save отзываются все типы токенов, не только api;
  2. ResetPassword снимает только timed lockout (liftTimedLockout), постоянную блокировку менеджера не трогает.

В эту же ветку вошёл #744: ENUM password_reset, TYPE_PASSWORD_RESET, Phinx-миграция. Ветка перебазирована на актуальный beta.

@biz87
biz87 merged commit eab0f52 into beta Sep 17, 2026
6 checks passed
@Ibochkarev
Ibochkarev deleted the fix/issue-732-733-customer-block-token branch September 23, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: high Важно исправить в ближайшее время

Projects

None yet

2 participants