dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 7963d88ddcfb030f5087f49770f95c72f023a750
parent 7cfcb196df76260b1ccac1abc1d8d395e4b1cb6d
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Thu, 14 Dec 2023 03:05:08 -0500

prevent submitting the same form twice

Diffstat:
Mpkg/web/handlers/data.go | 1+
Mpkg/web/handlers/handlers.go | 154+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mpkg/web/public/views/pages/poker.gohtml | 1+
3 files changed, 85 insertions(+), 71 deletions(-)

diff --git a/pkg/web/handlers/data.go b/pkg/web/handlers/data.go @@ -937,6 +937,7 @@ type pokerData struct { MinWithdrawAmount database.Piconero WithdrawAmount database.Piconero WithdrawAddress string + WithdrawUnique int64 Error string } diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -24,6 +24,7 @@ import ( "image" _ "image/gif" "image/png" + "math/rand" "net/http" "net/url" "os" @@ -753,6 +754,8 @@ func BHCHandler(c echo.Context) error { return c.Render(http.StatusOK, "bhc", data) } +var pokerWithdrawCache = cache.NewWithKey[database.UserID, int64](10*time.Minute, time.Hour) + func PokerHomeHandler(c echo.Context) error { db := c.Get("database").(*database.DkfDB) authUser := c.Get("authUser").(*database.User) @@ -772,6 +775,9 @@ func PokerHomeHandler(c echo.Context) error { data.Transactions, _ = db.GetUserPokerXmrTransactions(authUser.ID) data.PokerXmrSubAddress = authUser.PokerXmrSubAddress data.XmrBalanceStagenet = authUser.XmrBalanceStagenet + data.WithdrawUnique = rand.Int63() + withdrawUniqueOrig, _ := pokerWithdrawCache.Get(authUser.ID) + pokerWithdrawCache.SetD(authUser.ID, data.WithdrawUnique) data.Tables, _ = db.GetPokerTables() data.WithdrawAmount = minWithdrawAmount if authUser.PokerXmrSubAddress != "" { @@ -779,81 +785,87 @@ func PokerHomeHandler(c echo.Context) error { data.Img = getImgStr(b) } - if c.Request().Method == http.MethodPost { - data.WithdrawAmount = database.Piconero(utils.DoParseUint64(c.Request().PostFormValue("withdraw_amount"))) - data.WithdrawAddress = c.Request().PostFormValue("withdraw_address") - if len(data.WithdrawAddress) != 95 { - data.Error = "invalid xmr address" - return c.Render(http.StatusOK, "poker", data) - } - if data.WithdrawAddress == authUser.PokerXmrSubAddress { - data.Error = "cannot withdraw to the deposit address" - return c.Render(http.StatusOK, "poker", data) - } - withdrawAmount := data.WithdrawAmount - if withdrawAmount < minWithdrawAmount { - data.Error = fmt.Sprintf("minimum withdraw amount is %d", minWithdrawAmount) - return c.Render(http.StatusOK, "poker", data) - } - userChips := authUser.XmrBalanceStagenet - if withdrawAmount > userChips { - data.Error = fmt.Sprintf("maximum withdraw amount is %d (%d)", userChips, withdrawAmount) - return c.Render(http.StatusOK, "poker", data) - } - withdrawAmount = utils.Clamp(withdrawAmount, minWithdrawAmount, userChips) - - res, err := config.Xmr().Transfer(&wallet1.RequestTransfer{ - DoNotRelay: true, - GetTxMetadata: true, - Destinations: []*wallet1.Destination{ - {Address: data.WithdrawAddress, - Amount: uint64(withdrawAmount)}}}) - if err != nil { - logrus.Error(err) - data.Error = err.Error() - return c.Render(http.StatusOK, "poker", data) - } + if c.Request().Method == http.MethodGet { + return c.Render(http.StatusOK, "poker", data) + } + + data.WithdrawAmount = database.Piconero(utils.DoParseUint64(c.Request().PostFormValue("withdraw_amount"))) + data.WithdrawAddress = c.Request().PostFormValue("withdraw_address") + withdrawUniqueSub := utils.DoParseInt64(c.Request().PostFormValue("withdraw_unique")) + if withdrawUniqueOrig == 0 || withdrawUniqueSub != withdrawUniqueOrig { + data.Error = "form submitted twice, try again" + return c.Render(http.StatusOK, "poker", data) + } + if len(data.WithdrawAddress) != 95 { + data.Error = "invalid xmr address" + return c.Render(http.StatusOK, "poker", data) + } + if data.WithdrawAddress == authUser.PokerXmrSubAddress { + data.Error = "cannot withdraw to the deposit address" + return c.Render(http.StatusOK, "poker", data) + } + withdrawAmount := data.WithdrawAmount + if withdrawAmount < minWithdrawAmount { + data.Error = fmt.Sprintf("minimum withdraw amount is %d", minWithdrawAmount) + return c.Render(http.StatusOK, "poker", data) + } + userChips := authUser.XmrBalanceStagenet + if withdrawAmount > userChips { + data.Error = fmt.Sprintf("maximum withdraw amount is %d (%d)", userChips, withdrawAmount) + return c.Render(http.StatusOK, "poker", data) + } + withdrawAmount = utils.Clamp(withdrawAmount, minWithdrawAmount, userChips) + + res, err := config.Xmr().Transfer(&wallet1.RequestTransfer{ + DoNotRelay: true, + GetTxMetadata: true, + Destinations: []*wallet1.Destination{ + {Address: data.WithdrawAddress, + Amount: uint64(withdrawAmount)}}}) + if err != nil { + logrus.Error(err) + data.Error = err.Error() + return c.Render(http.StatusOK, "poker", data) + } - transactionFee := database.Piconero(res.Fee) + transactionFee := database.Piconero(res.Fee) - if withdrawAmount+transactionFee > authUser.XmrBalanceStagenet { - data.Error = fmt.Sprintf("not enough funds to pay for transaction fee %d (%s xmr)", transactionFee, transactionFee.XmrStr()) - return c.Render(http.StatusOK, "poker", data) - } + if withdrawAmount+transactionFee > authUser.XmrBalanceStagenet { + data.Error = fmt.Sprintf("not enough funds to pay for transaction fee %d (%s xmr)", transactionFee, transactionFee.XmrStr()) + return c.Render(http.StatusOK, "poker", data) + } - tx := db.Begin() - if err := authUser.SubXmrBalanceStagenet(db, withdrawAmount+transactionFee); err != nil { - data.Error = err.Error() - tx.Rollback() - return c.Render(http.StatusOK, "poker", data) - } - xmrBalanceStagenet, err := authUser.GetXmrBalanceStagenet(tx) - if err != nil { - data.Error = err.Error() - tx.Rollback() - return c.Render(http.StatusOK, "poker", data) - } - if xmrBalanceStagenet < 0 { - data.Error = "negative balance" - tx.Rollback() - return c.Render(http.StatusOK, "poker", data) - } - if _, err := config.Xmr().RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil { - logrus.Error(err) - data.Error = err.Error() - tx.Rollback() - return c.Render(http.StatusOK, "poker", data) - } - if _, err := tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil { - logrus.Error("failed to create poker xmr transaction", err) - data.Error = err.Error() - tx.Commit() - return c.Render(http.StatusOK, "poker", data) - } + tx := db.Begin() + if err := authUser.SubXmrBalanceStagenet(db, withdrawAmount+transactionFee); err != nil { + data.Error = err.Error() + tx.Rollback() + return c.Render(http.StatusOK, "poker", data) + } + xmrBalanceStagenet, err := authUser.GetXmrBalanceStagenet(tx) + if err != nil { + data.Error = err.Error() + tx.Rollback() + return c.Render(http.StatusOK, "poker", data) + } + if xmrBalanceStagenet < 0 { + data.Error = "negative balance" + tx.Rollback() + return c.Render(http.StatusOK, "poker", data) + } + if _, err := config.Xmr().RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil { + logrus.Error(err) + data.Error = err.Error() + tx.Rollback() + return c.Render(http.StatusOK, "poker", data) + } + if _, err := tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil { + logrus.Error("failed to create poker xmr transaction", err) + data.Error = err.Error() tx.Commit() - - return c.Redirect(http.StatusFound, c.Request().Referer()) + return c.Render(http.StatusOK, "poker", data) } + tx.Commit() - return c.Render(http.StatusOK, "poker", data) + pokerWithdrawCache.Delete(authUser.ID) + return c.Redirect(http.StatusFound, c.Request().Referer()) } diff --git a/pkg/web/public/views/pages/poker.gohtml b/pkg/web/public/views/pages/poker.gohtml @@ -29,6 +29,7 @@ {{ end }} <form method="post" class="form-inline"> <input type="hidden" name="csrf" value="{{ .CSRF }}" /> + <input type="hidden" name="withdraw_unique" value="{{ .Data.WithdrawUnique }}" /> <input type="text" name="withdraw_address" value="{{ .Data.WithdrawAddress }}" placeholder="address" class="form-control mr-2" style="width: 400px;" /> <input type="number" name="withdraw_amount" value="{{ .Data.WithdrawAmount }}" min="{{ .Data.MinWithdrawAmount }}" max="{{ .Data.XmrBalanceStagenet }}" placeholder="amount" class="form-control mr-2" style="width: 150px;" /> <button class="btn btn-primary">Withdraw</button>