-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hcaptcha.go
125 lines (103 loc) · 3.92 KB
/
hcaptcha.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
package captcha
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
)
var (
ErrHCaptchaMissingInputSecret = errors.New("your secret key is missing")
ErrHCaptchaInvalidInputSecret = errors.New("your secret key is invalid or malformed")
ErrHCaptchaMissingInputResponse = errors.New("the response parameter (verification token) is missing")
ErrHCaptchaInvalidInputResponse = errors.New("the response parameter (verification token) is invalid or malformed")
ErrHCaptchaBadRequest = errors.New("the request is invalid or malformed")
ErrHCaptchaInvalidOrAlreadySeenResponse = errors.New("the response parameter has already been checked, or has another issue")
ErrHCaptchaSitekeySecretMismatch = errors.New("the sitekey is not registered with the provided secret")
)
type hcaptchaClient struct {
client *http.Client
privateKey string
publicKey string
}
func (c *hcaptchaClient) CheckRequest(r *http.Request) (resp *Response, err error) {
if err := r.ParseForm(); err != nil {
return nil, err
}
return c.CheckPost(r.PostForm.Get("h-captcha-response"), r.RemoteAddr)
}
func (c *hcaptchaClient) CheckPost(post string, ip string) (ret *Response, err error) {
// Build request
form := url.Values{}
form.Add("sitekey", c.publicKey)
form.Add("secret", c.privateKey)
form.Add("response", post)
form.Add("remoteip", ip)
req, err := http.NewRequest("POST", "https://hcaptcha.com/siteverify", bytes.NewBufferString(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// Make request
if c.client == nil {
c.client = http.DefaultClient
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
//goland:noinspection GoUnhandledErrorResult
defer resp.Body.Close()
// Read response
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var hcaptchaResponse hcaptchaResponse
err = json.Unmarshal(b, &hcaptchaResponse)
if err != nil {
return nil, err
}
// Build new response
ret = &Response{
Time: hcaptchaResponse.ChallengeTS,
HostName: hcaptchaResponse.Hostname,
Success: hcaptchaResponse.Success,
}
if len(hcaptchaResponse.ErrorCodes) > 0 {
var errorMap = map[string]error{
"missing-input-secret": ErrHCaptchaMissingInputSecret,
"invalid-input-secret": ErrHCaptchaInvalidInputSecret,
"missing-input-response": ErrHCaptchaMissingInputResponse,
"invalid-input-response": ErrHCaptchaInvalidInputResponse,
"bad-request": ErrHCaptchaBadRequest,
"invalid-or-already-seen-response": ErrHCaptchaInvalidOrAlreadySeenResponse,
"sitekey-secret-mismatch": ErrHCaptchaSitekeySecretMismatch,
}
for _, errorCode := range hcaptchaResponse.ErrorCodes {
if err, ok := errorMap[errorCode]; ok {
ret.Errors = append(ret.Errors, err)
} else {
ret.Errors = append(ret.Errors, errors.New(errorCode))
}
}
}
return ret, nil
}
func (c *hcaptchaClient) SetClient(client *http.Client) {
c.client = client
}
func (c *hcaptchaClient) setKeys(private, public string) {
c.privateKey = private
c.publicKey = public
}
type hcaptchaResponse struct {
Success bool `json:"success"` // is the passcode valid, and does it meet security criteria you specified, e.g. sitekey?
ChallengeTS string `json:"challenge_ts"` // timestamp of the captcha (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
Hostname string `json:"hostname"` // the hostname of the site where the captcha was solved
Credit bool `json:"credit"` // optional: whether the response will be credited
ErrorCodes []string `json:"error-codes"` // optional: any error codes
Score float64 `json:"score"` // ENTERPRISE feature: a score denoting malicious activity.
Reason []string `json:"score_reason"` // ENTERPRISE feature: reason(s) for score. See BotStop.com for details.
}