Browse Source

cmd/faucet: protocol relative websockets, noauth mode

Péter Szilágyi 8 years ago
parent
commit
51a86f61be
5 changed files with 86 additions and 43 deletions
  1. 17 2
      cmd/faucet/faucet.go
  2. 6 6
      cmd/faucet/faucet.html
  3. 0 0
      cmd/faucet/website.go
  4. 18 6
      cmd/puppeth/module_faucet.go
  5. 45 29
      cmd/puppeth/wizard_faucet.go

+ 17 - 2
cmd/faucet/faucet.go

@@ -83,7 +83,8 @@ var (
 	captchaToken  = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
 	captchaToken  = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
 	captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server 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")
+	noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication")
+	logFlag    = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet")
 )
 )
 
 
 var (
 var (
@@ -132,6 +133,7 @@ func main() {
 		"Amounts":   amounts,
 		"Amounts":   amounts,
 		"Periods":   periods,
 		"Periods":   periods,
 		"Recaptcha": *captchaToken,
 		"Recaptcha": *captchaToken,
+		"NoAuth":    *noauthFlag,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		log.Crit("Failed to render the faucet template", "err", err)
 		log.Crit("Failed to render the faucet template", "err", err)
@@ -374,7 +376,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
 		if err = websocket.JSON.Receive(conn, &msg); err != nil {
 		if err = websocket.JSON.Receive(conn, &msg); err != nil {
 			return
 			return
 		}
 		}
-		if !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
+		if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
 			!strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
 			!strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
 			if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil {
 			if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil {
 				log.Warn("Failed to send URL error to client", "err", err)
 				log.Warn("Failed to send URL error to client", "err", err)
@@ -442,6 +444,8 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
 			username, avatar, address, err = authGooglePlus(msg.URL)
 			username, avatar, address, err = authGooglePlus(msg.URL)
 		case strings.HasPrefix(msg.URL, "https://www.facebook.com/"):
 		case strings.HasPrefix(msg.URL, "https://www.facebook.com/"):
 			username, avatar, address, err = authFacebook(msg.URL)
 			username, avatar, address, err = authFacebook(msg.URL)
+		case *noauthFlag:
+			username, avatar, address, err = authNoAuth(msg.URL)
 		default:
 		default:
 			err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
 			err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
 		}
 		}
@@ -776,3 +780,14 @@ func authFacebook(url string) (string, string, common.Address, error) {
 	}
 	}
 	return username + "@facebook", avatar, address, nil
 	return username + "@facebook", avatar, address, nil
 }
 }
+
+// authNoAuth tries to interpret a faucet request as a plain Ethereum address,
+// without actually performing any remote authentication. This mode is prone to
+// Byzantine attack, so only ever use for truly private networks.
+func authNoAuth(url string) (string, string, common.Address, error) {
+	address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(url))
+	if address == (common.Address{}) {
+		return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
+	}
+	return address.Hex() + "@noauth", "", address, nil
+}

+ 6 - 6
cmd/faucet/faucet.html

@@ -93,6 +93,11 @@
 
 
 							<dt style="width: auto; margin-left: 40px;"><i class="fa fa-facebook" aria-hidden="true" style="font-size: 36px;"></i></dt>
 							<dt style="width: auto; margin-left: 40px;"><i class="fa fa-facebook" aria-hidden="true" style="font-size: 36px;"></i></dt>
 							<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Facebook, publish a new <strong>public</strong> post with your Ethereum address embedded into the content (surrounding text doesn't matter).<br/>Copy-paste the <a href="https://www.facebook.com/help/community/question/?id=282662498552845" target="_about:blank">posts URL</a> into the above input box and fire away!</dd>
 							<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Facebook, publish a new <strong>public</strong> post with your Ethereum address embedded into the content (surrounding text doesn't matter).<br/>Copy-paste the <a href="https://www.facebook.com/help/community/question/?id=282662498552845" target="_about:blank">posts URL</a> into the above input box and fire away!</dd>
+
+							{{if .NoAuth}}
+								<dt class="text-danger" style="width: auto; margin-left: 40px;"><i class="fa fa-unlock-alt" aria-hidden="true" style="font-size: 36px;"></i></dt>
+								<dd class="text-danger" style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds <strong>without authentication</strong>, simply copy-paste your Ethereum address into the above input box (surrounding text doesn't matter) and fire away.<br/>This mode is susceptible to Byzantine attacks. Only use for debugging or private networks!</dd>
+							{{end}}
 						</dl>
 						</dl>
 						<p>You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
 						<p>You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
 						{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
 						{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
@@ -126,12 +131,7 @@
 			};
 			};
 			// Define a method to reconnect upon server loss
 			// Define a method to reconnect upon server loss
 			var reconnect = function() {
 			var reconnect = function() {
-				if (attempt % 2 == 0) {
-					server = new WebSocket("wss://" + location.host + "/api");
-				} else {
-					server = new WebSocket("ws://" + location.host + "/api");
-				}
-				attempt++;
+				server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
 
 
 				server.onmessage = function(event) {
 				server.onmessage = function(event) {
 					var msg = JSON.parse(event.data);
 					var msg = JSON.parse(event.data);

File diff suppressed because it is too large
+ 0 - 0
cmd/faucet/website.go


+ 18 - 6
cmd/puppeth/module_faucet.go

@@ -53,10 +53,10 @@ ADD account.pass /account.pass
 EXPOSE 8080
 EXPOSE 8080
 
 
 CMD [ \
 CMD [ \
-	"/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
-	"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}",          \
-	"--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", "--account.json", "/account.json", "--account.pass", "/account.pass"                       \
-	{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}                                                        \
+	"/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}",    \
+	"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}",             \
+	{{if .GitHubUser}}"--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", {{end}}"--account.json", "/account.json", "--account.pass", "/account.pass" \
+	{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}}                          \
 ]`
 ]`
 
 
 // faucetComposefile is the docker-compose.yml file required to deploy and maintain
 // faucetComposefile is the docker-compose.yml file required to deploy and maintain
@@ -81,7 +81,8 @@ services:
       - GITHUB_USER={{.GitHubUser}}
       - GITHUB_USER={{.GitHubUser}}
       - GITHUB_TOKEN={{.GitHubToken}}
       - GITHUB_TOKEN={{.GitHubToken}}
       - CAPTCHA_TOKEN={{.CaptchaToken}}
       - CAPTCHA_TOKEN={{.CaptchaToken}}
-      - CAPTCHA_SECRET={{.CaptchaSecret}}{{if .VHost}}
+      - CAPTCHA_SECRET={{.CaptchaSecret}}
+      - NO_AUTH={{.NoAuth}}{{if .VHost}}
       - VIRTUAL_HOST={{.VHost}}
       - VIRTUAL_HOST={{.VHost}}
       - VIRTUAL_PORT=8080{{end}}
       - VIRTUAL_PORT=8080{{end}}
     logging:
     logging:
@@ -114,6 +115,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
 		"FaucetAmount":  config.amount,
 		"FaucetAmount":  config.amount,
 		"FaucetMinutes": config.minutes,
 		"FaucetMinutes": config.minutes,
 		"FaucetTiers":   config.tiers,
 		"FaucetTiers":   config.tiers,
+		"NoAuth":        config.noauth,
 	})
 	})
 	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
 	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
 
 
@@ -132,6 +134,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
 		"FaucetAmount":  config.amount,
 		"FaucetAmount":  config.amount,
 		"FaucetMinutes": config.minutes,
 		"FaucetMinutes": config.minutes,
 		"FaucetTiers":   config.tiers,
 		"FaucetTiers":   config.tiers,
+		"NoAuth":        config.noauth,
 	})
 	})
 	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
 	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
 
 
@@ -161,6 +164,7 @@ type faucetInfos struct {
 	amount        int
 	amount        int
 	minutes       int
 	minutes       int
 	tiers         int
 	tiers         int
+	noauth        bool
 	githubUser    string
 	githubUser    string
 	githubToken   string
 	githubToken   string
 	captchaToken  string
 	captchaToken  string
@@ -179,7 +183,14 @@ func (info *faucetInfos) Report() map[string]string {
 		"Funding tiers":                strconv.Itoa(info.tiers),
 		"Funding tiers":                strconv.Itoa(info.tiers),
 		"Captha protection":            fmt.Sprintf("%v", info.captchaToken != ""),
 		"Captha protection":            fmt.Sprintf("%v", info.captchaToken != ""),
 		"Ethstats username":            info.node.ethstats,
 		"Ethstats username":            info.node.ethstats,
-		"GitHub authentication":        info.githubUser,
+	}
+	if info.githubUser != "" {
+		report["GitHub authentication"] = info.githubUser
+	} else {
+		report["GitHub authentication"] = "disabled, rate-limited"
+	}
+	if info.noauth {
+		report["Debug mode (no auth)"] = "enabled"
 	}
 	}
 	if info.node.keyJSON != "" {
 	if info.node.keyJSON != "" {
 		var key struct {
 		var key struct {
@@ -255,5 +266,6 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
 		githubToken:   infos.envvars["GITHUB_TOKEN"],
 		githubToken:   infos.envvars["GITHUB_TOKEN"],
 		captchaToken:  infos.envvars["CAPTCHA_TOKEN"],
 		captchaToken:  infos.envvars["CAPTCHA_TOKEN"],
 		captchaSecret: infos.envvars["CAPTCHA_SECRET"],
 		captchaSecret: infos.envvars["CAPTCHA_SECRET"],
+		noauth:        infos.envvars["NO_AUTH"] == "true",
 	}, nil
 	}, nil
 }
 }

+ 45 - 29
cmd/puppeth/wizard_faucet.go

@@ -87,34 +87,38 @@ func (w *wizard) deployFaucet() {
 	if infos.githubUser == "" {
 	if infos.githubUser == "" {
 		// No previous authorization (or new one requested)
 		// No previous authorization (or new one requested)
 		fmt.Println()
 		fmt.Println()
-		fmt.Println("Which GitHub user to verify Gists through?")
-		infos.githubUser = w.readString()
+		fmt.Println("Which GitHub user to verify Gists through? (default = none = rate-limited API)")
+		infos.githubUser = w.readDefaultString("")
 
 
-		fmt.Println()
-		fmt.Println("What is the GitHub personal access token of the user? (won't be echoed)")
-		infos.githubToken = w.readPassword()
-
-		// Do a sanity check query against github to ensure it's valid
-		req, _ := http.NewRequest("GET", "https://api.github.com/user", nil)
-		req.SetBasicAuth(infos.githubUser, infos.githubToken)
-		res, err := http.DefaultClient.Do(req)
-		if err != nil {
-			log.Error("Failed to verify GitHub authentication", "err", err)
-			return
-		}
-		defer res.Body.Close()
+		if infos.githubUser == "" {
+			log.Warn("Funding requests via GitHub will be heavily rate-limited")
+		} else {
+			fmt.Println()
+			fmt.Println("What is the GitHub personal access token of the user? (won't be echoed)")
+			infos.githubToken = w.readPassword()
+
+			// Do a sanity check query against github to ensure it's valid
+			req, _ := http.NewRequest("GET", "https://api.github.com/user", nil)
+			req.SetBasicAuth(infos.githubUser, infos.githubToken)
+			res, err := http.DefaultClient.Do(req)
+			if err != nil {
+				log.Error("Failed to verify GitHub authentication", "err", err)
+				return
+			}
+			defer res.Body.Close()
 
 
-		var msg struct {
-			Login   string `json:"login"`
-			Message string `json:"message"`
-		}
-		if err = json.NewDecoder(res.Body).Decode(&msg); err != nil {
-			log.Error("Failed to decode authorization response", "err", err)
-			return
-		}
-		if msg.Login != infos.githubUser {
-			log.Error("GitHub authorization failed", "user", infos.githubUser, "message", msg.Message)
-			return
+			var msg struct {
+				Login   string `json:"login"`
+				Message string `json:"message"`
+			}
+			if err = json.NewDecoder(res.Body).Decode(&msg); err != nil {
+				log.Error("Failed to decode authorization response", "err", err)
+				return
+			}
+			if msg.Login != infos.githubUser {
+				log.Error("GitHub authorization failed", "user", infos.githubUser, "message", msg.Message)
+				return
+			}
 		}
 		}
 	}
 	}
 	// Accessing the reCaptcha service requires API authorizations, request it
 	// Accessing the reCaptcha service requires API authorizations, request it
@@ -129,7 +133,9 @@ func (w *wizard) deployFaucet() {
 		// No previous authorization (or old one discarded)
 		// No previous authorization (or old one discarded)
 		fmt.Println()
 		fmt.Println()
 		fmt.Println("Enable reCaptcha protection against robots (y/n)? (default = no)")
 		fmt.Println("Enable reCaptcha protection against robots (y/n)? (default = no)")
-		if w.readDefaultString("n") == "y" {
+		if w.readDefaultString("n") == "n" {
+			log.Warn("Users will be able to requests funds via automated scripts")
+		} else {
 			// Captcha protection explicitly requested, read the site and secret keys
 			// Captcha protection explicitly requested, read the site and secret keys
 			fmt.Println()
 			fmt.Println()
 			fmt.Printf("What is the reCaptcha site key to authenticate human users?\n")
 			fmt.Printf("What is the reCaptcha site key to authenticate human users?\n")
@@ -175,7 +181,7 @@ func (w *wizard) deployFaucet() {
 			}
 			}
 		}
 		}
 	}
 	}
-	if infos.node.keyJSON == "" {
+	for i := 0; i < 3 && infos.node.keyJSON == ""; i++ {
 		fmt.Println()
 		fmt.Println()
 		fmt.Println("Please paste the faucet's funding account key JSON:")
 		fmt.Println("Please paste the faucet's funding account key JSON:")
 		infos.node.keyJSON = w.readJSON()
 		infos.node.keyJSON = w.readJSON()
@@ -186,9 +192,19 @@ func (w *wizard) deployFaucet() {
 
 
 		if _, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
 		if _, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
 			log.Error("Failed to decrypt key with given passphrase")
 			log.Error("Failed to decrypt key with given passphrase")
-			return
+			infos.node.keyJSON = ""
+			infos.node.keyPass = ""
 		}
 		}
 	}
 	}
+	// Check if the user wants to run the faucet in debug mode (noauth)
+	noauth := "n"
+	if infos.noauth {
+		noauth = "y"
+	}
+	fmt.Println()
+	fmt.Printf("Permit non-authenticated funding requests (y/n)? (default = %v)\n", infos.noauth)
+	infos.noauth = w.readDefaultString(noauth) != "n"
+
 	// Try to deploy the faucet server on the host
 	// Try to deploy the faucet server on the host
 	fmt.Println()
 	fmt.Println()
 	fmt.Printf("Should the faucet be built from scratch (y/n)? (default = no)\n")
 	fmt.Printf("Should the faucet be built from scratch (y/n)? (default = no)\n")

Some files were not shown because too many files changed in this diff