From 0c2992922fecffb67f53b602b17eb6a85835c9f5 Mon Sep 17 00:00:00 2001
From: Mudit Gupta <guptamudit@ymail.com>
Date: Fri, 11 Dec 2020 15:05:39 +0530
Subject: [PATCH] cmd/faucet: use Twitter API instead of scraping webpage
 (#21850)

This PR adds support for using Twitter API to query the tweet and author details. There are two reasons behind this change:

- Twitter will be deprecating the legacy website on 15th December. The current method is expected to stop working then.
- More importantly, the current system uses Twitter handle for spam protection but the Twitter handle can be changed via automated calls. This allows bots to use the same tweet to withdraw funds infinite times as long as they keep changing their handle between every request. The Rinkeby as well as the Goerli faucet are being actively drained via this method. This PR changes the spam protection to be based on Twitter IDs instead of usernames. A user can not change their Twitter ID.

# Conflicts:
#	cmd/faucet/faucet.go
---
 cmd/puppeth/module_faucet.go |  7 +++++++
 cmd/puppeth/wizard_faucet.go | 23 +++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go
index 348e984810..6ae17113ad 100644
--- a/cmd/puppeth/module_faucet.go
+++ b/cmd/puppeth/module_faucet.go
@@ -46,6 +46,7 @@ ENTRYPOINT [ \
 	"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}",             \
 	"--account.json", "/account.json", "--account.pass", "/account.pass"                                                                                                    \
 	{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}}                          \
+	{{if .TwitterToken}}, "--twitter.token", "{{.TwitterToken}}",
 ]`
 
 // faucetComposefile is the docker-compose.yml file required to deploy and maintain
@@ -71,6 +72,7 @@ services:
       - FAUCET_TIERS={{.FaucetTiers}}
       - CAPTCHA_TOKEN={{.CaptchaToken}}
       - CAPTCHA_SECRET={{.CaptchaSecret}}
+      - TWITTER_TOKEN={{.TwitterToken}}
       - NO_AUTH={{.NoAuth}}{{if .VHost}}
       - VIRTUAL_HOST={{.VHost}}
       - VIRTUAL_PORT=8080{{end}}
@@ -103,6 +105,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
 		"FaucetMinutes": config.minutes,
 		"FaucetTiers":   config.tiers,
 		"NoAuth":        config.noauth,
+		"TwitterToken":  config.twitterToken,
 	})
 	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
 
@@ -120,6 +123,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
 		"FaucetMinutes": config.minutes,
 		"FaucetTiers":   config.tiers,
 		"NoAuth":        config.noauth,
+		"TwitterToken":  config.twitterToken,
 	})
 	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
 
@@ -152,6 +156,7 @@ type faucetInfos struct {
 	noauth        bool
 	captchaToken  string
 	captchaSecret string
+	twitterToken  string
 }
 
 // Report converts the typed struct into a plain string->string map, containing
@@ -165,6 +170,7 @@ func (info *faucetInfos) Report() map[string]string {
 		"Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes),
 		"Funding tiers":                strconv.Itoa(info.tiers),
 		"Captha protection":            fmt.Sprintf("%v", info.captchaToken != ""),
+		"Using Twitter API":            fmt.Sprintf("%v", info.twitterToken != ""),
 		"Ethstats username":            info.node.ethstats,
 	}
 	if info.noauth {
@@ -243,5 +249,6 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
 		captchaToken:  infos.envvars["CAPTCHA_TOKEN"],
 		captchaSecret: infos.envvars["CAPTCHA_SECRET"],
 		noauth:        infos.envvars["NO_AUTH"] == "true",
+		twitterToken:  infos.envvars["TWITTER_TOKEN"],
 	}, nil
 }
diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go
index c91ef72674..c4103b1617 100644
--- a/cmd/puppeth/wizard_faucet.go
+++ b/cmd/puppeth/wizard_faucet.go
@@ -102,6 +102,29 @@ func (w *wizard) deployFaucet() {
 			infos.captchaSecret = w.readPassword()
 		}
 	}
+
+	// Accessing the twitter api requires a bearer token, request it
+	if infos.twitterToken != "" {
+		fmt.Println()
+		fmt.Println("Reuse previous twitter API Bearer token (y/n)? (default = yes)")
+		if !w.readDefaultYesNo(true) {
+			infos.twitterToken = ""
+		}
+	}
+	if infos.twitterToken == "" {
+		// No previous twitter token (or old one discarded)
+		fmt.Println()
+		fmt.Println("Enable twitter API (y/n)? (default = no)")
+		if !w.readDefaultYesNo(false) {
+			log.Warn("The faucet will fallback to using direct calls")
+		} else {
+			// Twitter api explicitly requested, read the bearer token
+			fmt.Println()
+			fmt.Printf("What is the twitter API Bearer token?\n")
+			infos.twitterToken = w.readString()
+		}
+	}
+
 	// Figure out where the user wants to store the persistent data
 	fmt.Println()
 	if infos.node.datadir == "" {
-- 
GitLab