Good evening. Faced a problem, I have a website on which a button is used to buy a digital product. A click on the button sends a post request to the server, processes the data, takes the balance and issues a digital product.

Today we were hacked, sending 25 commands to the server in 2 seconds, apparently the server did not fully manage and the user lost the balance 10 times less (The cost of 1 product was 35 rubles, the user had 800 balance, he bought 25 goods, but the balance became 700), I repeated his actions, using the chrome console sent 20 copies of the $ .post command and the result was the same, the goods stood out, but the balance decreased only by 1/10 of the required

The question is, how can I limit the number of requests to 1 url from 1 user, say 1 request per 2 seconds on the server side? Or maybe there are other solutions to the problem?

  • 3
    “the server didn’t handle it to the end” - you shouldn’t “humanize” the server code (by the way, what do we have on the server?). Record the number of goods purchased and the balance change wrapped in a general transaction? - Igor
  • one
    Does not fit. You will receive 50 requests in 2 seconds - vp_arth
  • one
    The sale of goods must be atomic in the context of the repository. Only transactions. Or crutches with semaphores. =) - vp_arth
  • 2
    No one is going to press your buttons - vp_arth
  • one

1 answer 1

It is necessary to wrap the critical section in the transaction.
Then the withdrawal operation and the registration of the fact of delivery of the goods will either be performed completely or will not be performed at all.

It also makes sense to block selected lines with the lockForUpdate method, so that a concurrent update attempt causes not overwriting, but throwing an exception (with subsequent rollback of the transaction).
Unlike sharedLock this method also blocks the reading of rows in other transactions.

  DB::beginTransaction(); try{ $user = App\User::where('id',auth()->id())->lockForUpdate()->get()->first() //делаем дела.... $user->save(); DB::commit(); } catch(\Exception $e) { DB::rollback(); return $e; }