@@ -5,20 +5,127 @@ package hcaptcha
55
66import (
77 "context"
8+ "io"
9+ "net/http"
10+ "net/url"
11+ "strings"
812
13+ "code.gitea.io/gitea/modules/json"
914 "code.gitea.io/gitea/modules/setting"
10-
11- "go.jolheiser.com/hcaptcha"
1215)
1316
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+
14121// Verify calls hCaptcha API to verify token
15122func 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 ))
17124 if err != nil {
18125 return false , err
19126 }
20127
21- resp , err := client .Verify (response , hcaptcha. PostOptions {
128+ resp , err := client .Verify (response , PostOptions {
22129 Sitekey : setting .Service .HcaptchaSitekey ,
23130 })
24131 if err != nil {
0 commit comments