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.