מרוץ ב-Next.js API Route
קופון לשימוש חד-פעמי אמור לעבוד רק פעם אחת.
אך בתרחיש הזה, שלושים אנשים יכולים להשתמש בו בדיוק באותו הזמן.
זה קורה בגלל מצב מרוץ מסוג Time-of-Check Time-of-Use (TOCTOU).
השגיאה מתרחשת בשני שלבים:
- שלב 1: השרת קורא מהדאטהבייס כדי לבדוק את מספר הפעמים שהקופון נוצל.
- שלב 2: השרת שולח פקודה שנייה כדי להגדיל את המונה הזה.
אם שולחים הרבה בקשות בבת אחת, השרת מטפל כולן באמצע שני השלבים הללו.
בקשות רבות קוראות את המונה בעודו עדיין אפס. כולן עוברות את הבדיקה. כולן מחילות את ההנחה. כולן מגדילות את המונה.
התוצאה? קופון שנועד לאדם אחד נוצל שלושים פעמים.
קל לפספס את זה במהלך סקירת קוד (code reviews). הלוגיקה נראית מושלמת כשקוראים אותה שורה אחר שורה. הפגם מופיע רק כשמתחשבים במהירות ובמקביליות (concurrency).
איך לתקן את זה:
הפסיקו להשתמש בפקודות קריאה וכתיבה נפרדות. במקום זאת, השתמשו בפעולה אטומית (atomic operation) אחת.
ב-Prisma, ניתן להשתמש ב-updateMany עם תנאי בתוך ה-WHERE clause:
• הדאטהבייס בודק את התנאי ומבצע את העדכון בתנועה אחת בלבד. • אם המונה כבר הגיע למקסימום, העדכון נכשל באופן מיידי. • שום בקשה אחרת לא יכולה "להשתחל" בין הבדיקה לכתיבה.
אפשרות נוספת היא שימוש בטרנזקציה של דאטהבייס (database transaction). זה נועל את הנתונים כך ששום בקשה אחרת לא תוכל לגעת בהם עד שתסיימו.
עבור SQLite, פקודת ה-update היחידה היא השיטה האמינה ביותר.
תמיד תשאלו את עצמכם: מה קורה אם שתי בקשות פוגעות בלוגיקה הזו באותה מילישנייה?
מקור: https://dev.to/oopssec-store/racing-a-nextjs-api-route-coupon-abuse-with-prisma-and-sqlite-3gma