cmd/faucet: add optional recaptcha validation support

This commit is contained in:
Péter Szilágyi 2017-04-16 19:49:40 +03:00
parent cb3f5f8b93
commit 03dffe3efd
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
3 changed files with 49 additions and 13 deletions

View File

@ -29,6 +29,7 @@ import (
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -73,6 +74,9 @@ var (
githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access") githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access")
githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with") githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with")
captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet")
) )
@ -96,9 +100,10 @@ func main() {
} }
website := new(bytes.Buffer) website := new(bytes.Buffer)
template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{ template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
"Network": *netnameFlag, "Network": *netnameFlag,
"Amount": *payoutFlag, "Amount": *payoutFlag,
"Period": period, "Period": period,
"Recaptcha": *captchaToken,
}) })
// Load and parse the genesis block requested by the user // Load and parse the genesis block requested by the user
blob, err := ioutil.ReadFile(*genesisFlag) blob, err := ioutil.ReadFile(*genesisFlag)
@ -297,7 +302,8 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
for { for {
// Fetch the next funding request and validate against github // Fetch the next funding request and validate against github
var msg struct { var msg struct {
URL string `json:"url"` URL string `json:"url"`
Captcha string `json:"captcha"`
} }
if err := websocket.JSON.Receive(conn, &msg); err != nil { if err := websocket.JSON.Receive(conn, &msg); err != nil {
return return
@ -306,8 +312,35 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"}) websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"})
continue continue
} }
log.Info("Faucet funds requested", "addr", conn.RemoteAddr(), "gist", msg.URL) log.Info("Faucet funds requested", "gist", msg.URL)
// If captcha verifications are enabled, make sure we're not dealing with a robot
if *captchaToken != "" {
form := url.Values{}
form.Add("secret", *captchaSecret)
form.Add("response", msg.Captcha)
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
if err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
continue
}
var result struct {
Success bool `json:"success"`
Errors json.RawMessage `json:"error-codes"`
}
err = json.NewDecoder(res.Body).Decode(&result)
res.Body.Close()
if err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
continue
}
if !result.Success {
log.Warn("Captcha verification failed", "err", string(result.Errors))
websocket.JSON.Send(conn, map[string]string{"error": "Beep-boop, you're a robot!"})
continue
}
}
// Retrieve the gist from the GitHub Gist APIs // Retrieve the gist from the GitHub Gist APIs
parts := strings.Split(msg.URL, "/") parts := strings.Split(msg.URL, "/")
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil) req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
@ -334,7 +367,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
continue continue
} }
if gist.Owner.Login == "" { if gist.Owner.Login == "" {
websocket.JSON.Send(conn, map[string]string{"error": "Nice try ;)"}) websocket.JSON.Send(conn, map[string]string{"error": "Anonymous Gists not allowed"})
continue continue
} }
// Iterate over all the files and look for Ethereum addresses // Iterate over all the files and look for Ethereum addresses
@ -349,7 +382,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
continue continue
} }
// Validate the user's existence since the API is unhelpful here // Validate the user's existence since the API is unhelpful here
if res, err = http.Head("https://github.com/%s", gist.Owner.Login); err != nil { if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()}) websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
continue continue
} }

View File

@ -51,9 +51,10 @@
<div class="input-group"> <div class="input-group">
<input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address..."> <input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address...">
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="submit()">Give me Ether!</button> <button class="btn btn-default" type="button" onclick="{{if .Recaptcha}}grecaptcha.execute(){{else}}submit(){{end}}">Give me Ether!</button>
</span> </span>
</div> </div>{{if .Recaptcha}}
<div class="g-recaptcha" data-sitekey="{{.Recaptcha}}" data-callback="submit" data-size="invisible"></div>{{end}}
</div> </div>
</div> </div>
<div class="row" style="margin-top: 32px;"> <div class="row" style="margin-top: 32px;">
@ -89,8 +90,9 @@
var server; var server;
// Define the function that submits a gist url to the server // Define the function that submits a gist url to the server
var submit = function() { var submit = function({{if .Recaptcha}}captcha{{end}}) {
server.send(JSON.stringify({url: $("#gist")[0].value})); server.send(JSON.stringify({url: $("#gist")[0].value{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
grecaptcha.reset();{{end}}
}; };
// Define a method to reconnect upon server loss // Define a method to reconnect upon server loss
var reconnect = function() { var reconnect = function() {
@ -138,6 +140,7 @@
} }
// Establish a websocket connection to the API server // Establish a websocket connection to the API server
reconnect(); reconnect();
</script> </script>{{if .Recaptcha}}
<script src="https://www.google.com/recaptcha/api.js" async defer></script>{{end}}
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long