From a6bfdc516ed494c275e25fbca2a2c1feefff3cd0 Mon Sep 17 00:00:00 2001 From: ByLCY Date: Sun, 8 Jan 2023 20:38:38 +0800 Subject: [PATCH 01/11] add new captcha: cloudflare turnstile Signed-off-by: ByLCY --- custom/conf/app.example.ini | 7 +- .../doc/advanced/config-cheat-sheet.en-us.md | 5 +- .../doc/advanced/config-cheat-sheet.zh-cn.md | 12 +++ modules/context/captcha.go | 17 +++- modules/setting/service.go | 6 ++ modules/setting/setting.go | 1 + modules/turnstile/turnstile.go | 93 +++++++++++++++++++ templates/base/footer.tmpl | 3 + templates/user/auth/captcha.tmpl | 4 + 9 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 modules/turnstile/turnstile.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index cec5e8cf03821..8a0d0437b1251 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -765,7 +765,7 @@ ROUTER = console ;; Enable this to require captcha validation for login ;REQUIRE_CAPTCHA_FOR_LOGIN = false ;; -;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha. +;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha, cfturnstile. ;CAPTCHA_TYPE = image ;; ;; Change this to use recaptcha.net or other recaptcha service @@ -787,6 +787,11 @@ ROUTER = console ;MCAPTCHA_SECRET = ;MCAPTCHA_SITEKEY = ;; +;; Go to https://dash.cloudflare.com/?to=/:account/turnstile to sign up for a key +;CF_TURNSTILE_SITEKEY = +;CF_TURNSTILE_SECRET = +;CF_REVERSE_PROXY_HEADER = +;; ;; Default value for KeepEmailPrivate ;; Each new user will get the value of this setting copied into their profile ;DEFAULT_KEEP_EMAIL_PRIVATE = false diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 3b2ff4cbbf1f0..16e5be3190b80 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -644,7 +644,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o - `REQUIRE_CAPTCHA_FOR_LOGIN`: **false**: Enable this to require captcha validation for login. You also must enable `ENABLE_CAPTCHA`. - `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation even for External Accounts (i.e. GitHub, OpenID Connect, etc). You also must enable `ENABLE_CAPTCHA`. -- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha\] +- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha, cfturnstile\] - `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha. - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha. - `RECAPTCHA_URL`: **https://www.google.com/recaptcha/**: Set the recaptcha url - allows the use of recaptcha net. @@ -653,6 +653,9 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o - `MCAPTCHA_SECRET`: **""**: Go to your mCaptcha instance to get a secret for mCaptcha. - `MCAPTCHA_SITEKEY`: **""**: Go to your mCaptcha instance to get a sitekey for mCaptcha. - `MCAPTCHA_URL` **https://demo.mcaptcha.org/**: Set the mCaptcha URL. +- `CF_TURNSTILE_SECRET` **""**: Go to https://dash.cloudflare.com/?to=/:account/turnstile to get a secret for cloudflare turnstile. +- `CF_TURNSTILE_SITEKEY` **""**: Go to https://dash.cloudflare.com/?to=/:account/turnstile to get a sitekey for cloudflare turnstile. +- `CF_REVERSE_PROXY_HEADER` **""**: The http header where the user's real ip is located. Otherwise it should be `""`. - `DEFAULT_KEEP_EMAIL_PRIVATE`: **false**: By default set users to keep their email address private. - `DEFAULT_ALLOW_CREATE_ORGANIZATION`: **true**: Allow new users to create organizations by default. - `DEFAULT_USER_IS_RESTRICTED`: **false**: Give new users restricted permissions by default diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index f10b6258c87a2..fd0828c24e79f 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -147,6 +147,18 @@ menu: - `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: 允许通过反向认证做自动注册。 - `ENABLE_CAPTCHA`: **false**: 注册时使用图片验证码。 - `REQUIRE_CAPTCHA_FOR_LOGIN`: **false**: 登录时需要图片验证码。需要同时开启 `ENABLE_CAPTCHA`。 +- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha, cfturnstile\],人机验证类型,分别表示图片认证、 recaptcha 、 hcaptcha 、mcaptcha 、和 cloudlfare 的 turnstile。 +- `RECAPTCHA_SECRET`: **""**: recaptcha 服务的密钥,可在 https://www.google.com/recaptcha/admin 获取。 +- `RECAPTCHA_SITEKEY`: **""**: recaptcha 服务的网站密钥 ,可在 https://www.google.com/recaptcha/admin 获取。 +- `RECAPTCHA_URL`: **https://www.google.com/recaptcha/**: 设置 recaptcha 的 url 。 +- `HCAPTCHA_SECRET`: **""**: hcaptcha 服务的密钥,可在 https://www.hcaptcha.com/ 获取。 +- `HCAPTCHA_SITEKEY`: **""**: hcaptcha 服务的网站密钥,可在 https://www.hcaptcha.com/ 获取。 +- `MCAPTCHA_SECRET`: **""**: mCaptcha 服务的密钥。 +- `MCAPTCHA_SITEKEY`: **""**: mCaptcha 服务的网站密钥。 +- `MCAPTCHA_URL` **https://demo.mcaptcha.org/**: 设置 remCaptchacaptcha 的 url 。 +- `CF_TURNSTILE_SECRET` **""**: cloudlfare turnstile 服务的密钥,可在 https://dash.cloudflare.com/?to=/:account/turnstile 获取。 +- `CF_TURNSTILE_SITEKEY` **""**: cloudlfare turnstile 服务的网站密钥 ,可在 https://www.google.com/recaptcha/admin 获取。 +- `CF_REVERSE_PROXY_HEADER` **""**: http 的 header 字段,用于获取客户端的 ip 供 cloudflare turnstile 验证时使用。如果没有反向代理设置这里应设置为 `""` 。 ### Service - Expore (`service.explore`) diff --git a/modules/context/captcha.go b/modules/context/captcha.go index 735613504caee..dd4b246a7a382 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,14 @@ 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: + var ip string + if setting.Service.CfReverseProxyHeader == "" { + ip = ctx.RemoteAddr() + } else { + ip = ctx.Req.Header.Get(setting.Service.CfReverseProxyHeader) + } + valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField), ip) 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 7b4bfc5c7b6b2..0992a0c629cdd 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -46,6 +46,9 @@ var Service = struct { RecaptchaSecret string RecaptchaSitekey string RecaptchaURL string + CfTurnstileSecret string + CfTurnstileSitekey string + CfReverseProxyHeader string HcaptchaSecret string HcaptchaSitekey string McaptchaSecret string @@ -137,6 +140,9 @@ 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.CfReverseProxyHeader = sec.Key("CF_REVERSE_PROXY_HEADER").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 07290fbfeb9f3..5f6efa52e01a2 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -60,6 +60,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 0000000000000..8b49c01ddd364 --- /dev/null +++ b/modules/turnstile/turnstile.go @@ -0,0 +1,93 @@ +// 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, ip 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}, + "remoteip": {ip}, + } + // 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: %s", err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err) + } + + var jsonResponse Response + err = json.Unmarshal(body, &jsonResponse) + 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 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() +} diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 3fa0f8e7aca8f..5f3ef74bdcb4f 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -21,6 +21,9 @@ {{if eq .CaptchaType "hcaptcha"}} {{end}} + {{if eq .CaptchaType "cfturnstile"}} + + {{end}} {{end}} {{template "custom/footer" .}} diff --git a/templates/user/auth/captcha.tmpl b/templates/user/auth/captcha.tmpl index 87b22a0720eda..d96203624eb17 100644 --- a/templates/user/auth/captcha.tmpl +++ b/templates/user/auth/captcha.tmpl @@ -21,4 +21,8 @@
+{{else if eq .CaptchaType "cfturnstile"}} +
+
+
{{end}}{{end}} From 8c634337822b44709610e4045cf2c78d4e3238da Mon Sep 17 00:00:00 2001 From: ByLCY Date: Wed, 18 Jan 2023 09:42:31 +0800 Subject: [PATCH 02/11] Change from inline css to using css class Signed-off-by: ByLCY --- templates/user/auth/captcha.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/auth/captcha.tmpl b/templates/user/auth/captcha.tmpl index d96203624eb17..2e915fdb01806 100644 --- a/templates/user/auth/captcha.tmpl +++ b/templates/user/auth/captcha.tmpl @@ -22,7 +22,7 @@
{{else if eq .CaptchaType "cfturnstile"}} -
+
{{end}}{{end}} From 59186ab2eb9124b3c2b2a55bcf90fdb0dfcbe273 Mon Sep 17 00:00:00 2001 From: ByLCY Date: Thu, 19 Jan 2023 16:53:59 +0800 Subject: [PATCH 03/11] Added dark mode for hcaptcha, recaptcha and cloudflare turnstile Signed-off-by: ByLCY --- templates/base/footer.tmpl | 6 ++--- templates/user/auth/captcha.tmpl | 8 +++--- web_src/js/features/captcha.js | 42 ++++++++++++++++++++++++++++++++ web_src/js/index.js | 2 ++ web_src/less/_form.less | 14 ++++++++--- 5 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 web_src/js/features/captcha.js diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 5f3ef74bdcb4f..abcfc95630f51 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -16,13 +16,13 @@ {{if .EnableCaptcha}} {{if eq .CaptchaType "recaptcha"}} - + {{end}} {{if eq .CaptchaType "hcaptcha"}} - + {{end}} {{if eq .CaptchaType "cfturnstile"}} - + {{end}} {{end}} diff --git a/templates/user/auth/captcha.tmpl b/templates/user/auth/captcha.tmpl index 2e915fdb01806..4cf28b414f911 100644 --- a/templates/user/auth/captcha.tmpl +++ b/templates/user/auth/captcha.tmpl @@ -9,20 +9,20 @@
{{else if eq .CaptchaType "recaptcha"}}
-
+
{{else if eq .CaptchaType "hcaptcha"}}
-
+
{{else if eq .CaptchaType "mcaptcha"}}
{{.locale.Tr "captcha"}}
-
+
{{else if eq .CaptchaType "cfturnstile"}}
-
+
{{end}}{{end}} diff --git a/web_src/js/features/captcha.js b/web_src/js/features/captcha.js new file mode 100644 index 0000000000000..aa19ddc737162 --- /dev/null +++ b/web_src/js/features/captcha.js @@ -0,0 +1,42 @@ +import {isDarkTheme} from '../utils.js'; + +export function initCaptcha() { + const captchaEl = document.querySelector('#captcha'); + if (!captchaEl) return; + + const siteKey = captchaEl.getAttribute('data-sitekey'); + const isDark = isDarkTheme(); + + const params = { + sitekey: siteKey, + theme: isDark ? 'dark' : 'light' + }; + + switch (captchaEl.getAttribute('captcha-type')) { + case 'g-recaptcha': { + // eslint-disable-next-line no-undef + if (grecaptcha) { + // eslint-disable-next-line no-undef + grecaptcha.ready(() => { + // eslint-disable-next-line no-undef + grecaptcha.render(captchaEl, params); + }); + } + break; + } + case 'cf-turnstile': { + // eslint-disable-next-line no-undef + if (turnstile) { + // eslint-disable-next-line no-undef + turnstile.render(captchaEl, params); + } + break; + } + case 'h-captcha': { + // eslint-disable-next-line no-undef + hcaptcha.render(captchaEl, params); + break; + } + default: + } +} diff --git a/web_src/js/index.js b/web_src/js/index.js index a866184203fd6..199d77945239d 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -90,6 +90,7 @@ import {initRepoCommentForm, initRepository} from './features/repo-legacy.js'; import {initFormattingReplacements} from './features/formatting.js'; import {initMcaptcha} from './features/mcaptcha.js'; import {initCopyContent} from './features/copycontent.js'; +import {initCaptcha} from './features/captcha.js'; // Run time-critical code as soon as possible. This is safe to do because this // script appears at the end of and rendered HTML is accessible at that point. @@ -190,6 +191,7 @@ $(document).ready(() => { initCommitStatuses(); initMcaptcha(); + initCaptcha(); initUserAuthLinkAccountView(); initUserAuthOauth2(); diff --git a/web_src/less/_form.less b/web_src/less/_form.less index 3d2ec9fb8a9ee..1a1c1678f8b3a 100644 --- a/web_src/less/_form.less +++ b/web_src/less/_form.less @@ -220,18 +220,24 @@ textarea:focus, } @media @mediaMdAndUp { - .g-recaptcha, - .h-captcha { + .g-recaptcha-style, + .h-captcha-style { margin: 0 auto !important; width: 304px; padding-left: 30px; + + iframe { + border-radius: 5px !important; + width: 302px !important; + height: 76px !important; + } } } @media (max-height: 575px) { #rc-imageselect, - .g-recaptcha, - .h-captcha { + .g-recaptcha-style, + .h-captcha-style { transform: scale(.77); transform-origin: 0 0; } From 0821c09d4be89eb5e702e191c37028fbec15378d Mon Sep 17 00:00:00 2001 From: ByLCY Date: Thu, 19 Jan 2023 18:42:24 +0800 Subject: [PATCH 04/11] Use ctx.RemoteAddr() to get the real ip instead of getting it from the http header Signed-off-by: ByLCY --- custom/conf/app.example.ini | 1 - docs/content/doc/advanced/config-cheat-sheet.en-us.md | 1 - docs/content/doc/advanced/config-cheat-sheet.zh-cn.md | 1 - modules/context/captcha.go | 8 ++++---- modules/setting/service.go | 2 -- modules/turnstile/turnstile.go | 4 ++-- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 8a0d0437b1251..d915588e5571f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -790,7 +790,6 @@ ROUTER = console ;; Go to https://dash.cloudflare.com/?to=/:account/turnstile to sign up for a key ;CF_TURNSTILE_SITEKEY = ;CF_TURNSTILE_SECRET = -;CF_REVERSE_PROXY_HEADER = ;; ;; Default value for KeepEmailPrivate ;; Each new user will get the value of this setting copied into their profile diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 16e5be3190b80..4ef10f1a2fbb9 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -655,7 +655,6 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o - `MCAPTCHA_URL` **https://demo.mcaptcha.org/**: Set the mCaptcha URL. - `CF_TURNSTILE_SECRET` **""**: Go to https://dash.cloudflare.com/?to=/:account/turnstile to get a secret for cloudflare turnstile. - `CF_TURNSTILE_SITEKEY` **""**: Go to https://dash.cloudflare.com/?to=/:account/turnstile to get a sitekey for cloudflare turnstile. -- `CF_REVERSE_PROXY_HEADER` **""**: The http header where the user's real ip is located. Otherwise it should be `""`. - `DEFAULT_KEEP_EMAIL_PRIVATE`: **false**: By default set users to keep their email address private. - `DEFAULT_ALLOW_CREATE_ORGANIZATION`: **true**: Allow new users to create organizations by default. - `DEFAULT_USER_IS_RESTRICTED`: **false**: Give new users restricted permissions by default diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index fd0828c24e79f..2598f16a14963 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -158,7 +158,6 @@ menu: - `MCAPTCHA_URL` **https://demo.mcaptcha.org/**: 设置 remCaptchacaptcha 的 url 。 - `CF_TURNSTILE_SECRET` **""**: cloudlfare turnstile 服务的密钥,可在 https://dash.cloudflare.com/?to=/:account/turnstile 获取。 - `CF_TURNSTILE_SITEKEY` **""**: cloudlfare turnstile 服务的网站密钥 ,可在 https://www.google.com/recaptcha/admin 获取。 -- `CF_REVERSE_PROXY_HEADER` **""**: http 的 header 字段,用于获取客户端的 ip 供 cloudflare turnstile 验证时使用。如果没有反向代理设置这里应设置为 `""` 。 ### Service - Expore (`service.explore`) diff --git a/modules/context/captcha.go b/modules/context/captcha.go index dd4b246a7a382..b6c0ebddabdcc 100644 --- a/modules/context/captcha.go +++ b/modules/context/captcha.go @@ -5,6 +5,7 @@ package context import ( "fmt" + "net" "sync" "code.gitea.io/gitea/modules/base" @@ -78,10 +79,9 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) { valid, err = mcaptcha.Verify(ctx, ctx.Req.Form.Get(mCaptchaResponseField)) case setting.CfTurnstile: var ip string - if setting.Service.CfReverseProxyHeader == "" { - ip = ctx.RemoteAddr() - } else { - ip = ctx.Req.Header.Get(setting.Service.CfReverseProxyHeader) + ip, _, err = net.SplitHostPort(ctx.RemoteAddr()) + if err != nil { + break } valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField), ip) default: diff --git a/modules/setting/service.go b/modules/setting/service.go index 0992a0c629cdd..1d33ac6bce88f 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -48,7 +48,6 @@ var Service = struct { RecaptchaURL string CfTurnstileSecret string CfTurnstileSitekey string - CfReverseProxyHeader string HcaptchaSecret string HcaptchaSitekey string McaptchaSecret string @@ -142,7 +141,6 @@ func newService() { 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.CfReverseProxyHeader = sec.Key("CF_REVERSE_PROXY_HEADER").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/turnstile/turnstile.go b/modules/turnstile/turnstile.go index 8b49c01ddd364..1f9d1963e45ac 100644 --- a/modules/turnstile/turnstile.go +++ b/modules/turnstile/turnstile.go @@ -52,10 +52,10 @@ func Verify(ctx context.Context, response, ip string) (bool, error) { } var jsonResponse Response - err = json.Unmarshal(body, &jsonResponse) - if err != nil { + if err := json.Unmarshal(body, &jsonResponse); 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] From c5d9c49af8466dd6d737660bc8cd7212e08e99f3 Mon Sep 17 00:00:00 2001 From: ByLCY Date: Thu, 26 Jan 2023 16:51:46 +0800 Subject: [PATCH 05/11] fix linter errors Get global variables of various captchas from `window` Signed-off-by: ByLCY --- web_src/js/features/captcha.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/web_src/js/features/captcha.js b/web_src/js/features/captcha.js index aa19ddc737162..ff7d598007953 100644 --- a/web_src/js/features/captcha.js +++ b/web_src/js/features/captcha.js @@ -14,27 +14,23 @@ export function initCaptcha() { switch (captchaEl.getAttribute('captcha-type')) { case 'g-recaptcha': { - // eslint-disable-next-line no-undef - if (grecaptcha) { - // eslint-disable-next-line no-undef - grecaptcha.ready(() => { - // eslint-disable-next-line no-undef - grecaptcha.render(captchaEl, params); + if (window.grecaptcha) { + window.grecaptcha.ready(() => { + window.grecaptcha.render(captchaEl, params); }); } break; } case 'cf-turnstile': { - // eslint-disable-next-line no-undef - if (turnstile) { - // eslint-disable-next-line no-undef - turnstile.render(captchaEl, params); + if (window.turnstile) { + window.turnstile.render(captchaEl, params); } break; } case 'h-captcha': { - // eslint-disable-next-line no-undef - hcaptcha.render(captchaEl, params); + if (window.hcaptcha) { + window.hcaptcha.render(captchaEl, params); + } break; } default: From 4ba29b3a28011dd635a61ee78e029d0d29cf1756 Mon Sep 17 00:00:00 2001 From: ByLCY Date: Thu, 26 Jan 2023 17:44:06 +0800 Subject: [PATCH 06/11] Move code from mcaptcha.js to captcha.js Signed-off-by: ByLCY --- templates/user/auth/captcha.tmpl | 2 +- web_src/js/features/captcha.js | 15 ++++++++++++++- web_src/js/features/mcaptcha.js | 16 ---------------- web_src/js/index.js | 2 -- 4 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 web_src/js/features/mcaptcha.js diff --git a/templates/user/auth/captcha.tmpl b/templates/user/auth/captcha.tmpl index 4cf28b414f911..da909cf258f49 100644 --- a/templates/user/auth/captcha.tmpl +++ b/templates/user/auth/captcha.tmpl @@ -19,7 +19,7 @@
{{.locale.Tr "captcha"}}
-
+
{{else if eq .CaptchaType "cfturnstile"}}
diff --git a/web_src/js/features/captcha.js b/web_src/js/features/captcha.js index ff7d598007953..29da93f3e2085 100644 --- a/web_src/js/features/captcha.js +++ b/web_src/js/features/captcha.js @@ -1,6 +1,6 @@ import {isDarkTheme} from '../utils.js'; -export function initCaptcha() { +export async function initCaptcha() { const captchaEl = document.querySelector('#captcha'); if (!captchaEl) return; @@ -33,6 +33,19 @@ export function initCaptcha() { } break; } + case 'm-captcha': { + const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue'); + mCaptcha.INPUT_NAME = 'm-captcha-response'; + const instanceURL = captchaEl.getAttribute('data-instance-url'); + + mCaptcha.default({ + siteKey: { + instanceUrl: new URL(instanceURL), + key: siteKey, + } + }); + break; + } default: } } diff --git a/web_src/js/features/mcaptcha.js b/web_src/js/features/mcaptcha.js deleted file mode 100644 index 725e2e28acf11..0000000000000 --- a/web_src/js/features/mcaptcha.js +++ /dev/null @@ -1,16 +0,0 @@ -export async function initMcaptcha() { - const mCaptchaEl = document.querySelector('.m-captcha'); - if (!mCaptchaEl) return; - - const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue'); - mCaptcha.INPUT_NAME = 'm-captcha-response'; - const siteKey = mCaptchaEl.getAttribute('data-sitekey'); - const instanceURL = mCaptchaEl.getAttribute('data-instance-url'); - - mCaptcha.default({ - siteKey: { - instanceUrl: new URL(instanceURL), - key: siteKey, - } - }); -} diff --git a/web_src/js/index.js b/web_src/js/index.js index 199d77945239d..2d96e792d2756 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -88,7 +88,6 @@ import {initCommonOrganization} from './features/common-organization.js'; import {initRepoWikiForm} from './features/repo-wiki.js'; import {initRepoCommentForm, initRepository} from './features/repo-legacy.js'; import {initFormattingReplacements} from './features/formatting.js'; -import {initMcaptcha} from './features/mcaptcha.js'; import {initCopyContent} from './features/copycontent.js'; import {initCaptcha} from './features/captcha.js'; @@ -190,7 +189,6 @@ $(document).ready(() => { initRepository(); initCommitStatuses(); - initMcaptcha(); initCaptcha(); initUserAuthLinkAccountView(); From a152ec7c8d746683d18a9a9bed8fc13938a34c7e Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sat, 4 Feb 2023 23:27:23 +0800 Subject: [PATCH 07/11] Update modules/turnstile/turnstile.go --- modules/turnstile/turnstile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/turnstile/turnstile.go b/modules/turnstile/turnstile.go index 1f9d1963e45ac..f19169ba0e45d 100644 --- a/modules/turnstile/turnstile.go +++ b/modules/turnstile/turnstile.go @@ -43,7 +43,7 @@ func Verify(ctx context.Context, response, ip string) (bool, error) { resp, err := http.DefaultClient.Do(req) if err != nil { - return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) + return false, fmt.Errorf("Failed to send CAPTCHA response: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) From 09ff01526beedd36bdf33e5062574198482fc507 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sat, 4 Feb 2023 23:27:32 +0800 Subject: [PATCH 08/11] Update modules/turnstile/turnstile.go --- modules/turnstile/turnstile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/turnstile/turnstile.go b/modules/turnstile/turnstile.go index f19169ba0e45d..36ae278522421 100644 --- a/modules/turnstile/turnstile.go +++ b/modules/turnstile/turnstile.go @@ -48,7 +48,7 @@ func Verify(ctx context.Context, response, ip string) (bool, error) { defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err) + return false, fmt.Errorf("Failed to read CAPTCHA response: %w", err) } var jsonResponse Response From 0d185a78ca15ab8b9d87c6f06fb7055ed078e685 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sat, 4 Feb 2023 23:27:40 +0800 Subject: [PATCH 09/11] Update modules/turnstile/turnstile.go --- modules/turnstile/turnstile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/turnstile/turnstile.go b/modules/turnstile/turnstile.go index 36ae278522421..361f897d2894f 100644 --- a/modules/turnstile/turnstile.go +++ b/modules/turnstile/turnstile.go @@ -53,7 +53,7 @@ func Verify(ctx context.Context, response, ip string) (bool, error) { var jsonResponse Response if err := json.Unmarshal(body, &jsonResponse); err != nil { - return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err) + return false, fmt.Errorf("Failed to parse CAPTCHA response: %w", err) } var respErr error From 4b3b828c8d308a558b0fe5cfb35c8276c9e13214 Mon Sep 17 00:00:00 2001 From: ByLCY Date: Sun, 5 Feb 2023 12:48:13 +0800 Subject: [PATCH 10/11] Modify custom attribute Custom attribute changed from `captcha-type` to `data-captcha-type` --- templates/user/auth/captcha.tmpl | 8 ++++---- web_src/js/features/captcha.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/user/auth/captcha.tmpl b/templates/user/auth/captcha.tmpl index da909cf258f49..a794c8f543ea8 100644 --- a/templates/user/auth/captcha.tmpl +++ b/templates/user/auth/captcha.tmpl @@ -9,20 +9,20 @@
{{else if eq .CaptchaType "recaptcha"}}
-
+
{{else if eq .CaptchaType "hcaptcha"}}
-
+
{{else if eq .CaptchaType "mcaptcha"}}
{{.locale.Tr "captcha"}}
-
+
{{else if eq .CaptchaType "cfturnstile"}}
-
+
{{end}}{{end}} diff --git a/web_src/js/features/captcha.js b/web_src/js/features/captcha.js index 29da93f3e2085..3da5dbda41854 100644 --- a/web_src/js/features/captcha.js +++ b/web_src/js/features/captcha.js @@ -12,7 +12,7 @@ export async function initCaptcha() { theme: isDark ? 'dark' : 'light' }; - switch (captchaEl.getAttribute('captcha-type')) { + switch (captchaEl.getAttribute('data-captcha-type')) { case 'g-recaptcha': { if (window.grecaptcha) { window.grecaptcha.ready(() => { From 46aa739879cd5a199a3d74be43b1a66c6e954a05 Mon Sep 17 00:00:00 2001 From: ByLCY Date: Sun, 5 Feb 2023 13:11:56 +0800 Subject: [PATCH 11/11] Remove the remoteip parameter for Turnstile --- modules/context/captcha.go | 8 +------- modules/turnstile/turnstile.go | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/modules/context/captcha.go b/modules/context/captcha.go index b6c0ebddabdcc..07232e9390663 100644 --- a/modules/context/captcha.go +++ b/modules/context/captcha.go @@ -5,7 +5,6 @@ package context import ( "fmt" - "net" "sync" "code.gitea.io/gitea/modules/base" @@ -78,12 +77,7 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) { case setting.MCaptcha: valid, err = mcaptcha.Verify(ctx, ctx.Req.Form.Get(mCaptchaResponseField)) case setting.CfTurnstile: - var ip string - ip, _, err = net.SplitHostPort(ctx.RemoteAddr()) - if err != nil { - break - } - valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField), ip) + 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/turnstile/turnstile.go b/modules/turnstile/turnstile.go index 361f897d2894f..38d0233446608 100644 --- a/modules/turnstile/turnstile.go +++ b/modules/turnstile/turnstile.go @@ -26,12 +26,11 @@ type Response struct { } // Verify calls Cloudflare Turnstile API to verify token -func Verify(ctx context.Context, response, ip string) (bool, error) { +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}, - "remoteip": {ip}, } // Basically a copy of http.PostForm, but with a context req, err := http.NewRequestWithContext(ctx, http.MethodPost,