Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: swap legacy faucet implementation #1614

Merged
merged 16 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contribs/gnodev/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/gnolang/gno v0.0.0-00010101000000-000000000000
github.com/gorilla/websocket v1.5.1
go.uber.org/zap v1.26.0
golang.org/x/term v0.17.0
golang.org/x/term v0.18.0
)

require (
Expand Down Expand Up @@ -46,7 +46,7 @@ require (
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
Expand Down
11 changes: 5 additions & 6 deletions contribs/gnodev/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contribs/gnokeykc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ require (
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
Expand Down
7 changes: 3 additions & 4 deletions contribs/gnokeykc/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 0 additions & 38 deletions gno.land/cmd/gnofaucet/README.md

This file was deleted.

177 changes: 177 additions & 0 deletions gno.land/cmd/gnofaucet/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"strings"
"time"
)

// getIPMiddleware returns the IP verification middleware, using the given subnet throttler
func getIPMiddleware(behindProxy bool, st *ipThrottler) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
var host string

// Check if the request is behind a proxy
if behindProxy {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
host = xff
}
} else {
// Determine the remote address
address, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
}

host = address
}
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved

// If the host is empty or IPv6 loopback, set it to IPv4 loopback
switch host {
case "", ipv6Loopback, ipv6ZeroAddr:
host = ipv4Loopback
}

// Make sure the host IP is valid
hostAddr, err := netip.ParseAddr(host)
if err != nil {
http.Error(
w,
fmt.Sprintf("invalid request IP, %s", err.Error()),
http.StatusUnauthorized,
)

return
}

// Register the request with the throttler
if err := st.registerNewRequest(hostAddr); err != nil {
http.Error(
w,
fmt.Sprintf("unable to verify IP request, %s", err.Error()),
http.StatusUnauthorized,
)

return
}

// Continue with serving the faucet request
next.ServeHTTP(w, r)
},
)
}
}

// getCaptchaMiddleware returns the captcha middleware, if any
func getCaptchaMiddleware(secret string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// Check if the captcha is enabled
if secret == "" {
// Continue with serving the faucet request
next.ServeHTTP(w, r)

return
}

// Parse the request to extract the captcha secret
var request struct {
Captcha string `json:"captcha"`
}

body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "unable to read request body", http.StatusInternalServerError)

return
}

// Close the original body
if err := r.Body.Close(); err != nil {
http.Error(w, "unable to close request body", http.StatusInternalServerError)

return
}

// Create a new ReadCloser from the read bytes
// so that future middleware will be able to read
r.Body = io.NopCloser(bytes.NewReader(body))

// Decode the original request
if err := json.NewDecoder(bytes.NewBuffer(body)).Decode(&request); err != nil {
http.Error(w, "invalid captcha request", http.StatusBadRequest)

return
}

// Verify the captcha response
if err := checkRecaptcha(secret, strings.TrimSpace(request.Captcha)); err != nil {
http.Error(w, "invalid captcha", http.StatusUnauthorized)

return
}

// Continue with serving the faucet request
next.ServeHTTP(w, r)
},
)
}
}

// checkRecaptcha checks the captcha challenge
func checkRecaptcha(secret, response string) error {
// Create an HTTP client with a timeout
client := &http.Client{
Timeout: time.Second * 10,
}

// Create the request
req, err := http.NewRequest(
http.MethodPost,
siteVerifyURL,
nil,
)
if err != nil {
return fmt.Errorf("unable to create request, %w", err)
}

// Craft the request query string
q := req.URL.Query()
q.Add("secret", secret)
q.Add("response", response)
req.URL.RawQuery = q.Encode()

// Execute the verify request
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("unable to execute request, %w", err)
}
defer resp.Body.Close()

// Verify the captcha-verify response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code, %d", resp.StatusCode)
}

// Decode the response body
var body SiteVerifyResponse
if err = json.NewDecoder(resp.Body).Decode(&body); err != nil {
return fmt.Errorf("failed to decode response, %w", err)
}

// Check if the recaptcha verification was successful
if !body.Success {
return errInvalidCaptcha
}

return nil
}
Loading
Loading