diff --git a/cmd/explorer/main.go b/cmd/explorer/main.go
index 611a67d974..800f669279 100644
--- a/cmd/explorer/main.go
+++ b/cmd/explorer/main.go
@@ -377,102 +377,103 @@ func main() {
)
router.HandleFunc("/", handlers.Index).Methods("GET")
- router.HandleFunc("/latestState", handlers.LatestState).Methods("GET")
- router.HandleFunc("/launchMetrics", handlers.SlotVizMetrics).Methods("GET")
+ router.HandleFunc("/turnstile/verify", handlers.VerifyTurnstile).Methods("GET")
+ router.HandleFunc("/latestState", utils.Adapt(http.HandlerFunc(handlers.LatestState), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/launchMetrics", utils.Adapt(http.HandlerFunc(handlers.SlotVizMetrics), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/index/data", handlers.IndexPageData).Methods("GET")
router.HandleFunc("/slot/{slotOrHash}", handlers.Slot).Methods("GET")
- router.HandleFunc("/slot/{slotOrHash}/deposits", handlers.SlotDepositData).Methods("GET")
- router.HandleFunc("/slot/{slotOrHash}/votes", handlers.SlotVoteData).Methods("GET")
- router.HandleFunc("/slot/{slot}/attestations", handlers.SlotAttestationsData).Methods("GET")
- router.HandleFunc("/slot/{slot}/withdrawals", handlers.SlotWithdrawalData).Methods("GET")
- router.HandleFunc("/slot/{slot}/blsChange", handlers.SlotBlsChangeData).Methods("GET")
+ router.HandleFunc("/slot/{slotOrHash}/deposits", utils.Adapt(http.HandlerFunc(handlers.SlotDepositData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/slot/{slotOrHash}/votes", utils.Adapt(http.HandlerFunc(handlers.SlotVoteData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/slot/{slot}/attestations", utils.Adapt(http.HandlerFunc(handlers.SlotAttestationsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/slot/{slot}/withdrawals", utils.Adapt(http.HandlerFunc(handlers.SlotWithdrawalData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/slot/{slot}/blsChange", utils.Adapt(http.HandlerFunc(handlers.SlotBlsChangeData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/slots/finder", handlers.SlotFinder).Methods("GET")
router.HandleFunc("/slots", handlers.Slots).Methods("GET")
- router.HandleFunc("/slots/data", handlers.SlotsData).Methods("GET")
+ router.HandleFunc("/slots/data", utils.Adapt(http.HandlerFunc(handlers.SlotsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/blocks", handlers.Eth1Blocks).Methods("GET")
- router.HandleFunc("/blocks/data", handlers.Eth1BlocksData).Methods("GET")
+ router.HandleFunc("/blocks/data", utils.Adapt(http.HandlerFunc(handlers.Eth1BlocksData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/blocks/highest", handlers.Eth1BlocksHighest).Methods("GET")
router.HandleFunc("/address/{address}", handlers.Eth1Address).Methods("GET")
- router.HandleFunc("/address/{address}/blocks", handlers.Eth1AddressBlocksMined).Methods("GET")
- router.HandleFunc("/address/{address}/uncles", handlers.Eth1AddressUnclesMined).Methods("GET")
- router.HandleFunc("/address/{address}/withdrawals", handlers.Eth1AddressWithdrawals).Methods("GET")
- router.HandleFunc("/address/{address}/transactions", handlers.Eth1AddressTransactions).Methods("GET")
- router.HandleFunc("/address/{address}/internalTxns", handlers.Eth1AddressInternalTransactions).Methods("GET")
- router.HandleFunc("/address/{address}/blobTxns", handlers.Eth1AddressBlobTransactions).Methods("GET")
- router.HandleFunc("/address/{address}/erc20", handlers.Eth1AddressErc20Transactions).Methods("GET")
- router.HandleFunc("/address/{address}/erc721", handlers.Eth1AddressErc721Transactions).Methods("GET")
- router.HandleFunc("/address/{address}/erc1155", handlers.Eth1AddressErc1155Transactions).Methods("GET")
+ router.HandleFunc("/address/{address}/blocks", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressBlocksMined), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/uncles", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressUnclesMined), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/withdrawals", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressWithdrawals), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/transactions", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressTransactions), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/internalTxns", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressInternalTransactions), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/blobTxns", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressBlobTransactions), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/erc20", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressErc20Transactions), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/erc721", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressErc721Transactions), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/address/{address}/erc1155", utils.Adapt(http.HandlerFunc(handlers.Eth1AddressErc1155Transactions), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/token/{token}", handlers.Eth1Token).Methods("GET")
- router.HandleFunc("/token/{token}/transfers", handlers.Eth1TokenTransfers).Methods("GET")
+ router.HandleFunc("/token/{token}/transfers", utils.Adapt(http.HandlerFunc(handlers.Eth1TokenTransfers), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/transactions", handlers.Eth1Transactions).Methods("GET")
- router.HandleFunc("/transactions/data", handlers.Eth1TransactionsData).Methods("GET")
+ router.HandleFunc("/transactions/data", utils.Adapt(http.HandlerFunc(handlers.Eth1TransactionsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/block/{block}", handlers.Eth1Block).Methods("GET")
- router.HandleFunc("/block/{block}/transactions", handlers.BlockTransactionsData).Methods("GET")
+ router.HandleFunc("/block/{block}/transactions", utils.Adapt(http.HandlerFunc(handlers.BlockTransactionsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/tx/{hash}", handlers.Eth1TransactionTx).Methods("GET")
router.HandleFunc("/mempool", handlers.MempoolView).Methods("GET")
router.HandleFunc("/burn", handlers.Burn).Methods("GET")
- router.HandleFunc("/burn/data", handlers.BurnPageData).Methods("GET")
+ router.HandleFunc("/burn/data", utils.Adapt(http.HandlerFunc(handlers.BurnPageData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/gasnow", handlers.GasNow).Methods("GET")
- router.HandleFunc("/gasnow/data", handlers.GasNowData).Methods("GET")
+ router.HandleFunc("/gasnow/data", utils.Adapt(http.HandlerFunc(handlers.GasNowData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/correlations", handlers.Correlations).Methods("GET")
- router.HandleFunc("/correlations/data", handlers.CorrelationsData).Methods("POST")
+ router.HandleFunc("/correlations/data", utils.Adapt(http.HandlerFunc(handlers.CorrelationsData), utils.TurnstileMiddleware).ServeHTTP).Methods("POST")
router.HandleFunc("/vis", handlers.Vis).Methods("GET")
router.HandleFunc("/charts", handlers.Charts).Methods("GET")
router.HandleFunc("/charts/{chart}", handlers.Chart).Methods("GET")
- router.HandleFunc("/charts/{chart}/data", handlers.GenericChartData).Methods("GET")
- router.HandleFunc("/vis/blocks", handlers.VisBlocks).Methods("GET")
+ router.HandleFunc("/charts/{chart}/data", utils.Adapt(http.HandlerFunc(handlers.GenericChartData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/vis/blocks", utils.Adapt(http.HandlerFunc(handlers.VisBlocks), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/vis/votes", handlers.VisVotes).Methods("GET")
router.HandleFunc("/epoch/{epoch}", handlers.Epoch).Methods("GET")
router.HandleFunc("/epochs", handlers.Epochs).Methods("GET")
- router.HandleFunc("/epochs/data", handlers.EpochsData).Methods("GET")
+ router.HandleFunc("/epochs/data", utils.Adapt(http.HandlerFunc(handlers.EpochsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validator/{index}", handlers.Validator).Methods("GET")
- router.HandleFunc("/validator/{index}/proposedblocks", handlers.ValidatorProposedBlocks).Methods("GET")
- router.HandleFunc("/validator/{index}/attestations", handlers.ValidatorAttestations).Methods("GET")
- router.HandleFunc("/validator/{index}/withdrawals", handlers.ValidatorWithdrawals).Methods("GET")
- router.HandleFunc("/validator/{index}/sync", handlers.ValidatorSync).Methods("GET")
- router.HandleFunc("/validator/{index}/history", handlers.ValidatorHistory).Methods("GET")
- router.HandleFunc("/validator/{pubkey}/deposits", handlers.ValidatorDeposits).Methods("GET")
- router.HandleFunc("/validator/{index}/slashings", handlers.ValidatorSlashings).Methods("GET")
- router.HandleFunc("/validator/{index}/effectiveness", handlers.ValidatorAttestationInclusionEffectiveness).Methods("GET")
+ router.HandleFunc("/validator/{index}/proposedblocks", utils.Adapt(http.HandlerFunc(handlers.ValidatorProposedBlocks), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{index}/attestations", utils.Adapt(http.HandlerFunc(handlers.ValidatorAttestations), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{index}/withdrawals", utils.Adapt(http.HandlerFunc(handlers.ValidatorWithdrawals), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{index}/sync", utils.Adapt(http.HandlerFunc(handlers.ValidatorSync), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{index}/history", utils.Adapt(http.HandlerFunc(handlers.ValidatorHistory), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{pubkey}/deposits", utils.Adapt(http.HandlerFunc(handlers.ValidatorDeposits), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{index}/slashings", utils.Adapt(http.HandlerFunc(handlers.ValidatorSlashings), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validator/{index}/effectiveness", utils.Adapt(http.HandlerFunc(handlers.ValidatorAttestationInclusionEffectiveness), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validator/{pubkey}/name", handlers.SaveValidatorName).Methods("POST")
router.HandleFunc("/watchlist/add", handlers.UsersModalAddValidator).Methods("POST")
router.HandleFunc("/validator/{pubkey}/remove", handlers.UserValidatorWatchlistRemove).Methods("POST")
router.HandleFunc("/validator/{index}/stats", handlers.ValidatorStatsTable).Methods("GET")
router.HandleFunc("/validators", handlers.Validators).Methods("GET")
- router.HandleFunc("/validators/data", handlers.ValidatorsData).Methods("GET")
+ router.HandleFunc("/validators/data", utils.Adapt(http.HandlerFunc(handlers.ValidatorsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validators/slashings", handlers.ValidatorsSlashings).Methods("GET")
- router.HandleFunc("/validators/slashings/data", handlers.ValidatorsSlashingsData).Methods("GET")
+ router.HandleFunc("/validators/slashings/data", utils.Adapt(http.HandlerFunc(handlers.ValidatorsSlashingsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validators/leaderboard", handlers.ValidatorsLeaderboard).Methods("GET")
- router.HandleFunc("/validators/leaderboard/data", handlers.ValidatorsLeaderboardData).Methods("GET")
+ router.HandleFunc("/validators/leaderboard/data", utils.Adapt(http.HandlerFunc(handlers.ValidatorsLeaderboardData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validators/withdrawals", handlers.Withdrawals).Methods("GET")
- router.HandleFunc("/validators/withdrawals/data", handlers.WithdrawalsData).Methods("GET")
- router.HandleFunc("/validators/withdrawals/bls", handlers.BLSChangeData).Methods("GET")
+ router.HandleFunc("/validators/withdrawals/data", utils.Adapt(http.HandlerFunc(handlers.WithdrawalsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/validators/withdrawals/bls", utils.Adapt(http.HandlerFunc(handlers.BLSChangeData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validators/deposits", handlers.Deposits).Methods("GET")
router.HandleFunc("/validators/initiated-deposits", handlers.Eth1Deposits).Methods("GET") // deprecated, will redirect to /validators/deposits
- router.HandleFunc("/validators/initiated-deposits/data", handlers.Eth1DepositsData).Methods("GET")
+ router.HandleFunc("/validators/initiated-deposits/data", utils.Adapt(http.HandlerFunc(handlers.Eth1DepositsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validators/deposit-leaderboard", handlers.Eth1DepositsLeaderboard).Methods("GET")
- router.HandleFunc("/validators/deposit-leaderboard/data", handlers.Eth1DepositsLeaderboardData).Methods("GET")
+ router.HandleFunc("/validators/deposit-leaderboard/data", utils.Adapt(http.HandlerFunc(handlers.Eth1DepositsLeaderboardData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/validators/included-deposits", handlers.Eth2Deposits).Methods("GET") // deprecated, will redirect to /validators/deposits
- router.HandleFunc("/validators/included-deposits/data", handlers.Eth2DepositsData).Methods("GET")
+ router.HandleFunc("/validators/included-deposits/data", utils.Adapt(http.HandlerFunc(handlers.Eth2DepositsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/heatmap", handlers.Heatmap).Methods("GET")
router.HandleFunc("/dashboard", handlers.Dashboard).Methods("GET")
router.HandleFunc("/dashboard/save", handlers.UserDashboardWatchlistAdd).Methods("POST")
- router.HandleFunc("/dashboard/data/allbalances", handlers.DashboardDataBalanceCombined).Methods("GET")
- router.HandleFunc("/dashboard/data/proposals", handlers.DashboardDataProposals).Methods("GET")
- router.HandleFunc("/dashboard/data/proposalshistory", handlers.DashboardDataProposalsHistory).Methods("GET")
- router.HandleFunc("/dashboard/data/validators", handlers.DashboardDataValidators).Methods("GET")
- router.HandleFunc("/dashboard/data/withdrawal", handlers.DashboardDataWithdrawals).Methods("GET")
- router.HandleFunc("/dashboard/data/effectiveness", handlers.DashboardDataEffectiveness).Methods("GET")
- router.HandleFunc("/dashboard/data/earnings", handlers.DashboardDataEarnings).Methods("GET")
+ router.HandleFunc("/dashboard/data/allbalances", utils.Adapt(http.HandlerFunc(handlers.DashboardDataBalanceCombined), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/dashboard/data/proposals", utils.Adapt(http.HandlerFunc(handlers.DashboardDataProposals), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/dashboard/data/proposalshistory", utils.Adapt(http.HandlerFunc(handlers.DashboardDataProposalsHistory), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/dashboard/data/validators", utils.Adapt(http.HandlerFunc(handlers.DashboardDataValidators), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/dashboard/data/withdrawal", utils.Adapt(http.HandlerFunc(handlers.DashboardDataWithdrawals), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/dashboard/data/effectiveness", utils.Adapt(http.HandlerFunc(handlers.DashboardDataEffectiveness), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/dashboard/data/earnings", utils.Adapt(http.HandlerFunc(handlers.DashboardDataEarnings), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/graffitiwall", handlers.Graffitiwall).Methods("GET")
router.HandleFunc("/calculator", handlers.StakingCalculator).Methods("GET")
router.HandleFunc("/search", handlers.Search).Methods("POST")
- router.HandleFunc("/search/{type}/{search}", handlers.SearchAhead).Methods("GET")
+ router.HandleFunc("/search/{type}/{search}", utils.Adapt(http.HandlerFunc(handlers.SearchAhead), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/imprint", handlers.Imprint).Methods("GET")
router.HandleFunc("/mobile", handlers.MobilePage).Methods("GET")
router.HandleFunc("/tools/unitConverter", handlers.UnitConverter).Methods("GET")
@@ -492,19 +493,19 @@ func main() {
router.HandleFunc("/pools", handlers.Pools).Methods("GET")
router.HandleFunc("/relays", handlers.Relays).Methods("GET")
router.HandleFunc("/pools/rocketpool", handlers.PoolsRocketpool).Methods("GET")
- router.HandleFunc("/pools/rocketpool/data/minipools", handlers.PoolsRocketpoolDataMinipools).Methods("GET")
- router.HandleFunc("/pools/rocketpool/data/nodes", handlers.PoolsRocketpoolDataNodes).Methods("GET")
- router.HandleFunc("/pools/rocketpool/data/dao_proposals", handlers.PoolsRocketpoolDataDAOProposals).Methods("GET")
- router.HandleFunc("/pools/rocketpool/data/dao_members", handlers.PoolsRocketpoolDataDAOMembers).Methods("GET")
+ router.HandleFunc("/pools/rocketpool/data/minipools", utils.Adapt(http.HandlerFunc(handlers.PoolsRocketpoolDataMinipools), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/pools/rocketpool/data/nodes", utils.Adapt(http.HandlerFunc(handlers.PoolsRocketpoolDataNodes), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/pools/rocketpool/data/dao_proposals", utils.Adapt(http.HandlerFunc(handlers.PoolsRocketpoolDataDAOProposals), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
+ router.HandleFunc("/pools/rocketpool/data/dao_members", utils.Adapt(http.HandlerFunc(handlers.PoolsRocketpoolDataDAOMembers), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/advertisewithus", handlers.AdvertiseWithUs).Methods("GET")
router.HandleFunc("/advertisewithus", handlers.AdvertiseWithUsPost).Methods("POST")
// confirming the email update should not require auth
router.HandleFunc("/settings/email/{hash}", handlers.UserConfirmUpdateEmail).Methods("GET")
- router.HandleFunc("/gitcoinfeed", handlers.GitcoinFeed).Methods("GET")
+ router.HandleFunc("/gitcoinfeed", utils.Adapt(http.HandlerFunc(handlers.GitcoinFeed), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/rewards", handlers.ValidatorRewards).Methods("GET")
- router.HandleFunc("/rewards/hist", handlers.RewardsHistoricalData).Methods("GET")
+ router.HandleFunc("/rewards/hist", utils.Adapt(http.HandlerFunc(handlers.RewardsHistoricalData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
router.HandleFunc("/rewards/hist/download", handlers.DownloadRewardsHistoricalData).Methods("GET")
router.HandleFunc("/notifications/unsubscribe", handlers.UserNotificationsUnsubscribeByHash).Methods("GET")
@@ -545,7 +546,7 @@ func main() {
authRouter.HandleFunc("/settings/email", handlers.UserUpdateEmailPost).Methods("POST")
authRouter.HandleFunc("/notifications", handlers.UserNotificationsCenter).Methods("GET")
authRouter.HandleFunc("/notifications/channels", handlers.UsersNotificationChannels).Methods("POST")
- authRouter.HandleFunc("/notifications/data", handlers.UserNotificationsData).Methods("GET")
+ authRouter.HandleFunc("/notifications/data", utils.Adapt(http.HandlerFunc(handlers.UserNotificationsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
authRouter.HandleFunc("/notifications/subscribe", handlers.UserNotificationsSubscribe).Methods("POST")
authRouter.HandleFunc("/notifications/network/update", handlers.UserModalAddNetworkEvent).Methods("POST")
authRouter.HandleFunc("/watchlist/add", handlers.UsersModalAddValidator).Methods("POST")
@@ -564,7 +565,7 @@ func main() {
authRouter.HandleFunc("/notifications-center", handlers.UserNotificationsCenter).Methods("GET")
authRouter.HandleFunc("/notifications-center/removeall", handlers.RemoveAllValidatorsAndUnsubscribe).Methods("POST")
- authRouter.HandleFunc("/subscriptions/data", handlers.UserSubscriptionsData).Methods("GET")
+ authRouter.HandleFunc("/subscriptions/data", utils.Adapt(http.HandlerFunc(handlers.UserSubscriptionsData), utils.TurnstileMiddleware).ServeHTTP).Methods("GET")
authRouter.HandleFunc("/generateKey", handlers.GenerateAPIKey).Methods("POST")
authRouter.HandleFunc("/ethClients", handlers.EthClientsServices).Methods("GET")
authRouter.HandleFunc("/rewards", handlers.ValidatorRewards).Methods("GET")
diff --git a/handlers/index.go b/handlers/index.go
index c80810038b..4379bd8268 100644
--- a/handlers/index.go
+++ b/handlers/index.go
@@ -9,6 +9,7 @@ import (
"fmt"
"math"
"net/http"
+ "time"
)
// Index will return the main "index" page using a go template
@@ -112,3 +113,38 @@ func calculateChurn(page *types.IndexPageData) {
page.ValidatorsPerEpoch = *limit
page.ValidatorsPerDay = limit_per_day
}
+
+func VerifyTurnstile(w http.ResponseWriter, r *http.Request) {
+
+ if utils.Config.Frontend.Turnstile.Enabled {
+
+ err := utils.VerifyTurnstileToken(r)
+
+ cookie := http.Cookie{
+ Name: "turnstile",
+ Value: "verified",
+ Path: "/",
+ MaxAge: int(utils.Config.Frontend.Turnstile.CookieMaxAge),
+ HttpOnly: false,
+ Secure: true,
+ SameSite: http.SameSiteLaxMode,
+ }
+
+ if err != nil {
+ fmt.Println(err)
+ // clear cookie
+ cookie.MaxAge = -1
+ http.SetCookie(w, &cookie)
+ http.Error(w, "Turnstile challenge failed", http.StatusServiceUnavailable)
+ return
+ }
+
+ validuntil := time.Now().Add(time.Duration(utils.Config.Frontend.Turnstile.SessionMaxAge) * time.Second).Format(time.RFC3339)
+
+ utils.SessionStore.SCS.Put(r.Context(), "TURNSTILE::VALIDUNTIL", validuntil)
+ http.SetCookie(w, &cookie)
+ w.Write([]byte("Success"))
+ } else {
+ w.Write([]byte("Turnstile not enabled"))
+ }
+}
diff --git a/handlers/pageData.go b/handlers/pageData.go
index 81f7a3d25b..41d4d0b58f 100644
--- a/handlers/pageData.go
+++ b/handlers/pageData.go
@@ -66,6 +66,7 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st
MainMenuItems: createMenuItems(active, isMainnet),
TermsOfServiceUrl: utils.Config.Frontend.Legal.TermsOfServiceUrl,
PrivacyPolicyUrl: utils.Config.Frontend.Legal.PrivacyPolicyUrl,
+ TurnstileSiteKey: utils.Config.Frontend.Turnstile.SiteKey,
}
adConfigurations, err := db.GetAdConfigurationsForTemplate(mainTemplates, data.NoAds)
diff --git a/static/js/banner.js b/static/js/banner.js
index 514b4f7a89..7e0761e619 100644
--- a/static/js/banner.js
+++ b/static/js/banner.js
@@ -28,101 +28,103 @@ function getCookie(cname) {
}
function updateBanner() {
- fetch("/latestState")
- .then(function (res) {
- return res.json()
- })
- .then(function (data) {
- // always visible
- var epochHandle = document.getElementById("banner-epoch-data")
-
- if (data.currentEpoch) {
- epochHandle.innerHTML = addCommas(data.currentEpoch)
- epochHandle.setAttribute("href", "/epoch/" + data.currentEpoch)
- }
-
- var slotHandle = document.getElementById("banner-slot-data")
- if (data.currentSlot) {
- slotHandle.innerHTML = addCommas(data.currentSlot)
- slotHandle.setAttribute("href", "/slot/" + data.currentSlot)
- }
-
- var ethPriceHandle = document.getElementById("banner-eth-price-data")
-
- try {
- let userCurrency = getCookie("currency")
- if (userCurrency == data.rates.mainCurrency) userCurrency = data.rates.tickerCurrency
- var price = data.rates.mainCurrencyPrices[userCurrency]
- ethPriceHandle.innerHTML = `${price.symbol} ${price.truncPrice}${addCommas(price.roundPrice)}`
- } catch (err) {
- console.error("failed updating banner-price:", err)
- }
-
- var finDelayDataHandle = document.getElementById("banner-fin-data")
- finDelayHtml = `
-
-
-
-
${data.finalityDelay}
-
+ waitForTurnstileToken(()=>{
+ fetch("/latestState", {headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}})
+ .then(function (res) {
+ return res.json()
+ })
+ .then(function (data) {
+ // always visible
+ var epochHandle = document.getElementById("banner-epoch-data")
+
+ if (data.currentEpoch) {
+ epochHandle.innerHTML = addCommas(data.currentEpoch)
+ epochHandle.setAttribute("href", "/epoch/" + data.currentEpoch)
+ }
+
+ var slotHandle = document.getElementById("banner-slot-data")
+ if (data.currentSlot) {
+ slotHandle.innerHTML = addCommas(data.currentSlot)
+ slotHandle.setAttribute("href", "/slot/" + data.currentSlot)
+ }
+
+ var ethPriceHandle = document.getElementById("banner-eth-price-data")
+
+ try {
+ let userCurrency = getCookie("currency")
+ if (userCurrency == data.rates.mainCurrency) userCurrency = data.rates.tickerCurrency
+ var price = data.rates.mainCurrencyPrices[userCurrency]
+ ethPriceHandle.innerHTML = `
${price.symbol} ${price.truncPrice}${addCommas(price.roundPrice)}`
+ } catch (err) {
+ console.error("failed updating banner-price:", err)
+ }
+
+ var finDelayDataHandle = document.getElementById("banner-fin-data")
+ finDelayHtml = `
+
+
+
+ ${data.finalityDelay}
+
+
-
- `
-
- if (!finDelayDataHandle && data.finalityDelay > 3 && !data.syncing) {
- // create fin delay node
- document.getElementById("banner-slot").insertAdjacentHTML("afterend", finDelayHtml)
- $("#banner-fin i").each(function () {
- $(this).tooltip("update")
- })
- } else if (finDelayDataHandle && data.finalityDelay > 3 && !data.syncing) {
- // update fin delay node
- finDelayDataHandle.textContent = data.finalityDelay
- var icons = document.querySelectorAll("#banner-fin i")
- for (let i = 0; i < icons.length; i++) {
- const icon = icons[i]
- icon.setAttribute("data-original-title", `The last finalized epoch was ${data.finalityDelay} epochs ago.`)
+ `
+
+ if (!finDelayDataHandle && data.finalityDelay > 3 && !data.syncing) {
+ // create fin delay node
+ document.getElementById("banner-slot").insertAdjacentHTML("afterend", finDelayHtml)
+ $("#banner-fin i").each(function () {
+ $(this).tooltip("update")
+ })
+ } else if (finDelayDataHandle && data.finalityDelay > 3 && !data.syncing) {
+ // update fin delay node
+ finDelayDataHandle.textContent = data.finalityDelay
+ var icons = document.querySelectorAll("#banner-fin i")
+ for (let i = 0; i < icons.length; i++) {
+ const icon = icons[i]
+ icon.setAttribute("data-original-title", `The last finalized epoch was ${data.finalityDelay} epochs ago.`)
+ }
+ $("#banner-fin i").each(function () {
+ $(this).tooltip("update")
+ })
+ } else {
+ // delete fin delay node if it exists
+ let findDelayHandle = document.getElementById("banner-fin")
+ if (findDelayHandle) findDelayHandle.remove()
}
- $("#banner-fin i").each(function () {
- $(this).tooltip("update")
- })
- } else {
- // delete fin delay node if it exists
- let findDelayHandle = document.getElementById("banner-fin")
- if (findDelayHandle) findDelayHandle.remove()
- }
- if (data.syncing) {
- // remove fin delay if we are still syncing
- let findDelayHandle = document.getElementById("banner-fin")
- if (findDelayHandle) findDelayHandle.remove()
-
- var bannerHandle = document.getElementById("banner-status")
- if (!bannerHandle) {
- var statusHtml = `
-
|
+ if (data.syncing) {
+ // remove fin delay if we are still syncing
+ let findDelayHandle = document.getElementById("banner-fin")
+ if (findDelayHandle) findDelayHandle.remove()
+
+ var bannerHandle = document.getElementById("banner-status")
+ if (!bannerHandle) {
+ var statusHtml = `
+
|
+ `
+ document.getElementById("banner-home").remove()
+ document.getElementById("banner-stats").insertAdjacentHTML("afterbegin", statusHtml)
+ }
+ } else {
+ // delete sync if it exists otherwise do nothing
+ var statusHandle = document.getElementById("banner-status")
+ if (statusHandle) {
+ var homeHtml = `
+
|
`
- document.getElementById("banner-home").remove()
- document.getElementById("banner-stats").insertAdjacentHTML("afterbegin", statusHtml)
- }
- } else {
- // delete sync if it exists otherwise do nothing
- var statusHandle = document.getElementById("banner-status")
- if (statusHandle) {
- var homeHtml = `
-
|
- `
- statusHandle.remove()
- document.getElementById("banner-stats").insertAdjacentHTML("afterbegin", homeHtml)
+ statusHandle.remove()
+ document.getElementById("banner-stats").insertAdjacentHTML("afterbegin", homeHtml)
+ }
}
- }
- })
+ })
+ })
}
// update the banner every 12 seconds
diff --git a/static/js/dashboard.js b/static/js/dashboard.js
index b22eb2e135..e5156e1538 100644
--- a/static/js/dashboard.js
+++ b/static/js/dashboard.js
@@ -116,38 +116,58 @@ function toggleFirstrow() {
}
function updateValidatorInfo(index) {
- fetch(`/validator/${index}/proposedblocks?draw=1&start=1&length=1`, {
- method: "GET",
- }).then((res) => {
- res.json().then((data) => {
- $("#blockCount span").text(data.recordsTotal)
+ waitForTurnstileToken(() => {
+ fetch(`/validator/${index}/proposedblocks?draw=1&start=1&length=1`, {
+ method: "GET",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}
+ }).then((res) => {
+ res.json().then((data) => {
+ $("#blockCount span").text(data.recordsTotal)
+ })
+ }).finally(()=>{
+ resetTurnstileToken()
})
})
- fetch(`/validator/${index}/attestations?draw=1&start=1&length=1`, {
- method: "GET",
- }).then((res) => {
- res.json().then((data) => {
- $("#attestationCount span").text(data.recordsTotal)
+ waitForTurnstileToken(() => {
+ fetch(`/validator/${index}/attestations?draw=1&start=1&length=1`, {
+ method: "GET",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}
+ }).then((res) => {
+ res.json().then((data) => {
+ $("#attestationCount span").text(data.recordsTotal)
+ })
+ }).finally(()=>{
+ resetTurnstileToken()
})
})
- fetch(`/validator/${index}/slashings?draw=1&start=1&length=1`, {
- method: "GET",
- }).then((res) => {
- res.json().then((data) => {
- var total = parseInt(data.recordsTotal)
- if (total > 0) {
- $("#slashingsCountDiv").removeClass("d-none")
- $("#slashingsCount span").text(total)
- } else {
- $("#slashingsCountDiv").addClass("d-none")
- }
+ waitForTurnstileToken(() => {
+ fetch(`/validator/${index}/slashings?draw=1&start=1&length=1`, {
+ method: "GET",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}
+ }).then((res) => {
+ res.json().then((data) => {
+ var total = parseInt(data.recordsTotal)
+ if (total > 0) {
+ $("#slashingsCountDiv").removeClass("d-none")
+ $("#slashingsCount span").text(total)
+ } else {
+ $("#slashingsCountDiv").addClass("d-none")
+ }
+ })
+ }).finally(()=>{
+ resetTurnstileToken()
})
})
- fetch(`/validator/${index}/effectiveness`, {
- method: "GET",
- }).then((res) => {
- res.json().then((data) => {
- setValidatorEffectiveness("effectiveness", data.effectiveness)
+ waitForTurnstileToken(() => {
+ fetch(`/validator/${index}/effectiveness`, {
+ method: "GET",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}
+ }).then((res) => {
+ res.json().then((data) => {
+ setValidatorEffectiveness("effectiveness", data.effectiveness)
+ })
+ }).finally(()=>{
+ resetTurnstileToken()
})
})
}
@@ -292,15 +312,19 @@ function renderProposedHistoryTable(data) {
}
function showProposedHistoryTable() {
- fetch("/dashboard/data/proposalshistory" + getValidatorQueryString(), {
- method: "GET",
- }).then((res) => {
- res.json().then(function (data) {
- let proposedHistTableData = []
- for (let item of data) {
- proposedHistTableData.push([item[0], item[1], [item[2], item[3], item[4]]])
- }
- renderProposedHistoryTable(proposedHistTableData)
+ waitForTurnstileToken(()=>{
+ fetch("/dashboard/data/proposalshistory" + getValidatorQueryString(), {
+ method: "GET",
+ }).then((res) => {
+ res.json().then(function (data) {
+ let proposedHistTableData = []
+ for (let item of data) {
+ proposedHistTableData.push([item[0], item[1], [item[2], item[3], item[4]]])
+ }
+ renderProposedHistoryTable(proposedHistTableData)
+ })
+ }).finally(()=>{
+ resetTurnstileToken()
})
})
}
@@ -692,6 +716,18 @@ $(document).ready(function () {
}
create_validators_typeahead("input[aria-controls='validators']", "#validators")
+
+ function prepare(query,settings){
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(jqXHR){
+ resetTurnstileToken()
+ }
+ return settings
+ }
+
var timeWait = 0
var debounce = function (context, func) {
var timeout, result
@@ -721,11 +757,16 @@ $(document).ready(function () {
// use prepare hook to modify the rateLimitWait parameter on input changes
// NOTE: we only need to do this for the first function because testing showed that queries are executed/queued in order
// No need to update `timeWait` multiple times.
- prepare: function (_, settings) {
- var cur_query = $(".typeahead-dashboard").val()
- timeWait = 4000 - Math.min(cur_query.length, 5) * 500
+ prepare: function (query, settings) {
+ timeWait = 4000 - Math.min(query.length, 5) * 500
// "wildcard" can't be used anymore, need to set query wildcard ourselves now
- settings.url = settings.url.replace("%QUERY", encodeURIComponent(cur_query))
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
return settings
},
},
@@ -739,7 +780,7 @@ $(document).ready(function () {
},
remote: {
url: "/search/validators_by_pubkey/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhPubkey.remote.transport._get = debounce(bhPubkey.remote.transport, bhPubkey.remote.transport._get)
@@ -751,7 +792,7 @@ $(document).ready(function () {
},
remote: {
url: "/search/indexed_validators_by_eth1_addresses/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhEth1Addresses.remote.transport._get = debounce(bhEth1Addresses.remote.transport, bhEth1Addresses.remote.transport._get)
@@ -763,7 +804,7 @@ $(document).ready(function () {
},
remote: {
url: "/search/indexed_validators_by_name/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhName.remote.transport._get = debounce(bhName.remote.transport, bhName.remote.transport._get)
@@ -775,7 +816,7 @@ $(document).ready(function () {
},
remote: {
url: "/search/indexed_validators_by_graffiti/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhGraffiti.remote.transport._get = debounce(bhGraffiti.remote.transport, bhGraffiti.remote.transport._get)
@@ -972,19 +1013,22 @@ $(document).ready(function () {
$("#dash-validator-history-index-div").removeClass("d-none")
$("#dash-validator-history-index-div").addClass("d-flex")
- fetch(`/dashboard/data/effectiveness${getValidatorQueryString()}`, {
- method: "GET",
- }).then((res) => {
- res.json().then((data) => {
- if (Object.keys(data).length === 0) {
- return
- }
- let sum = 0.0
- for (let eff of data) {
- sum += eff
- }
- sum = sum / data.length
- setValidatorEffectiveness("validator-eff-total", sum)
+ waitForTurnstileToken(()=>{
+ fetch(`/dashboard/data/effectiveness${getValidatorQueryString()}`, {
+ method: "GET",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}
+ }).then((res) => {
+ res.json().then((data) => {
+ if (Object.keys(data).length === 0) {
+ return
+ }
+ let sum = 0.0
+ for (let eff of data) {
+ sum += eff
+ }
+ sum = sum / data.length
+ setValidatorEffectiveness("validator-eff-total", sum)
+ })
})
})
@@ -1184,73 +1228,82 @@ $(document).ready(function () {
document.querySelector("#copy-button").style.visibility = "visible"
document.querySelector("#clear-search").style.visibility = "visible"
- $.ajax({
- url: "/dashboard/data/validators" + qryStr,
- success: function (result) {
- var t1 = Date.now()
- console.log(`loaded validators-data: length: ${result.data.length}, fetch: ${t1 - t0}ms`)
- if (!result || !result.data.length) {
- document.getElementById("validators-table-holder").style.display = "none"
- return
- }
- // pubkey, idx, currbal, effbal, slashed, acteligepoch, actepoch, exitepoch
- // 0:pubkey, 1:idx, 2:[currbal,effbal], 3:state, 4:[actepoch,acttime], 5:[exit,exittime], 6:[wd,wdt], 7:[lasta,lastat], 8:[exprop,misprop]
- state.validatorsCount.deposited = 0
- state.validatorsCount.pending = 0
- state.validatorsCount.active_online = 0
- state.validatorsCount.active_offline = 0
- state.validatorsCount.slashing_online = 0
- state.validatorsCount.slashing_offline = 0
- state.validatorsCount.exiting_online = 0
- state.validatorsCount.exiting_offline = 0
- state.validatorsCount.exited = 0
- state.validatorsCount.slashed = 0
-
- for (var i = 0; i < result.data.length; i++) {
- var v = result.data[i]
- var vIndex = v[1]
- var vState = v[3][1]
- if (!state.validatorsCount[vState]) state.validatorsCount[vState] = 0
- state.validatorsCount[vState]++
- var el = document.querySelector(`#selected-validators .item[data-validator-index="${vIndex}"]`)
- if (el) el.dataset.state = vState
- }
- validatorsDataTable.clear()
-
- validatorsDataTable.rows.add(result.data).draw()
- validatorsDataTable.column(6).visible(false)
-
- requestAnimationFrame(() => {
- validatorsDataTable.columns.adjust().responsive.recalc()
- })
-
- document.getElementById("validators-table-holder").style.display = "block"
-
- renderDashboardInfo()
- },
+ waitForTurnstileToken(()=>{
+ $.ajax({
+ url: "/dashboard/data/validators" + qryStr,
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken},
+ success: function (result) {
+ var t1 = Date.now()
+ console.log(`loaded validators-data: length: ${result.data.length}, fetch: ${t1 - t0}ms`)
+ if (!result || !result.data.length) {
+ document.getElementById("validators-table-holder").style.display = "none"
+ return
+ }
+ // pubkey, idx, currbal, effbal, slashed, acteligepoch, actepoch, exitepoch
+ // 0:pubkey, 1:idx, 2:[currbal,effbal], 3:state, 4:[actepoch,acttime], 5:[exit,exittime], 6:[wd,wdt], 7:[lasta,lastat], 8:[exprop,misprop]
+ state.validatorsCount.deposited = 0
+ state.validatorsCount.pending = 0
+ state.validatorsCount.active_online = 0
+ state.validatorsCount.active_offline = 0
+ state.validatorsCount.slashing_online = 0
+ state.validatorsCount.slashing_offline = 0
+ state.validatorsCount.exiting_online = 0
+ state.validatorsCount.exiting_offline = 0
+ state.validatorsCount.exited = 0
+ state.validatorsCount.slashed = 0
+
+ for (var i = 0; i < result.data.length; i++) {
+ var v = result.data[i]
+ var vIndex = v[1]
+ var vState = v[3][1]
+ if (!state.validatorsCount[vState]) state.validatorsCount[vState] = 0
+ state.validatorsCount[vState]++
+ var el = document.querySelector(`#selected-validators .item[data-validator-index="${vIndex}"]`)
+ if (el) el.dataset.state = vState
+ }
+ validatorsDataTable.clear()
+
+ validatorsDataTable.rows.add(result.data).draw()
+
+ validatorsDataTable.column(6).visible(false)
+
+ requestAnimationFrame(() => {
+ validatorsDataTable.columns.adjust().responsive.recalc()
+ })
+
+ document.getElementById("validators-table-holder").style.display = "block"
+
+ renderDashboardInfo()
+ },
+ })
})
if (firstValidatorWithIndex() !== undefined) {
document.querySelector("#rewards-button").style.visibility = "visible"
document.querySelector("#bookmark-button").style.visibility = "visible"
-
- $.ajax({
- url: "/dashboard/data/earnings" + qryStr,
- success: function (result) {
- var t1 = Date.now()
- console.log(`loaded earnings: fetch: ${t1 - t0}ms`)
- if (!result) return
-
- document.querySelector("#earnings-day").innerHTML = result.lastDayFormatted || summaryDefaultValue
- document.querySelector("#earnings-week").innerHTML = result.lastWeekFormatted || summaryDefaultValue
- document.querySelector("#earnings-month").innerHTML = result.lastMonthFormatted || summaryDefaultValue
- document.querySelector("#earnings-total").innerHTML = result.totalFormatted || summaryDefaultValue
- $("#earnings-total").find('[data-toggle="tooltip"]').tooltip()
- document.querySelector("#balance-total").innerHTML = result.totalBalance || summaryDefaultValue
- $("#balance-total span:first").removeClass("text-success").removeClass("text-danger")
- $("#balance-total span:first").html($("#balance-total span:first").html().replace("+", ""))
- },
+ waitForTurnstileToken(()=>{
+ $.ajax({
+ url: "/dashboard/data/earnings" + qryStr,
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken},
+ success: function (result) {
+ var t1 = Date.now()
+ console.log(`loaded earnings: fetch: ${t1 - t0}ms`)
+ if (!result) return
+
+ document.querySelector("#earnings-day").innerHTML = result.lastDayFormatted || summaryDefaultValue
+ document.querySelector("#earnings-week").innerHTML = result.lastWeekFormatted || summaryDefaultValue
+ document.querySelector("#earnings-month").innerHTML = result.lastMonthFormatted || summaryDefaultValue
+ document.querySelector("#earnings-total").innerHTML = result.totalFormatted || summaryDefaultValue
+ $("#earnings-total").find('[data-toggle="tooltip"]').tooltip()
+ document.querySelector("#balance-total").innerHTML = result.totalBalance || summaryDefaultValue
+ $("#balance-total span:first").removeClass("text-success").removeClass("text-danger")
+ $("#balance-total span:first").html($("#balance-total span:first").html().replace("+", ""))
+ },
+ complete:()=>{
+ resetTurnstileToken()
+ }
+ })
})
} else {
document.querySelector("#rewards-button").style.visibility = "hidden"
@@ -1323,32 +1376,44 @@ $(document).ready(function () {
function renderCharts() {
var t0 = Date.now()
var qryStr = "?validators=" + state.validators.join(",")
- $.ajax({
- url: "/dashboard/data/allbalances" + qryStr + "&days=31",
- success: function (result) {
- var t1 = Date.now()
- createIncomeChart(result.consensusChartData, result.executionChartData)
- var t2 = Date.now()
- console.log(`loaded balance-data: length: ${result.length}, fetch: ${t1 - t0}ms, render: ${t2 - t1}ms`)
- allIncomeLoaded = false
- $("#load-income-btn").removeClass("d-none")
- },
+ waitForTurnstileToken(()=>{
+ $.ajax({
+ url: "/dashboard/data/allbalances" + qryStr + "&days=31",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken},
+ success: function (result) {
+ var t1 = Date.now()
+ createIncomeChart(result.consensusChartData, result.executionChartData)
+ var t2 = Date.now()
+ console.log(`loaded balance-data: length: ${result.length}, fetch: ${t1 - t0}ms, render: ${t2 - t1}ms`)
+ allIncomeLoaded = false
+ $("#load-income-btn").removeClass("d-none")
+ },
+ complete:()=>{
+ resetTurnstileToken()
+ }
+ })
})
- $.ajax({
- url: "/dashboard/data/proposals" + qryStr,
- success: function (result) {
- var t1 = Date.now()
- if (result && result.length) {
- createProposedChart(result)
- } else {
- var chart = $("#proposed-chart").highcharts()
- if (chart !== undefined) {
- hideProposedChart()
+ waitForTurnstileToken(()=>{
+ $.ajax({
+ url: "/dashboard/data/proposals" + qryStr,
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken},
+ success: function (result) {
+ var t1 = Date.now()
+ if (result && result.length) {
+ createProposedChart(result)
+ } else {
+ var chart = $("#proposed-chart").highcharts()
+ if (chart !== undefined) {
+ hideProposedChart()
+ }
}
+ var t2 = Date.now()
+ console.log(`loaded proposal-data: length: ${result.length}, fetch: ${t1 - t0}ms, render: ${t2 - t1}ms`)
+ },
+ complete:()=>{
+ resetTurnstileToken()
}
- var t2 = Date.now()
- console.log(`loaded proposal-data: length: ${result.length}, fetch: ${t1 - t0}ms, render: ${t2 - t1}ms`)
- },
+ })
})
}
@@ -1360,25 +1425,28 @@ $(document).ready(function () {
const url = "/dashboard/data/allbalances?validators=" + state.validators.join(",")
$("#load-income-btn").text("Loading...")
- fetch(url)
- .then((response) => {
- if (!response.ok) {
- throw new Error("Network response was not ok.")
- }
- return response.json()
- })
- .then((data) => {
- createIncomeChart(data.consensusChartData, data.executionChartData)
- $("#load-income-btn").addClass("d-none")
- })
- .catch((error) => {
- console.error(error)
- alert("Error loading income data. Please try again.")
- allIncomeLoaded = false
- })
- .finally(() => {
- $("#load-income-btn").text("Show all rewards")
- })
+ waitForTurnstileToken(()=>{
+ fetch(url,{headers : {'X-TURNSTILE-TOKEN': window.turnstileToken}})
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error("Network response was not ok.")
+ }
+ return response.json()
+ })
+ .then((data) => {
+ createIncomeChart(data.consensusChartData, data.executionChartData)
+ $("#load-income-btn").addClass("d-none")
+ })
+ .catch((error) => {
+ console.error(error)
+ alert("Error loading income data. Please try again.")
+ allIncomeLoaded = false
+ })
+ .finally(() => {
+ resetTurnstileToken()
+ $("#load-income-btn").text("Show all rewards")
+ })
+ })
})
})
diff --git a/static/js/datatable_loader.js b/static/js/datatable_loader.js
index 3821a8cc32..afdba633ae 100644
--- a/static/js/datatable_loader.js
+++ b/static/js/datatable_loader.js
@@ -27,7 +27,7 @@ function dataTableLoader(path, param, dataSrc) {
}
const doFetch = (tableData, callback) => {
- fetch(`${path}?${$.param(tableData)}&${param}`)
+ fetch(`${path}?${$.param(tableData)}&${param}`, {headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}})
.then((response) => {
if (!response.ok) {
throw new Error(`Failed with status: ${response.status}`)
@@ -53,7 +53,9 @@ function dataTableLoader(path, param, dataSrc) {
const fetchWithRetry = (tableData, callback) => {
clearTimeout(timeoutId) // Clear any pending retries.
retries = 0 // Reset retry count.
- doFetch(tableData, callback)
+ waitForTurnstileToken(()=>{
+ doFetch(tableData, callback)
+ })
}
const debouncedFetchData = debounce(fetchWithRetry, DEBOUNCE_DELAY)
diff --git a/static/js/herofeed.js b/static/js/herofeed.js
index 0bf222c1af..439bc91564 100644
--- a/static/js/herofeed.js
+++ b/static/js/herofeed.js
@@ -58,38 +58,43 @@ function findNewDoner(data) {
}
function updateFeed() {
- $.ajax({
- url: "/gitcoinfeed",
- success: (data) => {
- let isLive = data.isLive
- if (!isLive && feedInterval !== null) {
- clearInterval(feedInterval)
- return
- }
- data = data.donors
-
- if (isLive) {
- $("#hero-feed").addClass("d-lg-flex fade-in-top")
-
- if (data.length > 0) {
- donors = findNewDoner(data)
- $("#hero-feed ul>li#gitcoinwaitmsg").remove()
- $(".hover-shadow").hover(
- function () {
- $(this).addClass("shadow color-shift-anim border-rounded")
- },
- function () {
- $(this).removeClass("shadow color-shift-anim border-rounded fade-in")
- }
- )
- } else {
- $("#hero-feed ul").html("")
- $("#hero-feed ul").prepend(`
-
Waiting for the next gitcoin round to start
- `)
+ waitForTurnstileToken(()=>{
+ $.ajax({
+ url: "/gitcoinfeed",
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken},
+ success: (data) => {
+ let isLive = data.isLive
+ if (!isLive && feedInterval !== null) {
+ clearInterval(feedInterval)
+ return
+ }
+ data = data.donors
+
+ if (isLive) {
+ $("#hero-feed").addClass("d-lg-flex fade-in-top")
+
+ if (data.length > 0) {
+ donors = findNewDoner(data)
+ $("#hero-feed ul>li#gitcoinwaitmsg").remove()
+ $(".hover-shadow").hover(
+ function () {
+ $(this).addClass("shadow color-shift-anim border-rounded")
+ },
+ function () {
+ $(this).removeClass("shadow color-shift-anim border-rounded fade-in")
+ }
+ )
+ } else {
+ $("#hero-feed ul").html("")
+ $("#hero-feed ul").prepend(`
+
Waiting for the next gitcoin round to start
+ `)
+ }
}
+ },complete:()=>{
+ resetTurnstileToken()
}
- },
+ })
})
}
diff --git a/static/js/layout.js b/static/js/layout.js
index 4fe01a84d2..06df81b19a 100644
--- a/static/js/layout.js
+++ b/static/js/layout.js
@@ -221,6 +221,17 @@ $(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip()
}
+ function prepare(query,settings){
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
+ return settings
+ }
+
// set maxParallelRequests to number of datasets queried in each search
// make sure this is set in every one bloodhound object
let requestNum = 10
@@ -237,7 +248,9 @@ $(document).ready(function () {
var args = arguments,
later = function () {
timeout = null
- result = func.apply(context, args)
+ waitForTurnstileToken(()=>{
+ result = func.apply(context, args)
+ })
}
clearTimeout(timeout)
timeout = setTimeout(later, timeWait)
@@ -259,11 +272,16 @@ $(document).ready(function () {
// use prepare hook to modify the rateLimitWait parameter on input changes
// NOTE: we only need to do this for the first function because testing showed that queries are executed/queued in order
// No need to update `timeWait` multiple times.
- prepare: function (_, settings) {
- var cur_query = $("input.typeahead.tt-input").val()
- timeWait = 4000 - Math.min(cur_query.length, 5) * 500
+ prepare: function (query, settings) {
+ timeWait = 4000 - Math.min(query.length, 5) * 500
// "wildcard" can't be used anymore, need to set query wildcard ourselves now
- settings.url = settings.url.replace("%QUERY", encodeURIComponent(cur_query))
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
return settings
},
maxPendingRequests: requestNum,
@@ -279,11 +297,11 @@ $(document).ready(function () {
},
remote: {
url: "/search/ens/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
transform: function (data) {
return data?.address && data?.domain ? { data: { ...data } } : null
},
+ prepare:prepare,
},
})
bhEns.remote.transport._get = debounce(bhEns.remote.transport, bhEns.remote.transport._get)
@@ -296,8 +314,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/slots/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhSlots.remote.transport._get = debounce(bhSlots.remote.transport, bhSlots.remote.transport._get)
@@ -310,8 +328,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/blocks/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhBlocks.remote.transport._get = debounce(bhBlocks.remote.transport, bhBlocks.remote.transport._get)
@@ -324,8 +342,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/transactions/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhTransactions.remote.transport._get = debounce(bhTransactions.remote.transport, bhTransactions.remote.transport._get)
@@ -338,8 +356,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/graffiti/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhGraffiti.remote.transport._get = debounce(bhGraffiti.remote.transport, bhGraffiti.remote.transport._get)
@@ -352,8 +370,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/epochs/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhEpochs.remote.transport._get = debounce(bhEpochs.remote.transport, bhEpochs.remote.transport._get)
@@ -366,8 +384,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/eth1_addresses/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhEth1Accounts.remote.transport._get = debounce(bhEth1Accounts.remote.transport, bhEth1Accounts.remote.transport._get)
@@ -380,8 +398,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/count_indexed_validators_by_eth1_address/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhValidatorsByAddress.remote.transport._get = debounce(bhValidatorsByAddress.remote.transport, bhValidatorsByAddress.remote.transport._get)
@@ -394,8 +412,8 @@ $(document).ready(function () {
},
remote: {
url: "/search/validators_by_pubkey/%QUERY",
- wildcard: "%QUERY",
maxPendingRequests: requestNum,
+ prepare:prepare,
},
})
bhPubkey.remote.transport._get = debounce(bhPubkey.remote.transport, bhPubkey.remote.transport._get)
diff --git a/static/js/notificationsCenter.js b/static/js/notificationsCenter.js
index 127bc22cd3..5c5225a3f5 100755
--- a/static/js/notificationsCenter.js
+++ b/static/js/notificationsCenter.js
@@ -4,7 +4,19 @@ const VALIDATOR_EVENTS = ["validator_attestation_missed", "validator_proposal_mi
// const MONITORING_EVENTS = ['monitoring_machine_offline', 'monitoring_hdd_almostfull', 'monitoring_cpu_load']
+function prepare(query,settings){
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
+ return settings
+}
+
function create_typeahead(input_container) {
+
var timeWait = 0
var debounce = function (context, func) {
var timeout, result
@@ -13,7 +25,9 @@ function create_typeahead(input_container) {
var args = arguments,
later = function () {
timeout = null
- result = func.apply(context, args)
+ waitForTurnstileToken(()=>{
+ result = func.apply(context, args)
+ })
}
clearTimeout(timeout)
timeout = setTimeout(later, timeWait)
@@ -35,11 +49,16 @@ function create_typeahead(input_container) {
// use prepare hook to modify the rateLimitWait parameter on input changes
// NOTE: we only need to do this for the first function because testing showed that queries are executed/queued in order
// No need to update `timeWait` multiple times.
- prepare: function (_, settings) {
- var cur_query = $(input_container).val()
- timeWait = 4000 - Math.min(cur_query.length, 5) * 500
+ prepare: function (query, settings) {
+ timeWait = 4000 - Math.min(query.length, 5) * 500
// "wildcard" can't be used anymore, need to set query wildcard ourselves now
- settings.url = settings.url.replace("%QUERY", encodeURIComponent(cur_query))
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
return settings
},
},
@@ -53,7 +72,7 @@ function create_typeahead(input_container) {
},
remote: {
url: "/search/indexed_validators_by_name/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhName.remote.transport._get = debounce(bhName.remote.transport, bhName.remote.transport._get)
@@ -65,7 +84,7 @@ function create_typeahead(input_container) {
},
remote: {
url: "/search/indexed_validators_by_graffiti/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhGraffiti.remote.transport._get = debounce(bhGraffiti.remote.transport, bhGraffiti.remote.transport._get)
@@ -77,7 +96,7 @@ function create_typeahead(input_container) {
},
remote: {
url: "/search/indexed_validators_by_eth1_addresses/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhEth1Addresses.remote.transport._get = debounce(bhEth1Addresses.remote.transport, bhEth1Addresses.remote.transport._get)
@@ -180,7 +199,7 @@ function create_validators_typeahead(input_container_selector, table_selector) {
},
remote: {
url: "/search/indexed_validators_by_eth1_addresses/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
$(input_container_selector).typeahead(
diff --git a/static/js/turnstile.js b/static/js/turnstile.js
new file mode 100644
index 0000000000..7934f5fcca
--- /dev/null
+++ b/static/js/turnstile.js
@@ -0,0 +1,98 @@
+function getCookie(cname) {
+ var name = cname + "="
+ var ca = document.cookie.split(";")
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i]
+ while (c.charAt(0) == " ") {
+ c = c.substring(1)
+ }
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length)
+ }
+ }
+ return ""
+}
+
+function renderTurnStile(){
+
+ if(!window.turnstile || getCookie("turnstile") === "verified") return
+
+ if(window.isRequestingTurnstileToken) return
+ window.isRequestingTurnstileToken = true
+
+ window.turnstileWidgetId = turnstile.render('#turnstileModalContent', {
+ sitekey: window.turnstileSiteKey,
+ theme: 'auto',
+ callback: function(token) {
+ window.turnstileToken = token;
+ console.log(`Challenge Success ${token}`);
+ verifyTurnStileToken(()=>{
+ window.isRequestingTurnstileToken = false
+ })
+ },
+ 'before-interactive-callback': function() {
+ $('#turnstileModal').modal('show');
+ },
+ 'after-interactive-callback': function() {
+ $('#turnstileModal').modal('hide');
+ },
+ 'error-callback': function(error) {
+ console.log(`error callback called with ${error}`);
+ window.isRequestingTurnstileToken = false
+ },
+ 'expired-callback': function() {
+ window.isRequestingTurnstileToken = false
+ },
+ 'timeout-callback': function() {
+ window.isRequestingTurnstileToken = false
+ },
+ 'unsupported-callback': function() {
+ console.log('unsupported callback called')
+ },
+ });
+}
+
+function _turnstileCb() {
+ // onSiteLoad turnstile iframe loaded, show modal if cookie not set
+ console.debug('_turnstileCb called');
+ renderTurnStile()
+}
+
+function waitForTurnstileToken(cb) {
+ if(window.turnstileSiteKey && !window.turnstileToken && !window.isRequestingTurnstileToken){
+ renderTurnStile()
+ }
+ if(window.turnstileSiteKey && !window.turnstileToken && getCookie("turnstile") !== "verified") {//we want it to match
+ setTimeout(waitForTurnstileToken.bind(this,cb), 50);//wait 50 milliseconds then recheck
+ return;
+ } else {
+ cb && cb()
+ }
+}
+
+function verifyTurnStileToken(cb){
+ fetch('/turnstile/verify', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-TURNSTILE-TOKEN': window.turnstileToken
+ }
+ }).then((res)=>{
+ cb && cb()
+ }).catch((err)=>{
+ console.log('error verifying turnstile token',err)
+ })
+}
+
+// resetting a widget causes it to rerender
+// rerender only if the cookie is not present
+function resetTurnstileToken() {
+ if(getCookie("turnstile") !== "verified") {
+ if(window.turnstile){
+ if(window.turnstileWidgetId){
+ window.turnstile.reset(window.turnstileWidgetId);
+ window.turnstileToken = ""
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/static/js/validatorRewards.js b/static/js/validatorRewards.js
index 16d8bcd5a9..cef52d6679 100644
--- a/static/js/validatorRewards.js
+++ b/static/js/validatorRewards.js
@@ -7,6 +7,18 @@ var subsTable = null
// let validators = []
function create_typeahead(input_container) {
+
+ function prepare(query,settings){
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
+ return settings
+ }
+
var timeWait = 0
var debounce = function (context, func) {
var timeout, result
@@ -15,7 +27,9 @@ function create_typeahead(input_container) {
var args = arguments,
later = function () {
timeout = null
- result = func.apply(context, args)
+ waitForTurnstileToken(()=>{
+ result = func.apply(context, args)
+ })
}
clearTimeout(timeout)
timeout = setTimeout(later, timeWait)
@@ -37,11 +51,16 @@ function create_typeahead(input_container) {
// use prepare hook to modify the rateLimitWait parameter on input changes
// NOTE: we only need to do this for the first function because testing showed that queries are executed/queued in order
// No need to update `timeWait` multiple times.
- prepare: function (_, settings) {
- var cur_query = $(input_container).val()
- timeWait = 4000 - Math.min(cur_query.length, 5) * 500
+ prepare: function (query, settings) {
+ timeWait = 4000 - Math.min(query.length, 5) * 500
// "wildcard" can't be used anymore, need to set query wildcard ourselves now
- settings.url = settings.url.replace("%QUERY", encodeURIComponent(cur_query))
+ settings.url = settings.url.replace("%QUERY", encodeURIComponent(query))
+ settings.beforeSend = function(jqXHR){
+ jqXHR.setRequestHeader('X-TURNSTILE-TOKEN', window.turnstileToken)
+ }
+ settings.complete = function(){
+ resetTurnstileToken()
+ }
return settings
},
},
@@ -55,7 +74,7 @@ function create_typeahead(input_container) {
},
remote: {
url: "/search/indexed_validators_by_eth1_addresses/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhEth1Addresses.remote.transport._get = debounce(bhEth1Addresses.remote.transport, bhEth1Addresses.remote.transport._get)
@@ -67,7 +86,7 @@ function create_typeahead(input_container) {
},
remote: {
url: "/search/indexed_validators_by_name/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhName.remote.transport._get = debounce(bhName.remote.transport, bhName.remote.transport._get)
@@ -79,7 +98,7 @@ function create_typeahead(input_container) {
},
remote: {
url: "/search/indexed_validators_by_graffiti/%QUERY",
- wildcard: "%QUERY",
+ prepare:prepare,
},
})
bhGraffiti.remote.transport._get = debounce(bhGraffiti.remote.transport, bhGraffiti.remote.transport._get)
diff --git a/templates/charts.html b/templates/charts.html
index 8d94b2c105..ee43082383 100644
--- a/templates/charts.html
+++ b/templates/charts.html
@@ -218,10 +218,12 @@
function fetchChart(chartHolderId) {
return new Promise((resolve, reject) => {
- fetch(`/charts/${chartHolderId}/data`)
- .then(res => res.json())
- .then(resolve)
- .catch(reject)
+ waitForTurnstileToken(()=>{
+ fetch(`/charts/${chartHolderId}/data`,{ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}})
+ .then(res => res.json())
+ .then(resolve)
+ .catch(reject)
+ })
})
}
diff --git a/templates/gasnow.html b/templates/gasnow.html
index 3ada10d750..fda10d11bf 100644
--- a/templates/gasnow.html
+++ b/templates/gasnow.html
@@ -528,12 +528,30 @@
methods: {
tick: function () {
if (this.updateIn <= 0) {
- $.getJSON('/gasnow/data', function (response) {
- // console.log('gasnow data', response)
- this.page = response;
-
- document.title = (response.data.rapid / 1e9).toFixed(0) + "-" + (response.data.fast / 1e9).toFixed(0) + " GWei | Ethereum (ETH) Mainnet - GasNow - beaconcha.in - " + new Date().getFullYear();
- }.bind(this));
+ waitForTurnstileToken(()=>{
+ fetch('/gasnow/data', {
+ headers: {'X-TURNSTILE-TOKEN': window.turnstileToken}
+ })
+ .then((res)=>{
+ if (!res.ok) {
+ throw new Error(`Failed with status: ${res.status}`)
+ }
+ if(window.turnstile){
+ window.turnstile.reset(window.turnstileWidgetId);
+ window.turnstileToken = "";
+ }
+ return res.json()
+ })
+ .then((result)=>{
+ this.page = result;
+ document.title = (result.data.rapid / 1e9).toFixed(0) + "-" + (result.data.fast / 1e9).toFixed(0) + " GWei | Ethereum (ETH) Mainnet - GasNow - beaconcha.in - " + new Date().getFullYear();
+ }).catch((err)=>{
+ console.log(err)
+ })
+ .finally(()=>{
+ resetTurnstileToken()
+ })
+ })
this.updateIn = 5;
this.progress = 0;
} else {
diff --git a/templates/layout.html b/templates/layout.html
index 0d4e31065a..6a7f37e4f3 100644
--- a/templates/layout.html
+++ b/templates/layout.html
@@ -36,6 +36,12 @@
+
+ {{ if not (eq $.TurnstileSiteKey "") }}
+
+ {{ end }}
@@ -480,6 +500,13 @@
Links
GoDebug.initialize({{ . }})
{{ end }}
+
+ {{ if not (eq $.TurnstileSiteKey "") }}
+
+
+ {{ end }}