forked from gnolang/gno
-
Notifications
You must be signed in to change notification settings - Fork 0
/
middleware.go
177 lines (146 loc) · 4.16 KB
/
middleware.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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) {
// Determine the remote address
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(
w,
fmt.Sprintf("invalid request IP and port, %s", err.Error()),
http.StatusUnauthorized,
)
return
}
// Check if the request is behind a proxy
if xff := r.Header.Get("X-Forwarded-For"); xff != "" && behindProxy {
host = xff
}
// 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
}