From 5bb7a4153f92f3df4ca416c86b8b4e3229f5af11 Mon Sep 17 00:00:00 2001 From: YiNN <86649490+YiNNx@users.noreply.github.com> Date: Sat, 26 Nov 2022 17:17:49 +0800 Subject: [PATCH] feat: add cloudflare turnstile captcha (#1327) * feat: add cloudflare turnstile captcha * fix: rename turnstile to cloudflare turnstile --- captcha/provider.go | 2 + captcha/turnstile.go | 66 +++++++++++++++++++++++++++++++++ web/src/Setting.js | 5 +++ web/src/common/CaptchaWidget.js | 15 ++++++++ 4 files changed, 88 insertions(+) create mode 100644 captcha/turnstile.go diff --git a/captcha/provider.go b/captcha/provider.go index 6968d3c8..b9635e87 100644 --- a/captcha/provider.go +++ b/captcha/provider.go @@ -31,6 +31,8 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider { return NewAliyunCaptchaProvider() } else if captchaType == "GEETEST" { return NewGEETESTCaptchaProvider() + } else if captchaType == "Cloudflare Turnstile" { + return NewCloudflareTurnstileProvider() } return nil } diff --git a/captcha/turnstile.go b/captcha/turnstile.go new file mode 100644 index 00000000..f143ea00 --- /dev/null +++ b/captcha/turnstile.go @@ -0,0 +1,66 @@ +// Copyright 2022 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package captcha + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "net/url" + "strings" +) + +const CloudflareTurnstileVerifyUrl = "https://challenges.cloudflare.com/turnstile/v0/siteverify" + +type CloudflareTurnstileProvider struct{} + +func NewCloudflareTurnstileProvider() *CloudflareTurnstileProvider { + captcha := &CloudflareTurnstileProvider{} + return captcha +} + +func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientSecret string) (bool, error) { + reqData := url.Values{ + "secret": {clientSecret}, + "response": {token}, + } + resp, err := http.PostForm(CloudflareTurnstileVerifyUrl, reqData) + if err != nil { + return false, err + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, err + } + + type captchaResponse struct { + Success bool `json:"success"` + ErrorCodes []string `json:"error-codes"` + } + captchaResp := &captchaResponse{} + err = json.Unmarshal(body, captchaResp) + if err != nil { + return false, err + } + + if len(captchaResp.ErrorCodes) > 0 { + return false, errors.New(strings.Join(captchaResp.ErrorCodes, ",")) + } + + return captchaResp.Success, nil +} diff --git a/web/src/Setting.js b/web/src/Setting.js index c1700b6d..32442656 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -145,6 +145,10 @@ export const OtherProviderInfo = { logo: `${StaticBaseUrl}/img/social_geetest.png`, url: "https://www.geetest.com", }, + "Cloudflare Turnstile": { + logo: `${StaticBaseUrl}/img/social_cloudflare.png`, + url: "https://www.cloudflare.com/products/turnstile/", + }, }, }; @@ -695,6 +699,7 @@ export function getProviderTypeOptions(category) { {id: "hCaptcha", name: "hCaptcha"}, {id: "Aliyun Captcha", name: "Aliyun Captcha"}, {id: "GEETEST", name: "GEETEST"}, + {id: "Cloudflare Turnstile", name: "Cloudflare Turnstile"}, ]); } else { return []; diff --git a/web/src/common/CaptchaWidget.js b/web/src/common/CaptchaWidget.js index 3f033806..4299c36e 100644 --- a/web/src/common/CaptchaWidget.js +++ b/web/src/common/CaptchaWidget.js @@ -105,6 +105,21 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh }, 500); break; } + case "Cloudflare Turnstile": { + const tTimer = setInterval(() => { + if (!window.turnstile) { + loadScript("https://challenges.cloudflare.com/turnstile/v0/api.js"); + } + if (window.turnstile && window.turnstile.render) { + window.turnstile.render("#captcha", { + sitekey: siteKey, + callback: onChange, + }); + clearInterval(tTimer); + } + }, 300); + break; + } default: break; }