Состояние гонки в API-роуте Next.js
Одноразовый купон должен срабатывать только один раз.
Но в данном сценарии тридцать человек могут использовать его одновременно.
Это происходит из-за состояния гонки типа Time-of-Check Time-of-Use (TOCTOU).
Ошибка возникает в два этапа:
- Этап 1: Сервер считывает данные из базы, чтобы проверить количество использований.
- Этап 2: Сервер отправляет вторую команду для увеличения этого счетчика.
Если отправить множество запросов одновременно, сервер обработает их все в промежутке между этими двумя этапами.
Множество запросов считывают значение счетчика, пока оно всё еще равно нулю. Все они проходят проверку. Все они применяют скидку. Все они увеличивают счетчик.
Результат? Купон, предназначенный для одного человека, используется тридцать раз.
Это легко упустить во время ревью кода. Логика кажется идеальной, если читать её построчно. Изъян проявляется только при учете скорости и параллелизма.
Как это исправить:
Перестаньте использовать раздельные команды чтения и записи. Вместо этого используйте одну атомарную операцию.
В Prisma можно использовать updateMany с условием в предложении WHERE:
• База данных проверяет условие и выполняет обновление за один шаг. • Если счетчик уже достиг лимита, обновление немедленно отклоняется. • Ни один другой запрос не сможет «проскочить» между проверкой и записью.
Другой вариант — использование транзакции базы данных. Она блокирует данные, так что ни один другой запрос не сможет их изменить, пока вы не закончите.
Для SQLite наиболее надежным методом является одна команда обновления.
Всегда спрашивайте себя: что произойдет, если два запроса обратятся к этой логике в одну и ту же миллисекунду?
Источник: https://dev.to/oopssec-store/racing-a-nextjs-api-route-coupon-abuse-with-prisma-and-sqlite-3gma