diff options
author | John Olheiser <john.olheiser@gmail.com> | 2020-10-02 22:37:53 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-02 23:37:53 -0400 |
commit | 72636fd6642fcae6e7447dd499cb097c5c65ab32 (patch) | |
tree | 2138dde0d4e1fc4d0d90943b34d50f40942fada3 /modules | |
parent | 5460bf89031a77ac9e0cde685c1bff00c29ee883 (diff) | |
download | gitea-72636fd6642fcae6e7447dd499cb097c5c65ab32.tar.gz gitea-72636fd6642fcae6e7447dd499cb097c5c65ab32.zip |
hCaptcha Support (#12594)
* Initial work on hCaptcha
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Use module
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Format
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* At least return and debug log a captcha error
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Pass context to hCaptcha
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Add context to recaptcha
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* fix lint
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Finish hcaptcha
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Update example config
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Apply error fix for recaptcha
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Change recaptcha ChallengeTS to string
Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/auth/user_form.go | 1 | ||||
-rw-r--r-- | modules/auth/user_form_auth_openid.go | 1 | ||||
-rw-r--r-- | modules/hcaptcha/hcaptcha.go | 34 | ||||
-rw-r--r-- | modules/recaptcha/recaptcha.go | 60 | ||||
-rw-r--r-- | modules/setting/service.go | 4 | ||||
-rw-r--r-- | modules/setting/setting.go | 1 |
6 files changed, 92 insertions, 9 deletions
diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 999d4cd74d..e657f78e6d 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -83,6 +83,7 @@ type RegisterForm struct { Password string `binding:"MaxSize(255)"` Retype string GRecaptchaResponse string `form:"g-recaptcha-response"` + HcaptchaResponse string `form:"h-captcha-response"` } // Validate validates the fields diff --git a/modules/auth/user_form_auth_openid.go b/modules/auth/user_form_auth_openid.go index c1d19b9bb4..841dbd840a 100644 --- a/modules/auth/user_form_auth_openid.go +++ b/modules/auth/user_form_auth_openid.go @@ -25,6 +25,7 @@ type SignUpOpenIDForm struct { UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` Email string `binding:"Required;Email;MaxSize(254)"` GRecaptchaResponse string `form:"g-recaptcha-response"` + HcaptchaResponse string `form:"h-captcha-response"` } // Validate validates the fields diff --git a/modules/hcaptcha/hcaptcha.go b/modules/hcaptcha/hcaptcha.go new file mode 100644 index 0000000000..95fe2dd1c3 --- /dev/null +++ b/modules/hcaptcha/hcaptcha.go @@ -0,0 +1,34 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package hcaptcha + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" + + "go.jolheiser.com/hcaptcha" +) + +// 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)) + if err != nil { + return false, err + } + + resp, err := client.Verify(response, hcaptcha.PostOptions{ + Sitekey: setting.Service.HcaptchaSitekey, + }) + if err != nil { + return false, err + } + + var respErr error + if len(resp.ErrorCodes) > 0 { + respErr = resp.ErrorCodes[0] + } + return resp.Success, respErr +} diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index a9718f2fdd..54ea1dc0b3 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -5,12 +5,13 @@ package recaptcha import ( + "context" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" - "time" + "strings" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -18,18 +19,29 @@ import ( // Response is the structure of JSON returned from API type Response struct { - Success bool `json:"success"` - ChallengeTS time.Time `json:"challenge_ts"` - Hostname string `json:"hostname"` - ErrorCodes []string `json:"error-codes"` + Success bool `json:"success"` + ChallengeTS string `json:"challenge_ts"` + Hostname string `json:"hostname"` + ErrorCodes []ErrorCode `json:"error-codes"` } const apiURL = "api/siteverify" // Verify calls Google Recaptcha API to verify token -func Verify(response string) (bool, error) { - resp, err := http.PostForm(util.URLJoin(setting.Service.RecaptchaURL, apiURL), - url.Values{"secret": {setting.Service.RecaptchaSecret}, "response": {response}}) +func Verify(ctx context.Context, response string) (bool, error) { + post := url.Values{ + "secret": {setting.Service.RecaptchaSecret}, + "response": {response}, + } + // Basically a copy of http.PostForm, but with a context + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + util.URLJoin(setting.Service.RecaptchaURL, apiURL), strings.NewReader(post.Encode())) + if err != nil { + return false, fmt.Errorf("Failed to create CAPTCHA request: %v", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) if err != nil { return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) } @@ -43,6 +55,36 @@ func Verify(response string) (bool, error) { if err != nil { return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err) } + var respErr error + if len(jsonResponse.ErrorCodes) > 0 { + respErr = jsonResponse.ErrorCodes[0] + } + return jsonResponse.Success, respErr +} + +// ErrorCode is a reCaptcha error +type ErrorCode string + +// String fulfills the Stringer interface +func (e ErrorCode) String() string { + switch e { + case "missing-input-secret": + return "The secret parameter is missing." + case "invalid-input-secret": + return "The secret parameter is invalid or malformed." + case "missing-input-response": + return "The response parameter is missing." + case "invalid-input-response": + return "The response parameter is invalid or malformed." + case "bad-request": + return "The request is invalid or malformed." + case "timeout-or-duplicate": + return "The response is no longer valid: either is too old or has been used previously." + } + return string(e) +} - return jsonResponse.Success, nil +// Error fulfills the error interface +func (e ErrorCode) Error() string { + return e.String() } diff --git a/modules/setting/service.go b/modules/setting/service.go index c463b0a9d5..4d03df17a4 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -35,6 +35,8 @@ var Service struct { RecaptchaSecret string RecaptchaSitekey string RecaptchaURL string + HcaptchaSecret string + HcaptchaSitekey string DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool EnableTimetracking bool @@ -76,6 +78,8 @@ func newService() { Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("") Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("") Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/") + Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("") + Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("") Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index cdbe07f911..dc7697bca7 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -59,6 +59,7 @@ const ( const ( ImageCaptcha = "image" ReCaptcha = "recaptcha" + HCaptcha = "hcaptcha" ) // settings |