diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/context/captcha.go | 11 | ||||
-rw-r--r-- | modules/setting/service.go | 4 | ||||
-rw-r--r-- | modules/setting/setting.go | 1 | ||||
-rw-r--r-- | modules/turnstile/turnstile.go | 92 |
4 files changed, 105 insertions, 3 deletions
diff --git a/modules/context/captcha.go b/modules/context/captcha.go index 735613504c..07232e9390 100644 --- a/modules/context/captcha.go +++ b/modules/context/captcha.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/mcaptcha" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/turnstile" "gitea.com/go-chi/captcha" ) @@ -47,12 +48,14 @@ func SetCaptchaData(ctx *Context) { ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL + ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey } const ( - gRecaptchaResponseField = "g-recaptcha-response" - hCaptchaResponseField = "h-captcha-response" - mCaptchaResponseField = "m-captcha-response" + gRecaptchaResponseField = "g-recaptcha-response" + hCaptchaResponseField = "h-captcha-response" + mCaptchaResponseField = "m-captcha-response" + cfTurnstileResponseField = "cf-turnstile-response" ) // VerifyCaptcha verifies Captcha data @@ -73,6 +76,8 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) { valid, err = hcaptcha.Verify(ctx, ctx.Req.Form.Get(hCaptchaResponseField)) case setting.MCaptcha: valid, err = mcaptcha.Verify(ctx, ctx.Req.Form.Get(mCaptchaResponseField)) + case setting.CfTurnstile: + valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField)) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return diff --git a/modules/setting/service.go b/modules/setting/service.go index 7b4bfc5c7b..1d33ac6bce 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -46,6 +46,8 @@ var Service = struct { RecaptchaSecret string RecaptchaSitekey string RecaptchaURL string + CfTurnstileSecret string + CfTurnstileSitekey string HcaptchaSecret string HcaptchaSitekey string McaptchaSecret string @@ -137,6 +139,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.CfTurnstileSecret = sec.Key("CF_TURNSTILE_SECRET").MustString("") + Service.CfTurnstileSitekey = sec.Key("CF_TURNSTILE_SITEKEY").MustString("") Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("") Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("") Service.McaptchaURL = sec.Key("MCAPTCHA_URL").MustString("https://demo.mcaptcha.org/") diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 23cd90553e..a68a46f7ad 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -61,6 +61,7 @@ const ( ReCaptcha = "recaptcha" HCaptcha = "hcaptcha" MCaptcha = "mcaptcha" + CfTurnstile = "cfturnstile" ) // settings diff --git a/modules/turnstile/turnstile.go b/modules/turnstile/turnstile.go new file mode 100644 index 0000000000..38d0233446 --- /dev/null +++ b/modules/turnstile/turnstile.go @@ -0,0 +1,92 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package turnstile + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" +) + +// Response is the structure of JSON returned from API +type Response struct { + Success bool `json:"success"` + ChallengeTS string `json:"challenge_ts"` + Hostname string `json:"hostname"` + ErrorCodes []ErrorCode `json:"error-codes"` + Action string `json:"login"` + Cdata string `json:"cdata"` +} + +// Verify calls Cloudflare Turnstile API to verify token +func Verify(ctx context.Context, response string) (bool, error) { + // Cloudflare turnstile official access instruction address: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ + post := url.Values{ + "secret": {setting.Service.CfTurnstileSecret}, + "response": {response}, + } + // Basically a copy of http.PostForm, but with a context + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + "https://challenges.cloudflare.com/turnstile/v0/siteverify", strings.NewReader(post.Encode())) + if err != nil { + return false, fmt.Errorf("Failed to create CAPTCHA request: %w", 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: %w", err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, fmt.Errorf("Failed to read CAPTCHA response: %w", err) + } + + var jsonResponse Response + if err := json.Unmarshal(body, &jsonResponse); err != nil { + return false, fmt.Errorf("Failed to parse CAPTCHA response: %w", 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 was not passed." + case "invalid-input-secret": + return "The secret parameter was invalid or did not exist." + case "missing-input-response": + return "The response parameter was not passed." + case "invalid-input-response": + return "The response parameter is invalid or has expired." + case "bad-request": + return "The request was rejected because it was malformed." + case "timeout-or-duplicate": + return "The response parameter has already been validated before." + case "internal-error": + return "An internal error happened while validating the response. The request can be retried." + } + return string(e) +} + +// Error fulfills the error interface +func (e ErrorCode) Error() string { + return e.String() +} |