diff options
Diffstat (limited to 'modules/hcaptcha/hcaptcha.go')
-rw-r--r-- | modules/hcaptcha/hcaptcha.go | 115 |
1 files changed, 111 insertions, 4 deletions
diff --git a/modules/hcaptcha/hcaptcha.go b/modules/hcaptcha/hcaptcha.go index 4d20cfd483..b970d491c5 100644 --- a/modules/hcaptcha/hcaptcha.go +++ b/modules/hcaptcha/hcaptcha.go @@ -5,20 +5,127 @@ package hcaptcha import ( "context" + "io" + "net/http" + "net/url" + "strings" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" - - "go.jolheiser.com/hcaptcha" ) +const verifyURL = "https://hcaptcha.com/siteverify" + +// Client is an hCaptcha client +type Client struct { + ctx context.Context + http *http.Client + + secret string +} + +// PostOptions are optional post form values +type PostOptions struct { + RemoteIP string + Sitekey string +} + +// ClientOption is a func to modify a new Client +type ClientOption func(*Client) + +// WithHTTP sets the http.Client of a Client +func WithHTTP(httpClient *http.Client) func(*Client) { + return func(hClient *Client) { + hClient.http = httpClient + } +} + +// WithContext sets the context.Context of a Client +func WithContext(ctx context.Context) func(*Client) { + return func(hClient *Client) { + hClient.ctx = ctx + } +} + +// New returns a new hCaptcha Client +func New(secret string, options ...ClientOption) (*Client, error) { + if strings.TrimSpace(secret) == "" { + return nil, ErrMissingInputSecret + } + + client := &Client{ + ctx: context.Background(), + http: http.DefaultClient, + secret: secret, + } + + for _, opt := range options { + opt(client) + } + + return client, nil +} + +// Response is an hCaptcha response +type Response struct { + Success bool `json:"success"` + ChallengeTS string `json:"challenge_ts"` + Hostname string `json:"hostname"` + Credit bool `json:"credit,omitempty"` + ErrorCodes []ErrorCode `json:"error-codes"` +} + +// Verify checks the response against the hCaptcha API +func (c *Client) Verify(token string, opts PostOptions) (*Response, error) { + if strings.TrimSpace(token) == "" { + return nil, ErrMissingInputResponse + } + + post := url.Values{ + "secret": []string{c.secret}, + "response": []string{token}, + } + if strings.TrimSpace(opts.RemoteIP) != "" { + post.Add("remoteip", opts.RemoteIP) + } + if strings.TrimSpace(opts.Sitekey) != "" { + post.Add("sitekey", opts.Sitekey) + } + + // Basically a copy of http.PostForm, but with a context + req, err := http.NewRequestWithContext(c.ctx, http.MethodPost, verifyURL, strings.NewReader(post.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := c.http.Do(req) + if err != nil { + return nil, err + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var response *Response + if err := json.Unmarshal(body, &response); err != nil { + return nil, err + } + + return response, nil +} + // Verify calls hCaptcha API to verify token func Verify(ctx context.Context, response string) (bool, error) { - client, err := hcaptcha.New(setting.Service.HcaptchaSecret, hcaptcha.WithContext(ctx)) + client, err := New(setting.Service.HcaptchaSecret, WithContext(ctx)) if err != nil { return false, err } - resp, err := client.Verify(response, hcaptcha.PostOptions{ + resp, err := client.Verify(response, PostOptions{ Sitekey: setting.Service.HcaptchaSitekey, }) if err != nil { |