Skip to content

Commit

Permalink
feat: add hcaptcha verification feature
Browse files Browse the repository at this point in the history
  • Loading branch information
iczc committed Sep 14, 2023
1 parent 20f049d commit 6bbfaf7
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 48 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,17 @@ Then run the faucet application without the wallet command-line flags:

The following are the available command-line flags(excluding above wallet flags):

| Flag | Description | Default Value |
|-----------------|--------------------------------------------------|---------------|
| -httpport | Listener port to serve HTTP connection | 8080 |
| -proxycount | Count of reverse proxies in front of the server | 0 |
| -queuecap | Maximum transactions waiting to be sent | 100 |
| -faucet.amount | Number of Ethers to transfer per user request | 1 |
| -faucet.minutes | Number of minutes to wait between funding rounds | 1440 |
| -faucet.name | Network name to display on the frontend | testnet |
| -faucet.symbol | Token symbol to display on the frontend | ETH |
| Flag | Description | Default Value |
|-------------------|--------------------------------------------------|---------------|
| -httpport | Listener port to serve HTTP connection | 8080 |
| -proxycount | Count of reverse proxies in front of the server | 0 |
| -queuecap | Maximum transactions waiting to be sent | 100 |
| -faucet.amount | Number of Ethers to transfer per user request | 1 |
| -faucet.minutes | Number of minutes to wait between funding rounds | 1440 |
| -faucet.name | Network name to display on the frontend | testnet |
| -faucet.symbol | Token symbol to display on the frontend | ETH |
| -hcaptcha.sitekey | hCaptcha sitekey | |
| -hcaptcha.secret | hCaptcha secret | |

### Docker deployment

Expand Down
5 changes: 4 additions & 1 deletion cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ var (
keyPassFlag = flag.String("wallet.keypass", "password.txt", "Passphrase text file to decrypt keystore")
privKeyFlag = flag.String("wallet.privkey", os.Getenv("PRIVATE_KEY"), "Private key hex to fund user requests with")
providerFlag = flag.String("wallet.provider", os.Getenv("WEB3_PROVIDER"), "Endpoint for Ethereum JSON-RPC connection")

hcaptchaSiteKeyFlag = flag.String("hcaptcha.sitekey", os.Getenv("HCAPTCHA_SITEKEY"), "hCaptcha sitekey")
hcaptchaSecretFlag = flag.String("hcaptcha.secret", os.Getenv("HCAPTCHA_SECRET"), "hCaptcha secret")
)

func init() {
Expand All @@ -58,7 +61,7 @@ func Execute() {
if err != nil {
panic(fmt.Errorf("cannot connect to web3 provider: %w", err))
}
config := server.NewConfig(*netnameFlag, *symbolFlag, *httpPortFlag, *intervalFlag, *payoutFlag, *proxyCntFlag, *queueCapFlag)
config := server.NewConfig(*netnameFlag, *symbolFlag, *httpPortFlag, *intervalFlag, *payoutFlag, *proxyCntFlag, *queueCapFlag, *hcaptchaSiteKeyFlag, *hcaptchaSecretFlag)
go server.NewServer(txBuilder, config).Run()

c := make(chan os.Signal, 1)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/agiledragon/gomonkey/v2 v2.10.1
github.com/ethereum/go-ethereum v1.10.26
github.com/jellydator/ttlcache/v2 v2.11.1
github.com/kataras/hcaptcha v0.0.2
github.com/sirupsen/logrus v1.9.3
github.com/urfave/negroni v1.0.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/kataras/hcaptcha v0.0.2 h1:8gPteB5vPD1WvsKv4OcYF+EfntCY7cm7s1b8bB9ai7Y=
github.com/kataras/hcaptcha v0.0.2/go.mod h1:Ce7mO5B8q8RKyWWWJt2fczJ3O1vTlX+mZ2DZZOMnfSw=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
Expand Down
34 changes: 19 additions & 15 deletions internal/server/config.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package server

type Config struct {
network string
symbol string
httpPort int
interval int
payout int
proxyCount int
queueCap int
network string
symbol string
httpPort int
interval int
payout int
proxyCount int
queueCap int
hcaptchaSiteKey string
hcaptchaSecret string
}

func NewConfig(network, symbol string, httpPort, interval, payout, proxyCount, queueCap int) *Config {
func NewConfig(network, symbol string, httpPort, interval, payout, proxyCount, queueCap int, hcaptchaSiteKey, hcaptchaSecret string) *Config {
return &Config{
network: network,
symbol: symbol,
httpPort: httpPort,
interval: interval,
payout: payout,
proxyCount: proxyCount,
queueCap: queueCap,
network: network,
symbol: symbol,
httpPort: httpPort,
interval: interval,
payout: payout,
proxyCount: proxyCount,
queueCap: queueCap,
hcaptchaSiteKey: hcaptchaSiteKey,
hcaptchaSecret: hcaptchaSecret,
}
}
9 changes: 5 additions & 4 deletions internal/server/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ type claimResponse struct {
}

type infoResponse struct {
Account string `json:"account"`
Network string `json:"network"`
Payout string `json:"payout"`
Symbol string `json:"symbol"`
Account string `json:"account"`
Network string `json:"network"`
Payout string `json:"payout"`
Symbol string `json:"symbol"`
HcaptchaSiteKey string `json:"hcaptcha_sitekey,omitempty"`
}

type malformedRequest struct {
Expand Down
30 changes: 30 additions & 0 deletions internal/server/limiter.go → internal/server/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/jellydator/ttlcache/v2"
"github.com/kataras/hcaptcha"
log "github.com/sirupsen/logrus"
"github.com/urfave/negroni"
)
Expand Down Expand Up @@ -99,3 +100,32 @@ func getClientIPFromRequest(proxyCount int, r *http.Request) string {
}
return remoteIP
}

type Captcha struct {
client *hcaptcha.Client
secret string
}

func NewCaptcha(hcaptchaSiteKey, hcaptchaSecret string) *Captcha {
client := hcaptcha.New(hcaptchaSecret)
client.SiteKey = hcaptchaSiteKey
return &Captcha{
client: client,
secret: hcaptchaSecret,
}
}

func (c *Captcha) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if c.secret == "" {
next.ServeHTTP(w, r)
return
}

response := c.client.VerifyToken(r.Header.Get("h-captcha-response"))
if !response.Success {
renderJSON(w, claimResponse{Message: "Captcha verification failed, please try again"}, http.StatusTooManyRequests)
return
}

next.ServeHTTP(w, r)
}
12 changes: 7 additions & 5 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func (s *Server) setupRouter() *http.ServeMux {
router := http.NewServeMux()
router.Handle("/", http.FileServer(web.Dist()))
limiter := NewLimiter(s.cfg.proxyCount, time.Duration(s.cfg.interval)*time.Minute)
router.Handle("/api/claim", negroni.New(limiter, negroni.Wrap(s.handleClaim())))
hcaptcha := NewCaptcha(s.cfg.hcaptchaSiteKey, s.cfg.hcaptchaSecret)
router.Handle("/api/claim", negroni.New(limiter, hcaptcha, negroni.Wrap(s.handleClaim())))
router.Handle("/api/info", s.handleInfo())

return router
Expand Down Expand Up @@ -126,10 +127,11 @@ func (s *Server) handleInfo() http.HandlerFunc {
return
}
renderJSON(w, infoResponse{
Account: s.Sender().String(),
Network: s.cfg.network,
Symbol: s.cfg.symbol,
Payout: strconv.Itoa(s.cfg.payout),
Account: s.Sender().String(),
Network: s.cfg.network,
Symbol: s.cfg.symbol,
Payout: strconv.Itoa(s.cfg.payout),
HcaptchaSiteKey: s.cfg.hcaptchaSiteKey,
}, http.StatusOK)
}
}
73 changes: 59 additions & 14 deletions web/src/Faucet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,33 @@
network: 'testnet',
payout: 1,
symbol: 'ETH',
hcaptcha_sitekey: '',
};
$: document.title = `${faucetInfo.symbol} ${capitalize(
faucetInfo.network,
)} Faucet`;
let mounted = false;
let hcaptchaLoaded = false;
onMount(async () => {
const res = await fetch('/api/info');
faucetInfo = await res.json();
mounted = true;
});
window.hcaptchaOnLoad = () => {
hcaptchaLoaded = true;
};
$: document.title = `${faucetInfo.symbol} ${capitalize(
faucetInfo.network,
)} Faucet`;
let widgetID;
$: if (mounted && hcaptchaLoaded) {
widgetID = window.hcaptcha.render('hcaptcha', {
sitekey: faucetInfo.hcaptcha_sitekey,
});
}
setToast({
position: 'bottom-center',
dismissible: true,
Expand All @@ -31,6 +47,11 @@
async function handleRequest() {
let address = input;
if (address === null) {
toast({ message: 'input required', type: 'is-warning' });
return;
}
if (address.endsWith('.eth')) {
try {
const provider = new CloudflareProvider();
Expand All @@ -52,19 +73,32 @@
return;
}
const res = await fetch('/api/claim', {
method: 'POST',
headers: {
try {
let headers = {
'Content-Type': 'application/json',
},
body: JSON.stringify({
address,
}),
});
};
let { msg } = await res.json();
let type = res.ok ? 'is-success' : 'is-warning';
toast({ message: msg, type });
if (hcaptchaLoaded) {
const { response } = await window.hcaptcha.execute(widgetID, {
async: true,
});
headers['h-captcha-response'] = response;
}
const res = await fetch('/api/claim', {
method: 'POST',
headers,
body: JSON.stringify({
address,
}),
});
let { msg } = await res.json();
let type = res.ok ? 'is-success' : 'is-warning';
toast({ message: msg, type });
} catch (err) {
console.error(err);
}
}
function capitalize(str) {
Expand All @@ -73,6 +107,16 @@
}
</script>

<svelte:head>
{#if mounted && faucetInfo.hcaptcha_sitekey}
<script
src="https://hcaptcha.com/1/api.js?onload=hcaptchaOnLoad&render=explicit"
async
defer
></script>
{/if}
</svelte:head>

<main>
<section class="hero is-info is-fullheight">
<div class="hero-head">
Expand Down Expand Up @@ -115,6 +159,7 @@
<h2 class="subtitle">
Serving from {faucetInfo.account}
</h2>
<div id="hcaptcha" data-size="invisible"></div>
<div class="box">
<div class="field is-grouped">
<p class="control is-expanded">
Expand Down

0 comments on commit 6bbfaf7

Please sign in to comment.