OTP-код, отправленный на чужой номер, указывает на ошибку сопоставления пользователя и challenge. Такой сценарий нужно остановить, потому что он может привести к захвату аккаунта или раскрытию факта регистрации.

Проверьте связь request_id, user_id и нормализованного номера на каждом этапе: создание challenge, запись в кэш, постановка SMS в очередь и проверка ответа.

Коротко: что сделать

  • Отключить затронутый сценарий при подтвержденной путанице
  • Зафиксировать request_id и время
  • Проверить нормализацию телефона в E.164
  • Проверить ключ OTP в кэше и payload очереди
  • Инвалидировать потенциально перепутанные коды

Почему возникает проблема

Код должен быть привязан к конкретному challenge, пользователю, каналу и сроку. Использование телефона как единственного ключа часто создает гонки и коллизии.

  • Два параллельных запроса перезаписывают общий ключ кэша
  • Номер нормализуется по-разному в профиле и OTP-сервисе
  • Очередь повторно использует изменяемый объект payload
  • Телефон другого пользователя остался в сессии
  • Провайдер получает неверный template parameter
  • Проверка кода не сверяет владельца challenge

Пошаговая диагностика

Проверку лучше проводить на одном воспроизводимом примере и фиксировать результат каждого шага. Так можно быстро отделить первопричину от побочных ошибок и не менять несколько компонентов одновременно.

  • Проследить один request_id через все сервисы
  • Сравнить номер до и после нормализации
  • Проверить уникальность ключей Redis
  • Проверить повторные доставки сообщений очереди
  • Проверить смену номера и повторную отправку
  • Проверить маскирование номера в интерфейсе

Как исправить

Нужно сделать OTP challenge неизменяемой записью с явным владельцем и проверять его при подтверждении.

  • Создавать случайный challenge_id для каждой попытки
  • Хранить hash кода вместе с user_id и нормализованным номером
  • Передавать в очередь неизменяемый payload
  • Ограничить число попыток и повторных отправок
  • Инвалидировать предыдущий challenge по понятному правилу
  • Добавить серверную проверку владельца при подтверждении

Как проверить результат

  • Запустить параллельные запросы двух пользователей
  • Проверить смену номера между запросами
  • Проверить повторную доставку одного сообщения
  • Убедиться, что чужой challenge не подтверждается
  • Проверить отсутствие полного телефона и кода в логах

Как не допустить повторения

OTP следует проектировать как короткоживущую транзакцию, а не как поле пользователя.

  • Использовать correlation ID
  • Добавить тесты гонок и повторной доставки
  • Маскировать телефоны и коды в логах
  • Настроить алерт на аномальные отправки

Чего не стоит делать

  • Не хранить OTP в открытом виде
  • Не использовать один глобальный ключ current_otp
  • Не подтверждать код без user_id или challenge_id
  • Не показывать полный чужой номер в сообщении об ошибке

Что подготовить для диагностики

  • Request ID проблемной попытки
  • Обезличенная цепочка логов
  • Схема OTP challenge
  • Правила нормализации телефонов
  • Настройки очереди и повторов

Частые вопросы

Можно ли привязать код только к номеру?

Лучше привязывать к challenge и пользователю. Один номер может меняться, переиспользоваться или участвовать в параллельных попытках.

Нужно ли хранить сам код?

Безопаснее хранить его hash с коротким TTL и сравнивать полученное значение.

Почему ошибка появляется редко?

Редкие случаи часто связаны с гонкой запросов, повторной доставкой очереди или общим ключом кэша.

Когда стоит обратиться за помощью

Обращайтесь за помощью, если ошибка произошла в production, затрагивает авторизацию или нужно проверить распределенную цепочку API, Redis, очередь и SMS-провайдера.

Итог

Надежный OTP связывает код с уникальным challenge, пользователем и номером и устойчив к параллельным запросам. Разобрать такую цепочку можно через @rabotator_support.