mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 00:50:28 +08:00
feat: support configurable captcha(reCaptcha & hCaptcha) (#765)
* feat: support configurable captcha(layered architecture) * refactor & add captcha logo * rename captcha * Update authz.go * Update hcaptcha.go * Update default.go * Update recaptcha.go Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
This commit is contained in:
@ -95,7 +95,8 @@ p, *, *, GET, /api/get-providers, *, *
|
|||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
p, *, *, GET, /api/get-human-check, *, *
|
p, *, *, GET, /api/get-captcha, *, *
|
||||||
|
p, *, *, POST, /api/verify-captcha, *, *
|
||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
29
captcha/default.go
Normal file
29
captcha/default.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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 "github.com/casdoor/casdoor/object"
|
||||||
|
|
||||||
|
type DefaultCaptchaProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
|
||||||
|
captcha := &DefaultCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
return object.VerifyCaptcha(clientSecret, token), nil
|
||||||
|
}
|
60
captcha/hcaptcha.go
Normal file
60
captcha/hcaptcha.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const HCaptchaVerifyUrl = "https://hcaptcha.com/siteverify"
|
||||||
|
|
||||||
|
type HCaptchaProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHCaptchaProvider() *HCaptchaProvider {
|
||||||
|
captcha := &HCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
reqData := url.Values{
|
||||||
|
"secret": {clientSecret},
|
||||||
|
"response": {token},
|
||||||
|
}
|
||||||
|
resp, err := http.PostForm(HCaptchaVerifyUrl, reqData)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type captchaResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaResp.Success, nil
|
||||||
|
}
|
30
captcha/provider.go
Normal file
30
captcha/provider.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
type CaptchaProvider interface {
|
||||||
|
VerifyCaptcha(token, clientSecret string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||||
|
if captchaType == "Default" {
|
||||||
|
return NewDefaultCaptchaProvider()
|
||||||
|
} else if captchaType == "reCAPTCHA" {
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
} else if captchaType == "hCaptcha" {
|
||||||
|
return NewHCaptchaProvider()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
60
captcha/recaptcha.go
Normal file
60
captcha/recaptcha.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ReCaptchaVerifyUrl = "https://recaptcha.net/recaptcha/api/siteverify"
|
||||||
|
|
||||||
|
type ReCaptchaProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReCaptchaProvider() *ReCaptchaProvider {
|
||||||
|
captcha := &ReCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
reqData := url.Values{
|
||||||
|
"secret": {clientSecret},
|
||||||
|
"response": {token},
|
||||||
|
}
|
||||||
|
resp, err := http.PostForm(ReCaptchaVerifyUrl, reqData)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type captchaResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaResp.Success, nil
|
||||||
|
}
|
@ -75,12 +75,14 @@ type Response struct {
|
|||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanCheck struct {
|
type Captcha struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
Scene string `json:"scene"`
|
Scene string `json:"scene"`
|
||||||
CaptchaId string `json:"captchaId"`
|
CaptchaId string `json:"captchaId"`
|
||||||
CaptchaImage interface{} `json:"captchaImage"`
|
CaptchaImage []byte `json:"captchaImage"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signup
|
// Signup
|
||||||
@ -291,20 +293,27 @@ func (c *ApiController) GetUserinfo() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHumanCheck ...
|
// GetCaptcha ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetHumancheck
|
// @Title GetCaptcha
|
||||||
// @router /api/get-human-check [get]
|
// @router /api/get-captcha [get]
|
||||||
func (c *ApiController) GetHumanCheck() {
|
func (c *ApiController) GetCaptcha() {
|
||||||
c.Data["json"] = HumanCheck{Type: "none"}
|
applicationId := c.Input().Get("applicationId")
|
||||||
|
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||||
|
|
||||||
provider := object.GetDefaultHumanCheckProvider()
|
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
|
||||||
if provider == nil {
|
if err != nil {
|
||||||
id, img := object.GetCaptcha()
|
c.ResponseError(err.Error())
|
||||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ServeJSON()
|
if captchaProvider.Type == "Default" {
|
||||||
|
id, img := object.GetCaptcha()
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
||||||
|
return
|
||||||
|
} else if captchaProvider.Type != "" {
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, ClientId: captchaProvider.ClientId, ClientSecret: captchaProvider.ClientSecret})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(Captcha{Type: "none"})
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/captcha"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -48,15 +49,25 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||||
|
|
||||||
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
|
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 {
|
||||||
c.ResponseError("Missing parameter.")
|
c.ResponseError("Missing parameter.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isHuman := false
|
provider := captcha.GetCaptchaProvider(checkType)
|
||||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
if provider == nil {
|
||||||
if captchaProvider == nil {
|
c.ResponseError("Invalid captcha provider.")
|
||||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkKey == "" {
|
||||||
|
c.ResponseError("Missing parameter: checkKey.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isHuman, err := provider.VerifyCaptcha(checkKey, checkId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("Failed to verify captcha: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isHuman {
|
if !isHuman {
|
||||||
@ -173,3 +184,36 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyCaptcha ...
|
||||||
|
// @Title VerifyCaptcha
|
||||||
|
// @Tag Verification API
|
||||||
|
// @router /verify-captcha [post]
|
||||||
|
func (c *ApiController) VerifyCaptcha() {
|
||||||
|
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
||||||
|
|
||||||
|
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||||
|
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||||
|
if captchaToken == "" {
|
||||||
|
c.ResponseError("Missing parameter: captchaToken.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clientSecret == "" {
|
||||||
|
c.ResponseError("Missing parameter: clientSecret.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := captcha.GetCaptchaProvider(captchaType)
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError("Invalid captcha provider.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("Failed to verify captcha: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(isValid)
|
||||||
|
}
|
||||||
|
@ -142,8 +142,8 @@ func GetProvider(id string) *Provider {
|
|||||||
return getProvider(owner, name)
|
return getProvider(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultHumanCheckProvider() *Provider {
|
func GetDefaultCaptchaProvider() *Provider {
|
||||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||||
existed, err := adapter.Engine.Get(&provider)
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -225,3 +225,37 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
|||||||
func (p *Provider) GetId() string {
|
func (p *Provider) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(applicationId)
|
||||||
|
provider := Provider{Owner: owner, Name: name, Category: "Captcha"}
|
||||||
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil, fmt.Errorf("the provider: %s does not exist", applicationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider string) (*Provider, error) {
|
||||||
|
if isCurrentProvider == "true" {
|
||||||
|
return GetCaptchaProviderByOwnerName(applicationId)
|
||||||
|
}
|
||||||
|
application := GetApplication(applicationId)
|
||||||
|
if application == nil || len(application.Providers) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid application id")
|
||||||
|
}
|
||||||
|
for _, provider := range application.Providers {
|
||||||
|
if provider.Provider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if provider.Provider.Category == "Captcha" {
|
||||||
|
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no captcha provider found")
|
||||||
|
}
|
||||||
|
@ -94,8 +94,9 @@ func initAPI() {
|
|||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||||
|
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||||
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||||
|
|
||||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||||
|
@ -378,12 +378,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/api/get-human-check": {
|
"/api/api/get-captcha": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Login API"
|
"Login API"
|
||||||
],
|
],
|
||||||
"operationId": "ApiController.GetHumancheck"
|
"operationId": "ApiController.GetCaptcha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/api/reset-email-or-phone": {
|
"/api/api/reset-email-or-phone": {
|
||||||
|
@ -243,11 +243,11 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/api/get-human-check:
|
/api/api/get-captcha:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
operationId: ApiController.GetHumancheck
|
operationId: ApiController.GetCaptcha
|
||||||
/api/api/reset-email-or-phone:
|
/api/api/reset-email-or-phone:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -20,6 +20,7 @@ import * as Setting from "./Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { authConfig } from "./auth/Auth";
|
import { authConfig } from "./auth/Auth";
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { CaptchaPreview } from "./common/CaptchaPreview";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@ -77,6 +78,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
}
|
}
|
||||||
|
case "Captcha":
|
||||||
|
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
}
|
}
|
||||||
@ -94,6 +97,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
}
|
}
|
||||||
|
case "Captcha":
|
||||||
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
}
|
}
|
||||||
@ -193,6 +198,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField('domain', Setting.getFullServerUrl());
|
this.updateProviderField('domain', Setting.getFullServerUrl());
|
||||||
} else if (value === "SAML") {
|
} else if (value === "SAML") {
|
||||||
this.updateProviderField('type', 'Aliyun IDaaS');
|
this.updateProviderField('type', 'Aliyun IDaaS');
|
||||||
|
} else if (value === "Captcha") {
|
||||||
|
this.updateProviderField('type', 'Default');
|
||||||
}
|
}
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
@ -203,6 +210,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
{id: 'Storage', name: 'Storage'},
|
{id: 'Storage', name: 'Storage'},
|
||||||
{id: 'SAML', name: 'SAML'},
|
{id: 'SAML', name: 'SAML'},
|
||||||
{id: 'Payment', name: 'Payment'},
|
{id: 'Payment', name: 'Payment'},
|
||||||
|
{id: 'Captcha', name: 'Captcha'},
|
||||||
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
@ -341,26 +349,31 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Row style={{marginTop: '20px'}} >
|
{
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.type !== "Default" &&
|
||||||
{this.getClientIdLabel()}
|
<>
|
||||||
</Col>
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col span={22} >
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Input value={this.state.provider.clientId} onChange={e => {
|
{this.getClientIdLabel()}
|
||||||
this.updateProviderField('clientId', e.target.value);
|
</Col>
|
||||||
}} />
|
<Col span={22} >
|
||||||
</Col>
|
<Input value={this.state.provider.clientId} onChange={e => {
|
||||||
</Row>
|
this.updateProviderField('clientId', e.target.value);
|
||||||
<Row style={{marginTop: '20px'}} >
|
}} />
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
</Col>
|
||||||
{this.getClientSecretLabel()}
|
</Row>
|
||||||
</Col>
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col span={22} >
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
{this.getClientSecretLabel()}
|
||||||
this.updateProviderField('clientSecret', e.target.value);
|
</Col>
|
||||||
}} />
|
<Col span={22} >
|
||||||
</Col>
|
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||||
</Row>
|
this.updateProviderField('clientSecret', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeChat" ? null : (
|
this.state.provider.type !== "WeChat" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -627,16 +640,39 @@ class ProviderEditPage extends React.Component {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{this.getAppIdRow()}
|
{this.getAppIdRow()}
|
||||||
<Row style={{marginTop: '20px'}} >
|
{
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.type !== "Default" &&
|
||||||
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
<Row style={{marginTop: '20px'}} >
|
||||||
</Col>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Col span={22} >
|
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
||||||
<Input prefix={<LinkOutlined/>} value={this.state.provider.providerUrl} onChange={e => {
|
</Col>
|
||||||
this.updateProviderField('providerUrl', e.target.value);
|
<Col span={22} >
|
||||||
}} />
|
<Input prefix={<LinkOutlined/>} value={this.state.provider.providerUrl} onChange={e => {
|
||||||
</Col>
|
this.updateProviderField('providerUrl', e.target.value);
|
||||||
</Row>
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.provider.category === "Captcha" &&
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<CaptchaPreview
|
||||||
|
provider={this.state.provider}
|
||||||
|
providerName={this.state.providerName}
|
||||||
|
clientSecret={this.state.provider.clientSecret}
|
||||||
|
captchaType={this.state.provider.type}
|
||||||
|
owner={this.state.provider.owner}
|
||||||
|
clientId={this.state.provider.clientId}
|
||||||
|
name={this.state.provider.name}
|
||||||
|
providerUrl={this.state.provider.providerUrl}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,7 @@ class ProviderListPage extends BaseListPage {
|
|||||||
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
|
{text: 'Captcha', value: 'Captcha', children: Setting.getProviderTypeOptions('Captcha').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
],
|
],
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
@ -106,6 +106,20 @@ export const OtherProviderInfo = {
|
|||||||
url: "https://gc.org"
|
url: "https://gc.org"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Captcha: {
|
||||||
|
"Default": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||||
|
url: "https://pkg.go.dev/github.com/dchest/captcha",
|
||||||
|
},
|
||||||
|
"reCAPTCHA": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
|
"hCaptcha": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
||||||
|
url: "https://www.hcaptcha.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getCountryRegionData() {
|
export function getCountryRegionData() {
|
||||||
@ -595,6 +609,12 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'PayPal', name: 'PayPal'},
|
{id: 'PayPal', name: 'PayPal'},
|
||||||
{id: 'GC', name: 'GC'},
|
{id: 'GC', name: 'GC'},
|
||||||
]);
|
]);
|
||||||
|
} else if (category === "Captcha") {
|
||||||
|
return ([
|
||||||
|
{id: 'Default', name: 'Default'},
|
||||||
|
{id: 'reCAPTCHA', name: 'reCAPTCHA'},
|
||||||
|
{id: 'hCaptcha', name: 'hCaptcha'},
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,30 @@ export function sendCode(checkType, checkId, checkKey, dest, type, orgId, checkU
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function verifyCaptcha(captchaType, captchaToken, clientSecret) {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("captchaType", captchaType);
|
||||||
|
formData.append("captchaToken", captchaToken);
|
||||||
|
formData.append("clientSecret", clientSecret);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/verify-captcha`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: formData
|
||||||
|
}).then(res => res.json()).then(res => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
if (res.data) {
|
||||||
|
Setting.showMessage("success", i18next.t("user:Captcha Verify Success"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("user:Captcha Verify Failed"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("user:" + res.msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function resetEmailOrPhone(dest, type, code) {
|
export function resetEmailOrPhone(dest, type, code) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("dest", dest);
|
formData.append("dest", dest);
|
||||||
@ -124,8 +148,8 @@ export function resetEmailOrPhone(dest, type, code) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHumanCheck() {
|
export function getCaptcha(owner, name, isCurrentProvider) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-human-check`, {
|
return fetch(`${Setting.ServerUrl}/api/get-captcha?applicationId=${owner}/${encodeURIComponent(name)}&isCurrentProvider=${isCurrentProvider}`, {
|
||||||
method: "GET"
|
method: "GET"
|
||||||
}).then(res => res.json());
|
}).then(res => res.json()).then(res => res.data);
|
||||||
}
|
}
|
||||||
|
139
web/src/common/CaptchaPreview.js
Normal file
139
web/src/common/CaptchaPreview.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import { Button, Col, Input, Modal, Row } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
|
import * as ProviderBackend from "../backend/ProviderBackend";
|
||||||
|
import { SafetyOutlined } from "@ant-design/icons";
|
||||||
|
import { CaptchaWidget } from "./CaptchaWidget";
|
||||||
|
|
||||||
|
export const CaptchaPreview = ({ provider, providerName, clientSecret, captchaType, owner, clientId, name, providerUrl }) => {
|
||||||
|
const [visible, setVisible] = React.useState(false);
|
||||||
|
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||||
|
const [captchaToken, setCaptchaToken] = React.useState("");
|
||||||
|
const [secret, setSecret] = React.useState(clientSecret);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
UserBackend.verifyCaptcha(
|
||||||
|
captchaType,
|
||||||
|
captchaToken,
|
||||||
|
secret
|
||||||
|
).then(() => {
|
||||||
|
setCaptchaToken("");
|
||||||
|
setVisible(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCaptchaFromBackend = () => {
|
||||||
|
UserBackend.getCaptcha(owner, name, true).then((res) => {
|
||||||
|
if (captchaType === "Default") {
|
||||||
|
setSecret(res.captchaId);
|
||||||
|
setCaptchaImg(res.captchaImage);
|
||||||
|
} else {
|
||||||
|
setSecret(res.clientSecret);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickPreview = () => {
|
||||||
|
setVisible(true);
|
||||||
|
provider.name = name;
|
||||||
|
provider.clientId = clientId;
|
||||||
|
provider.type = captchaType;
|
||||||
|
provider.providerUrl = providerUrl;
|
||||||
|
if (clientSecret !== "***") {
|
||||||
|
provider.clientSecret = clientSecret;
|
||||||
|
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
||||||
|
getCaptchaFromBackend();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getCaptchaFromBackend();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDefaultCaptcha = () => {
|
||||||
|
return (
|
||||||
|
<Col>
|
||||||
|
<Row
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
height: "80px",
|
||||||
|
width: "200px",
|
||||||
|
borderRadius: "3px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Row>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
value={captchaToken}
|
||||||
|
prefix={<SafetyOutlined />}
|
||||||
|
placeholder={i18next.t("general:Captcha")}
|
||||||
|
onPressEnter={handleOk}
|
||||||
|
onChange={(e) => setCaptchaToken(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (token) => {
|
||||||
|
setCaptchaToken(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const renderCheck = () => {
|
||||||
|
if (captchaType === "Default") {
|
||||||
|
return renderDefaultCaptcha();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<CaptchaWidget
|
||||||
|
captchaType={captchaType}
|
||||||
|
siteKey={clientId}
|
||||||
|
onChange={onSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button style={{ fontSize: 14 }} type={"primary"} onClick={clickPreview}>
|
||||||
|
{i18next.t("general:Preview")}
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
closable={false}
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose={true}
|
||||||
|
title={i18next.t("general:Captcha")}
|
||||||
|
visible={visible}
|
||||||
|
okText={i18next.t("user:OK")}
|
||||||
|
cancelText={i18next.t("user:Cancel")}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
width={348}
|
||||||
|
>
|
||||||
|
{renderCheck()}
|
||||||
|
</Modal>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
62
web/src/common/CaptchaWidget.js
Normal file
62
web/src/common/CaptchaWidget.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
export const CaptchaWidget = ({ captchaType, siteKey, onChange }) => {
|
||||||
|
const loadScript = (src) => {
|
||||||
|
var tag = document.createElement("script");
|
||||||
|
tag.async = false;
|
||||||
|
tag.src = src;
|
||||||
|
var body = document.getElementsByTagName("body")[0];
|
||||||
|
body.appendChild(tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (captchaType) {
|
||||||
|
case "reCAPTCHA":
|
||||||
|
const reTimer = setInterval(() => {
|
||||||
|
if (!window.grecaptcha) {
|
||||||
|
loadScript("https://recaptcha.net/recaptcha/api.js");
|
||||||
|
}
|
||||||
|
if (window.grecaptcha) {
|
||||||
|
window.grecaptcha.render("captcha", {
|
||||||
|
sitekey: siteKey,
|
||||||
|
callback: onChange,
|
||||||
|
});
|
||||||
|
clearInterval(reTimer);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
case "hCaptcha":
|
||||||
|
const hTimer = setInterval(() => {
|
||||||
|
if (!window.hcaptcha) {
|
||||||
|
loadScript("https://js.hcaptcha.com/1/api.js");
|
||||||
|
}
|
||||||
|
if (window.hcaptcha) {
|
||||||
|
window.hcaptcha.render("captcha", {
|
||||||
|
sitekey: siteKey,
|
||||||
|
callback: onChange,
|
||||||
|
});
|
||||||
|
clearInterval(hTimer);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [captchaType, siteKey]);
|
||||||
|
|
||||||
|
return <div id="captcha"></div>;
|
||||||
|
};
|
@ -18,6 +18,8 @@ import * as Setting from "../Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
import {SafetyOutlined} from "@ant-design/icons";
|
import {SafetyOutlined} from "@ant-design/icons";
|
||||||
|
import {authConfig} from "../auth/Auth";
|
||||||
|
import { CaptchaWidget } from "./CaptchaWidget";
|
||||||
|
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
|
||||||
@ -30,6 +32,8 @@ export const CountDownInput = (props) => {
|
|||||||
const [checkId, setCheckId] = React.useState("");
|
const [checkId, setCheckId] = React.useState("");
|
||||||
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
|
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
|
||||||
const [buttonLoading, setButtonLoading] = React.useState(false);
|
const [buttonLoading, setButtonLoading] = React.useState(false);
|
||||||
|
const [buttonDisabled, setButtonDisabled] = React.useState(true);
|
||||||
|
const [clientId, setClientId] = React.useState("");
|
||||||
|
|
||||||
const handleCountDown = (leftTime = 60) => {
|
const handleCountDown = (leftTime = 60) => {
|
||||||
let leftTimeSecond = leftTime
|
let leftTimeSecond = leftTime
|
||||||
@ -62,14 +66,19 @@ export const CountDownInput = (props) => {
|
|||||||
setKey("");
|
setKey("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadHumanCheck = () => {
|
const loadCaptcha = () => {
|
||||||
UserBackend.getHumanCheck().then(res => {
|
UserBackend.getCaptcha("admin", authConfig.appName, false).then(res => {
|
||||||
if (res.type === "none") {
|
if (res.type === "none") {
|
||||||
UserBackend.sendCode("none", "", "", ...onButtonClickArgs);
|
UserBackend.sendCode("none", "", "", ...onButtonClickArgs);
|
||||||
} else if (res.type === "captcha") {
|
} else if (res.type === "Default") {
|
||||||
setCheckId(res.captchaId);
|
setCheckId(res.captchaId);
|
||||||
setCaptchaImg(res.captchaImage);
|
setCaptchaImg(res.captchaImage);
|
||||||
setCheckType("captcha");
|
setCheckType("Default");
|
||||||
|
setVisible(true);
|
||||||
|
} else if (res.type === "reCAPTCHA" || res.type === "hCaptcha") {
|
||||||
|
setCheckType(res.type);
|
||||||
|
setClientId(res.clientId);
|
||||||
|
setCheckId(res.clientSecret);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", i18next.t("signup:Unknown Check Type"));
|
Setting.showMessage("error", i18next.t("signup:Unknown Check Type"));
|
||||||
@ -98,9 +107,23 @@ export const CountDownInput = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSubmit = (token) => {
|
||||||
|
setButtonDisabled(false);
|
||||||
|
setKey(token);
|
||||||
|
}
|
||||||
|
|
||||||
const renderCheck = () => {
|
const renderCheck = () => {
|
||||||
if (checkType === "captcha") return renderCaptcha();
|
if (checkType === "Default") {
|
||||||
return null;
|
return renderCaptcha();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<CaptchaWidget
|
||||||
|
captchaType={checkType}
|
||||||
|
siteKey={clientId}
|
||||||
|
onChange={onSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -116,7 +139,7 @@ export const CountDownInput = (props) => {
|
|||||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
onSearch={loadHumanCheck}
|
onSearch={loadCaptcha}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
closable={false}
|
closable={false}
|
||||||
@ -128,8 +151,8 @@ export const CountDownInput = (props) => {
|
|||||||
cancelText={i18next.t("user:Cancel")}
|
cancelText={i18next.t("user:Cancel")}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
okButtonProps={{disabled: key.length !== 5}}
|
okButtonProps={{disabled: key.length !== 5 && buttonDisabled}}
|
||||||
width={248}
|
width={348}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
renderCheck()
|
renderCheck()
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Geheimer Zugangsschlüssel",
|
"Secret access key": "Geheimer Zugangsschlüssel",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||||
"Sign Name": "Schild Name",
|
"Sign Name": "Schild Name",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "HTML registrieren",
|
"Signup HTML": "HTML registrieren",
|
||||||
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
||||||
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Vorlagencode",
|
"Template Code": "Vorlagencode",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Tooltip",
|
"Bio - Tooltip": "Bio - Tooltip",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code gesendet",
|
"Code Sent": "Code gesendet",
|
||||||
"Country/Region": "Land/Region",
|
"Country/Region": "Land/Region",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Secret access key",
|
"Secret access key": "Secret access key",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||||
"Sign Name": "Sign Name",
|
"Sign Name": "Sign Name",
|
||||||
"Sign Name - Tooltip": "Sign Name - Tooltip",
|
"Sign Name - Tooltip": "Sign Name - Tooltip",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "Signup HTML",
|
"Signup HTML": "Signup HTML",
|
||||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Template Code",
|
"Template Code": "Template Code",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Tooltip",
|
"Bio - Tooltip": "Bio - Tooltip",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code Sent",
|
"Code Sent": "Code Sent",
|
||||||
"Country/Region": "Country/Region",
|
"Country/Region": "Country/Region",
|
||||||
"Country/Region - Tooltip": "Country/Region - Tooltip",
|
"Country/Region - Tooltip": "Country/Region - Tooltip",
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Clé d'accès secrète",
|
"Secret access key": "Clé d'accès secrète",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Infobulle",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Infobulle",
|
||||||
"Sign Name": "Nom du panneau",
|
"Sign Name": "Nom du panneau",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "Inscription HTML",
|
"Signup HTML": "Inscription HTML",
|
||||||
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
||||||
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Code du modèle",
|
"Template Code": "Code du modèle",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Infobulle",
|
"Bio - Tooltip": "Bio - Infobulle",
|
||||||
"Cancel": "Abandonner",
|
"Cancel": "Abandonner",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code envoyé",
|
"Code Sent": "Code envoyé",
|
||||||
"Country/Region": "Pays/Région",
|
"Country/Region": "Pays/Région",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "シークレットアクセスキー",
|
"Secret access key": "シークレットアクセスキー",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "シークレットアクセスキー - ツールチップ",
|
"SecretAccessKey - Tooltip": "シークレットアクセスキー - ツールチップ",
|
||||||
"Sign Name": "署名名",
|
"Sign Name": "署名名",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "HTMLの登録",
|
"Signup HTML": "HTMLの登録",
|
||||||
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
||||||
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "テンプレートコード",
|
"Template Code": "テンプレートコード",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "略歴",
|
"Bio": "略歴",
|
||||||
"Bio - Tooltip": "バイオチップ(ツールチップ)",
|
"Bio - Tooltip": "バイオチップ(ツールチップ)",
|
||||||
"Cancel": "キャンセル",
|
"Cancel": "キャンセル",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "コードを送信しました",
|
"Code Sent": "コードを送信しました",
|
||||||
"Country/Region": "国/地域",
|
"Country/Region": "国/地域",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Secret access key",
|
"Secret access key": "Secret access key",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||||
"Sign Name": "Sign Name",
|
"Sign Name": "Sign Name",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "Signup HTML",
|
"Signup HTML": "Signup HTML",
|
||||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Template Code",
|
"Template Code": "Template Code",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Tooltip",
|
"Bio - Tooltip": "Bio - Tooltip",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code Sent",
|
"Code Sent": "Code Sent",
|
||||||
"Country/Region": "Country/Region",
|
"Country/Region": "Country/Region",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Секретный ключ доступа",
|
"Secret access key": "Секретный ключ доступа",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Подсказка",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Подсказка",
|
||||||
"Sign Name": "Имя подписи",
|
"Sign Name": "Имя подписи",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "Регистрация HTML",
|
"Signup HTML": "Регистрация HTML",
|
||||||
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
||||||
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Код шаблона",
|
"Template Code": "Код шаблона",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "Био",
|
"Bio": "Био",
|
||||||
"Bio - Tooltip": "Био - Подсказка",
|
"Bio - Tooltip": "Био - Подсказка",
|
||||||
"Cancel": "Отмена",
|
"Cancel": "Отмена",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Код отправлен",
|
"Code Sent": "Код отправлен",
|
||||||
"Country/Region": "Страна/регион",
|
"Country/Region": "Страна/регион",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -445,6 +445,8 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - 工具提示",
|
"Scope - Tooltip": "Scope - 工具提示",
|
||||||
"Secret access key": "秘密访问密钥",
|
"Secret access key": "秘密访问密钥",
|
||||||
|
"Secret key": "服务端密钥",
|
||||||
|
"Secret key - Tooltip": "服务端密钥",
|
||||||
"SecretAccessKey - Tooltip": "访问密钥-工具提示",
|
"SecretAccessKey - Tooltip": "访问密钥-工具提示",
|
||||||
"Sign Name": "签名名称",
|
"Sign Name": "签名名称",
|
||||||
"Sign Name - Tooltip": "签名名称",
|
"Sign Name - Tooltip": "签名名称",
|
||||||
@ -456,6 +458,8 @@
|
|||||||
"Signup HTML": "注册页面HTML",
|
"Signup HTML": "注册页面HTML",
|
||||||
"Signup HTML - Edit": "注册页面HTML - 编辑",
|
"Signup HTML - Edit": "注册页面HTML - 编辑",
|
||||||
"Signup HTML - Tooltip": "自定义HTML,用于替换默认的注册页面样式",
|
"Signup HTML - Tooltip": "自定义HTML,用于替换默认的注册页面样式",
|
||||||
|
"Site key": "客户端密钥",
|
||||||
|
"Site key - Tooltip": "客户端密钥",
|
||||||
"Sub type": "子类型",
|
"Sub type": "子类型",
|
||||||
"Sub type - Tooltip": "子类型",
|
"Sub type - Tooltip": "子类型",
|
||||||
"Template Code": "模板代码",
|
"Template Code": "模板代码",
|
||||||
@ -578,6 +582,8 @@
|
|||||||
"Bio": "自我介绍",
|
"Bio": "自我介绍",
|
||||||
"Bio - Tooltip": "自我介绍",
|
"Bio - Tooltip": "自我介绍",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
|
"Captcha Verify Failed": "验证码校验失败",
|
||||||
|
"Captcha Verify Success": "验证码校验成功",
|
||||||
"Code Sent": "验证码已发送",
|
"Code Sent": "验证码已发送",
|
||||||
"Country/Region": "国家/地区",
|
"Country/Region": "国家/地区",
|
||||||
"Country/Region - Tooltip": "国家/地区",
|
"Country/Region - Tooltip": "国家/地区",
|
||||||
|
Reference in New Issue
Block a user