سباق في مسار Next.js API

يجب أن يعمل الكوبون المخصص للاستخدام لمرة واحدة مرة واحدة فقط.

ولكن في هذا السيناريو، يمكن لثلاثين شخصاً استخدامه في الوقت نفسه تماماً.

يحدث هذا بسبب حالة سباق من نوع Time-of-Check Time-of-Use (TOCTOU).

يحدث الخطأ في خطوتين:

  • الخطوة 1: يقرأ الخادم قاعدة البيانات للتحقق من عدد مرات الاستخدام.
  • الخطوة 2: يرسل الخادم أمراً ثانياً لزيادة ذلك العدد.

إذا أرسلت العديد من الطلبات في وقت واحد، فسيقوم الخادم بمعالجتها جميعاً في منتصف هاتين الخطوتين.

تقرأ العديد من الطلبات العدد بينما لا يزال صفراً. جميعها تجتاز التحقق. جميعها تطبق الخصم. وجميعها تزيد العداد.

النتيجة؟ كوبون مخصص لشخص واحد يُستخدم ثلاثين مرة.

من السهل إغفال هذا الأمر أثناء مراجعة الكود. يبدو المنطق مثالياً عندما تقرأه سطراً بسطر، لكن الخلل لا يظهر إلا عند مراعاة السرعة والتزامن (concurrency).

كيفية الإصلاح:

توقف عن استخدام أوامر القراءة والكتابة المنفصلة. بدلاً من ذلك، استخدم عملية ذرية (atomic operation) واحدة.

في Prisma، يمكنك استخدام updateMany مع شرط في جملة WHERE:

• تتحقق قاعدة البيانات من الشرط وتجري التحديث في عملية واحدة فقط. • إذا كان العدد قد وصل بالفعل إلى الحد الأقصى، سيفشل التحديث فوراً. • لا يمكن لأي طلب آخر أن يتسلل بين عملية التحقق وعملية الكتابة.

خيار آخر هو استخدام معاملات قاعدة البيانات (database transaction). هذا يقوم بقفل البيانات بحيث لا يمكن لأي طلب آخر المساس بها حتى تنتهي.

بالنسبة لـ SQLite، فإن أمر التحديث الواحد هو الطريقة الأكثر موثوقية.

اسأل نفسك دائماً: ماذا يحدث إذا وصل طلبان إلى هذا المنطق في نفس الملي ثانية؟

المصدر: https://dev.to/oopssec-store/racing-a-nextjs-api-route-coupon-abuse-with-prisma-and-sqlite-3gma