@@ -5,20 +5,127 @@ package hcaptcha
5
5
6
6
import (
7
7
"context"
8
+ "io"
9
+ "net/http"
10
+ "net/url"
11
+ "strings"
8
12
13
+ "code.gitea.io/gitea/modules/json"
9
14
"code.gitea.io/gitea/modules/setting"
10
-
11
- "go.jolheiser.com/hcaptcha"
12
15
)
13
16
17
+ const verifyURL = "https://hcaptcha.com/siteverify"
18
+
19
+ // Client is an hCaptcha client
20
+ type Client struct {
21
+ ctx context.Context
22
+ http * http.Client
23
+
24
+ secret string
25
+ }
26
+
27
+ // PostOptions are optional post form values
28
+ type PostOptions struct {
29
+ RemoteIP string
30
+ Sitekey string
31
+ }
32
+
33
+ // ClientOption is a func to modify a new Client
34
+ type ClientOption func (* Client )
35
+
36
+ // WithHTTP sets the http.Client of a Client
37
+ func WithHTTP (httpClient * http.Client ) func (* Client ) {
38
+ return func (hClient * Client ) {
39
+ hClient .http = httpClient
40
+ }
41
+ }
42
+
43
+ // WithContext sets the context.Context of a Client
44
+ func WithContext (ctx context.Context ) func (* Client ) {
45
+ return func (hClient * Client ) {
46
+ hClient .ctx = ctx
47
+ }
48
+ }
49
+
50
+ // New returns a new hCaptcha Client
51
+ func New (secret string , options ... ClientOption ) (* Client , error ) {
52
+ if strings .TrimSpace (secret ) == "" {
53
+ return nil , ErrMissingInputSecret
54
+ }
55
+
56
+ client := & Client {
57
+ ctx : context .Background (),
58
+ http : http .DefaultClient ,
59
+ secret : secret ,
60
+ }
61
+
62
+ for _ , opt := range options {
63
+ opt (client )
64
+ }
65
+
66
+ return client , nil
67
+ }
68
+
69
+ // Response is an hCaptcha response
70
+ type Response struct {
71
+ Success bool `json:"success"`
72
+ ChallengeTS string `json:"challenge_ts"`
73
+ Hostname string `json:"hostname"`
74
+ Credit bool `json:"credit,omitempty"`
75
+ ErrorCodes []ErrorCode `json:"error-codes"`
76
+ }
77
+
78
+ // Verify checks the response against the hCaptcha API
79
+ func (c * Client ) Verify (token string , opts PostOptions ) (* Response , error ) {
80
+ if strings .TrimSpace (token ) == "" {
81
+ return nil , ErrMissingInputResponse
82
+ }
83
+
84
+ post := url.Values {
85
+ "secret" : []string {c .secret },
86
+ "response" : []string {token },
87
+ }
88
+ if strings .TrimSpace (opts .RemoteIP ) != "" {
89
+ post .Add ("remoteip" , opts .RemoteIP )
90
+ }
91
+ if strings .TrimSpace (opts .Sitekey ) != "" {
92
+ post .Add ("sitekey" , opts .Sitekey )
93
+ }
94
+
95
+ // Basically a copy of http.PostForm, but with a context
96
+ req , err := http .NewRequestWithContext (c .ctx , http .MethodPost , verifyURL , strings .NewReader (post .Encode ()))
97
+ if err != nil {
98
+ return nil , err
99
+ }
100
+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
101
+
102
+ resp , err := c .http .Do (req )
103
+ if err != nil {
104
+ return nil , err
105
+ }
106
+
107
+ body , err := io .ReadAll (resp .Body )
108
+ if err != nil {
109
+ return nil , err
110
+ }
111
+ defer resp .Body .Close ()
112
+
113
+ var response * Response
114
+ if err := json .Unmarshal (body , & response ); err != nil {
115
+ return nil , err
116
+ }
117
+
118
+ return response , nil
119
+ }
120
+
14
121
// Verify calls hCaptcha API to verify token
15
122
func Verify (ctx context.Context , response string ) (bool , error ) {
16
- client , err := hcaptcha . New (setting .Service .HcaptchaSecret , hcaptcha . WithContext (ctx ))
123
+ client , err := New (setting .Service .HcaptchaSecret , WithContext (ctx ))
17
124
if err != nil {
18
125
return false , err
19
126
}
20
127
21
- resp , err := client .Verify (response , hcaptcha. PostOptions {
128
+ resp , err := client .Verify (response , PostOptions {
22
129
Sitekey : setting .Service .HcaptchaSitekey ,
23
130
})
24
131
if err != nil {
0 commit comments