diff --git a/captcha/geetest.go b/captcha/geetest.go new file mode 100644 index 00000000..d1c4de05 --- /dev/null +++ b/captcha/geetest.go @@ -0,0 +1,82 @@ +// 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" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + "github.com/casdoor/casdoor/util" +) + +const GEETESTCaptchaVerifyUrl = "http://gcaptcha4.geetest.com/validate" + +type GEETESTCaptchaProvider struct { +} + +func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider { + captcha := &GEETESTCaptchaProvider{} + return captcha +} + +func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) { + pathData, err := url.ParseQuery(token) + if err != nil { + return false, err + } + + signToken := util.GetHmacSha256(clientSecret, pathData["lot_number"][0]) + + formData := make(url.Values) + formData["lot_number"] = []string{pathData["lot_number"][0]} + formData["captcha_output"] = []string{pathData["captcha_output"][0]} + formData["pass_token"] = []string{pathData["pass_token"][0]} + formData["gen_time"] = []string{pathData["gen_time"][0]} + formData["sign_token"] = []string{signToken} + captchaId := pathData["captcha_id"][0] + + cli := http.Client{Timeout: time.Second * 5} + resp, err := cli.PostForm(fmt.Sprintf("%s?captcha_id=%s", GEETESTCaptchaVerifyUrl, captchaId), formData) + if err != nil || resp.StatusCode != 200 { + return false, err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, err + } + + type captchaResponse struct { + Result string `json:"result"` + Reason string `json:"reason"` + } + captchaResp := &captchaResponse{} + err = json.Unmarshal(body, captchaResp) + if err != nil { + return false, err + } + + if captchaResp.Result == "success" { + return true, nil + } + + return false, errors.New(captchaResp.Reason) +} diff --git a/captcha/provider.go b/captcha/provider.go index 01a54e20..88aaee42 100644 --- a/captcha/provider.go +++ b/captcha/provider.go @@ -27,6 +27,8 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider { return NewHCaptchaProvider() } else if captchaType == "Aliyun Captcha" { return NewAliyunCaptchaProvider() + } else if captchaType == "GEETEST" { + return NewGEETESTCaptchaProvider() } return nil } diff --git a/util/crypto.go b/util/crypto.go index 658adb93..9e181380 100644 --- a/util/crypto.go +++ b/util/crypto.go @@ -17,7 +17,9 @@ package util import ( "crypto/hmac" "crypto/sha1" + "crypto/sha256" "encoding/base64" + "encoding/hex" ) func GetHmacSha1(keyStr, value string) string { @@ -28,3 +30,10 @@ func GetHmacSha1(keyStr, value string) string { return res } + +func GetHmacSha256(key string, data string) string { + mac := hmac.New(sha256.New, []byte(key)) + mac.Write([]byte(data)) + + return hex.EncodeToString(mac.Sum(nil)) +} diff --git a/web/src/Setting.js b/web/src/Setting.js index 41068d13..aea70cf3 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -122,6 +122,10 @@ export const OtherProviderInfo = { "Aliyun Captcha": { logo: `${StaticBaseUrl}/img/social_aliyun.png`, url: "https://help.aliyun.com/product/28308.html", + }, + "GEETEST": { + logo: `${StaticBaseUrl}/img/social_geetest.png`, + url: "https://www.geetest.com", } } }; @@ -615,6 +619,7 @@ export function getProviderTypeOptions(category) { {id: "reCAPTCHA", name: "reCAPTCHA"}, {id: "hCaptcha", name: "hCaptcha"}, {id: "Aliyun Captcha", name: "Aliyun Captcha"}, + {id: "GEETEST", name: "GEETEST"}, ]); } else { return []; diff --git a/web/src/common/CaptchaWidget.js b/web/src/common/CaptchaWidget.js index 86617b3b..7da87eab 100644 --- a/web/src/common/CaptchaWidget.js +++ b/web/src/common/CaptchaWidget.js @@ -25,7 +25,7 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh useEffect(() => { switch (captchaType) { - case "reCAPTCHA": + case "reCAPTCHA": { const reTimer = setInterval(() => { if (!window.grecaptcha) { loadScript("https://recaptcha.net/recaptcha/api.js"); @@ -39,7 +39,8 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh } }, 300); break; - case "hCaptcha": + } + case "hCaptcha": { const hTimer = setInterval(() => { if (!window.hcaptcha) { loadScript("https://js.hcaptcha.com/1/api.js"); @@ -53,7 +54,8 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh } }, 300); break; - case "Aliyun Captcha": + } + case "Aliyun Captcha": { const AWSCTimer = setInterval(() => { if (!window.AWSC) { loadScript("https://g.alicdn.com/AWSC/AWSC/awsc.js"); @@ -76,6 +78,33 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh } }, 300); break; + } + case "GEETEST": { + let getLock = false; + const gTimer = setInterval(() => { + if (!window.initGeetest4) { + loadScript("https://static.geetest.com/v4/gt4.js"); + } + if (window.initGeetest4 && siteKey && !getLock) { + const captchaId = String(siteKey); + window.initGeetest4({ + captchaId, + product: "float", + }, function(captchaObj) { + if (!getLock) { + captchaObj.appendTo("#captcha"); + getLock = true; + } + captchaObj.onSuccess(function() { + const result = captchaObj.getValidate(); + onChange(`lot_number=${result.lot_number}&captcha_output=${result.captcha_output}&pass_token=${result.pass_token}&gen_time=${result.gen_time}&captcha_id=${siteKey}`); + }); + }); + clearInterval(gTimer); + } + }, 500); + break; + } default: break; }