mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-03 04:10:20 +08:00
feat: allow captcha to be enabled when logging in (#1211)
* Fix bug in GetAcceptLanguage() * feat: allow captcha to be enabled when logging in * feat: when the login password is wrong, enable captcha * feat: Restrict captcha from frontend * fix: modify CaptchaModal component * fix: modify the words of i18n * Update data.json Co-authored-by: Gucheng Wang <nomeguy@qq.com> Co-authored-by: hsluoyz <hsluoyz@qq.com>
This commit is contained in:
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package captcha
|
package captcha
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type CaptchaProvider interface {
|
type CaptchaProvider interface {
|
||||||
VerifyCaptcha(token, clientSecret string) (bool, error)
|
VerifyCaptcha(token, clientSecret string) (bool, error)
|
||||||
}
|
}
|
||||||
@ -32,3 +34,12 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func VerifyCaptchaByCaptchaType(captchaType, token, clientSecret string) (bool, error) {
|
||||||
|
provider := GetCaptchaProvider(captchaType)
|
||||||
|
if provider == nil {
|
||||||
|
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.VerifyCaptcha(token, clientSecret)
|
||||||
|
}
|
||||||
|
@ -64,6 +64,10 @@ type RequestForm struct {
|
|||||||
RelayState string `json:"relayState"`
|
RelayState string `json:"relayState"`
|
||||||
SamlRequest string `json:"samlRequest"`
|
SamlRequest string `json:"samlRequest"`
|
||||||
SamlResponse string `json:"samlResponse"`
|
SamlResponse string `json:"samlResponse"`
|
||||||
|
|
||||||
|
CaptchaType string `json:"captchaType"`
|
||||||
|
CaptchaToken string `json:"captchaToken"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@ -23,6 +23,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/captcha"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/idp"
|
"github.com/casdoor/casdoor/idp"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -251,6 +253,25 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.CheckToEnableCaptcha(application) {
|
||||||
|
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isHuman {
|
||||||
|
c.ResponseError("Turing test failed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
password := form.Password
|
password := form.Password
|
||||||
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
|
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
|
||||||
}
|
}
|
||||||
|
@ -340,3 +340,20 @@ func CheckUsername(username string, lang string) string {
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckToEnableCaptcha(application *Application) bool {
|
||||||
|
if len(application.Providers) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, providerItem := range application.Providers {
|
||||||
|
if providerItem.Provider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if providerItem.Provider.Category == "Captcha" && providerItem.Provider.Type == "Default" {
|
||||||
|
return providerItem.Rule == "Always"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -143,7 +143,7 @@ func initBuiltInApplication() {
|
|||||||
EnablePassword: true,
|
EnablePassword: true,
|
||||||
EnableSignUp: true,
|
EnableSignUp: true,
|
||||||
Providers: []*ProviderItem{
|
Providers: []*ProviderItem{
|
||||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
|
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Rule: "None", Provider: nil},
|
||||||
},
|
},
|
||||||
SignupItems: []*SignupItem{
|
SignupItems: []*SignupItem{
|
||||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||||
|
@ -21,6 +21,7 @@ type ProviderItem struct {
|
|||||||
CanUnlink bool `json:"canUnlink"`
|
CanUnlink bool `json:"canUnlink"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
AlertType string `json:"alertType"`
|
AlertType string `json:"alertType"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
Provider *Provider `json:"provider"`
|
Provider *Provider `json:"provider"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class ProviderTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRow(table) {
|
addRow(table) {
|
||||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None"};
|
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None", rule: "None"};
|
||||||
if (table === undefined) {
|
if (table === undefined) {
|
||||||
table = [];
|
table = [];
|
||||||
}
|
}
|
||||||
@ -193,6 +193,28 @@ class ProviderTable extends React.Component {
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
title: i18next.t("application:Rule"),
|
||||||
|
dataIndex: "rule",
|
||||||
|
key: "rule",
|
||||||
|
width: "100px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (record.provider?.category !== "Captcha") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
value={text}
|
||||||
|
defaultValue="None"
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, "rule", value);
|
||||||
|
}} >
|
||||||
|
<Option key="None" value="None">{i18next.t("application:None")}</Option>
|
||||||
|
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Action"),
|
title: i18next.t("general:Action"),
|
||||||
key: "action",
|
key: "action",
|
||||||
|
@ -164,7 +164,7 @@ class SignupTable extends React.Component {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("application:rule"),
|
title: i18next.t("application:Rule"),
|
||||||
dataIndex: "rule",
|
dataIndex: "rule",
|
||||||
key: "rule",
|
key: "rule",
|
||||||
width: "155px",
|
width: "155px",
|
||||||
|
@ -29,6 +29,7 @@ import CustomGithubCorner from "../CustomGithubCorner";
|
|||||||
import {CountDownInput} from "../common/CountDownInput";
|
import {CountDownInput} from "../common/CountDownInput";
|
||||||
import SelectLanguageBox from "../SelectLanguageBox";
|
import SelectLanguageBox from "../SelectLanguageBox";
|
||||||
import {withTranslation} from "react-i18next";
|
import {withTranslation} from "react-i18next";
|
||||||
|
import {CaptchaModal} from "../common/CaptchaModal";
|
||||||
|
|
||||||
const {TabPane} = Tabs;
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
@ -48,6 +49,9 @@ class LoginPage extends React.Component {
|
|||||||
validEmail: false,
|
validEmail: false,
|
||||||
validPhone: false,
|
validPhone: false,
|
||||||
loginMethod: "password",
|
loginMethod: "password",
|
||||||
|
enableCaptchaModal: false,
|
||||||
|
openCaptchaModal: false,
|
||||||
|
verifyCaptcha: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||||
@ -68,6 +72,18 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
if (this.state.application && !prevState.application) {
|
||||||
|
const defaultCaptchaProviderItems = this.getDefaultCaptchaProviderItems(this.state.application);
|
||||||
|
|
||||||
|
if (!defaultCaptchaProviderItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({enableCaptchaModal: defaultCaptchaProviderItems.some(providerItem => providerItem.rule === "Always")});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getApplicationLogin() {
|
getApplicationLogin() {
|
||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
AuthBackend.getApplicationLogin(oAuthParams)
|
AuthBackend.getApplicationLogin(oAuthParams)
|
||||||
@ -225,6 +241,23 @@ class LoginPage extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.loginMethod === "password" && this.state.enableCaptchaModal) {
|
||||||
|
this.setState({
|
||||||
|
openCaptchaModal: true,
|
||||||
|
verifyCaptcha: (captchaType, captchaToken, secret) => {
|
||||||
|
values["captchaType"] = captchaType;
|
||||||
|
values["captchaToken"] = captchaToken;
|
||||||
|
values["clientSecret"] = secret;
|
||||||
|
|
||||||
|
this.login(values);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.login(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login(values) {
|
||||||
// here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
|
// here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
|
||||||
if (this.state.type === "cas") {
|
if (this.state.type === "cas") {
|
||||||
// CAS
|
// CAS
|
||||||
@ -239,6 +272,8 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
Util.showMessage("success", msg);
|
Util.showMessage("success", msg);
|
||||||
|
|
||||||
|
this.setState({openCaptchaModal: false});
|
||||||
|
|
||||||
if (casParams.service !== "") {
|
if (casParams.service !== "") {
|
||||||
const st = res.data;
|
const st = res.data;
|
||||||
const newUrl = new URL(casParams.service);
|
const newUrl = new URL(casParams.service);
|
||||||
@ -246,6 +281,7 @@ class LoginPage extends React.Component {
|
|||||||
window.location.href = newUrl.toString();
|
window.location.href = newUrl.toString();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.setState({openCaptchaModal: false});
|
||||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -258,6 +294,7 @@ class LoginPage extends React.Component {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const responseType = values["type"];
|
const responseType = values["type"];
|
||||||
|
|
||||||
if (responseType === "login") {
|
if (responseType === "login") {
|
||||||
Util.showMessage("success", "Logged in successfully");
|
Util.showMessage("success", "Logged in successfully");
|
||||||
|
|
||||||
@ -275,6 +312,7 @@ class LoginPage extends React.Component {
|
|||||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.setState({openCaptchaModal: false});
|
||||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -418,6 +456,9 @@ class LoginPage extends React.Component {
|
|||||||
i18next.t("login:Sign In")
|
i18next.t("login:Sign In")
|
||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
|
{
|
||||||
|
this.renderCaptchaModal(application)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
this.renderFooter(application)
|
this.renderFooter(application)
|
||||||
}
|
}
|
||||||
@ -460,6 +501,46 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefaultCaptchaProviderItems(application) {
|
||||||
|
const providers = application?.providers;
|
||||||
|
|
||||||
|
if (providers === undefined || providers === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers.filter(providerItem => {
|
||||||
|
if (providerItem.provider === undefined || providerItem.provider === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerItem.provider.category === "Captcha" && providerItem.provider.type === "Default";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCaptchaModal(application) {
|
||||||
|
if (!this.state.enableCaptchaModal) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.getDefaultCaptchaProviderItems(application)
|
||||||
|
.filter(providerItem => providerItem.rule === "Always")
|
||||||
|
.map(providerItem => providerItem.provider)[0];
|
||||||
|
|
||||||
|
return <CaptchaModal
|
||||||
|
owner={provider.owner}
|
||||||
|
name={provider.name}
|
||||||
|
captchaType={provider.type}
|
||||||
|
subType={provider.subType}
|
||||||
|
clientId={provider.clientId}
|
||||||
|
clientId2={provider.clientId2}
|
||||||
|
clientSecret={provider.clientSecret}
|
||||||
|
clientSecret2={provider.clientSecret2}
|
||||||
|
open={this.state.openCaptchaModal}
|
||||||
|
onOk={(captchaType, captchaToken, secret) => this.state.verifyCaptcha?.(captchaType, captchaToken, secret)}
|
||||||
|
canCancel={false}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
renderFooter(application) {
|
renderFooter(application) {
|
||||||
if (this.state.mode === "signup") {
|
if (this.state.mode === "signup") {
|
||||||
return (
|
return (
|
||||||
|
159
web/src/common/CaptchaModal.js
Normal file
159
web/src/common/CaptchaModal.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// 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 i18next from "i18next";
|
||||||
|
import React, {useEffect} from "react";
|
||||||
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
|
import {CaptchaWidget} from "./CaptchaWidget";
|
||||||
|
import {SafetyOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
export const CaptchaModal = ({
|
||||||
|
owner,
|
||||||
|
name,
|
||||||
|
captchaType,
|
||||||
|
subType,
|
||||||
|
clientId,
|
||||||
|
clientId2,
|
||||||
|
clientSecret,
|
||||||
|
clientSecret2,
|
||||||
|
open,
|
||||||
|
onOk,
|
||||||
|
onCancel,
|
||||||
|
canCancel,
|
||||||
|
}) => {
|
||||||
|
const [visible, setVisible] = React.useState(false);
|
||||||
|
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||||
|
const [captchaToken, setCaptchaToken] = React.useState("");
|
||||||
|
const [secret, setSecret] = React.useState(clientSecret);
|
||||||
|
const [secret2, setSecret2] = React.useState(clientSecret2);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setVisible(() => {
|
||||||
|
if (open) {
|
||||||
|
getCaptchaFromBackend();
|
||||||
|
} else {
|
||||||
|
cleanUp();
|
||||||
|
}
|
||||||
|
return open;
|
||||||
|
});
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
onOk?.(captchaType, captchaToken, secret);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
onCancel?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanUp = () => {
|
||||||
|
setCaptchaToken("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCaptchaFromBackend = () => {
|
||||||
|
UserBackend.getCaptcha(owner, name, true).then((res) => {
|
||||||
|
if (captchaType === "Default") {
|
||||||
|
setSecret(res.captchaId);
|
||||||
|
setCaptchaImg(res.captchaImage);
|
||||||
|
} else {
|
||||||
|
setSecret(res.clientSecret);
|
||||||
|
setSecret2(res.clientSecret2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDefaultCaptcha = () => {
|
||||||
|
return (
|
||||||
|
<Col>
|
||||||
|
<Row
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
height: "80px",
|
||||||
|
width: "200px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
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 (
|
||||||
|
<Col>
|
||||||
|
<Row>
|
||||||
|
<CaptchaWidget
|
||||||
|
captchaType={captchaType}
|
||||||
|
subType={subType}
|
||||||
|
siteKey={clientId}
|
||||||
|
clientSecret={secret}
|
||||||
|
onChange={onSubmit}
|
||||||
|
clientId2={clientId2}
|
||||||
|
clientSecret2={secret2}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFooter = () => {
|
||||||
|
if (canCancel) {
|
||||||
|
return [
|
||||||
|
<Button key="cancel" onClick={handleCancel}>{i18next.t("user:Cancel")}</Button>,
|
||||||
|
<Button key="ok" type="primary" onClick={handleOk}>{i18next.t("user:OK")}</Button>,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
<Button key="ok" type="primary" onClick={handleOk}>{i18next.t("user:OK")}</Button>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Modal
|
||||||
|
closable={false}
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose={true}
|
||||||
|
title={i18next.t("general:Captcha")}
|
||||||
|
visible={visible}
|
||||||
|
width={348}
|
||||||
|
footer={renderFooter()}
|
||||||
|
>
|
||||||
|
{renderCheck()}
|
||||||
|
</Modal>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -12,13 +12,12 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import {Button, Col, Input, Modal, Row} from "antd";
|
import {Button} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import {CaptchaModal} from "./CaptchaModal";
|
||||||
import * as ProviderBackend from "../backend/ProviderBackend";
|
import * as ProviderBackend from "../backend/ProviderBackend";
|
||||||
import {SafetyOutlined} from "@ant-design/icons";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
import {CaptchaWidget} from "./CaptchaWidget";
|
|
||||||
|
|
||||||
export const CaptchaPreview = ({
|
export const CaptchaPreview = ({
|
||||||
provider,
|
provider,
|
||||||
@ -33,37 +32,9 @@ export const CaptchaPreview = ({
|
|||||||
clientId2,
|
clientId2,
|
||||||
clientSecret2,
|
clientSecret2,
|
||||||
}) => {
|
}) => {
|
||||||
const [visible, setVisible] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [captchaImg, setCaptchaImg] = React.useState("");
|
|
||||||
const [captchaToken, setCaptchaToken] = React.useState("");
|
|
||||||
const [secret, setSecret] = React.useState(clientSecret);
|
|
||||||
const [secret2, setSecret2] = React.useState(clientSecret2);
|
|
||||||
|
|
||||||
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);
|
|
||||||
setSecret2(res.clientSecret2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const clickPreview = () => {
|
const clickPreview = () => {
|
||||||
setVisible(true);
|
|
||||||
provider.name = name;
|
provider.name = name;
|
||||||
provider.clientId = clientId;
|
provider.clientId = clientId;
|
||||||
provider.type = captchaType;
|
provider.type = captchaType;
|
||||||
@ -71,64 +42,10 @@ export const CaptchaPreview = ({
|
|||||||
if (clientSecret !== "***") {
|
if (clientSecret !== "***") {
|
||||||
provider.clientSecret = clientSecret;
|
provider.clientSecret = clientSecret;
|
||||||
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
||||||
getCaptchaFromBackend();
|
setOpen(true);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
getCaptchaFromBackend();
|
setOpen(true);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDefaultCaptcha = () => {
|
|
||||||
return (
|
|
||||||
<Col>
|
|
||||||
<Row
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
height: "80px",
|
|
||||||
width: "200px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
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 (
|
|
||||||
<Col>
|
|
||||||
<Row>
|
|
||||||
<CaptchaWidget
|
|
||||||
captchaType={captchaType}
|
|
||||||
subType={subType}
|
|
||||||
siteKey={clientId}
|
|
||||||
clientSecret={secret}
|
|
||||||
onChange={onSubmit}
|
|
||||||
clientId2={clientId2}
|
|
||||||
clientSecret2={secret2}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,6 +63,16 @@ export const CaptchaPreview = ({
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOk = (captchaType, captchaToken, secret) => {
|
||||||
|
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
|
||||||
|
setOpen(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Button
|
<Button
|
||||||
@ -156,20 +83,20 @@ export const CaptchaPreview = ({
|
|||||||
>
|
>
|
||||||
{i18next.t("general:Preview")}
|
{i18next.t("general:Preview")}
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<CaptchaModal
|
||||||
closable={false}
|
owner={owner}
|
||||||
maskClosable={false}
|
name={name}
|
||||||
destroyOnClose={true}
|
captchaType={captchaType}
|
||||||
title={i18next.t("general:Captcha")}
|
subType={subType}
|
||||||
visible={visible}
|
clientId={clientId}
|
||||||
okText={i18next.t("user:OK")}
|
clientId2={clientId2}
|
||||||
cancelText={i18next.t("user:Cancel")}
|
clientSecret={clientSecret}
|
||||||
onOk={handleOk}
|
clientSecret2={clientSecret2}
|
||||||
onCancel={handleCancel}
|
open={open}
|
||||||
width={348}
|
onOk={onOk}
|
||||||
>
|
onCancel={onCancel}
|
||||||
{renderCheck()}
|
canCancel={true}
|
||||||
</Modal>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "Token läuft ab - Tooltip",
|
"Token expire - Tooltip": "Token läuft ab - Tooltip",
|
||||||
"Token format": "Token-Format",
|
"Token format": "Token-Format",
|
||||||
"Token format - Tooltip": "Token-Format - Tooltip",
|
"Token format - Tooltip": "Token-Format - Tooltip",
|
||||||
"rule": "rule"
|
"Rule": "Rule",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Bitgröße",
|
"Bit size": "Bitgröße",
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip",
|
"Token format - Tooltip": "Token format - Tooltip",
|
||||||
"rule": "rule"
|
"Rule": "Rule",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Bit size",
|
"Bit size": "Bit size",
|
||||||
|
@ -45,7 +45,9 @@
|
|||||||
"Token expire - Tooltip": "Expiración del Token - Tooltip",
|
"Token expire - Tooltip": "Expiración del Token - Tooltip",
|
||||||
"Token format": "Formato del Token",
|
"Token format": "Formato del Token",
|
||||||
"Token format - Tooltip": "Formato del Token - Tooltip",
|
"Token format - Tooltip": "Formato del Token - Tooltip",
|
||||||
"rule": "rule"
|
"Rule": "Rule",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Tamaño del Bit",
|
"Bit size": "Tamaño del Bit",
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
|
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
|
||||||
"Token format": "Format du jeton",
|
"Token format": "Format du jeton",
|
||||||
"Token format - Tooltip": "Format du jeton - infobulle",
|
"Token format - Tooltip": "Format du jeton - infobulle",
|
||||||
"rule": "rule"
|
"Rule": "Rule",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Taille du bit",
|
"Bit size": "Taille du bit",
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
|
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
|
||||||
"Token format": "トークンのフォーマット",
|
"Token format": "トークンのフォーマット",
|
||||||
"Token format - Tooltip": "トークンフォーマット - ツールチップ",
|
"Token format - Tooltip": "トークンフォーマット - ツールチップ",
|
||||||
"rule": "rule"
|
"Rule": "Rule",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "ビットサイズ",
|
"Bit size": "ビットサイズ",
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip",
|
"Token format - Tooltip": "Token format - Tooltip",
|
||||||
"rule": "rule"
|
"Rule": "Rule",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Bit size",
|
"Bit size": "Bit size",
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "Истек токен - Подсказка",
|
"Token expire - Tooltip": "Истек токен - Подсказка",
|
||||||
"Token format": "Формат токена",
|
"Token format": "Формат токена",
|
||||||
"Token format - Tooltip": "Формат токена - Подсказка",
|
"Token format - Tooltip": "Формат токена - Подсказка",
|
||||||
"rule": "правило"
|
"Rule": "правило",
|
||||||
|
"None": "None",
|
||||||
|
"Always": "Always"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Размер бита",
|
"Bit size": "Размер бита",
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
"Token expire - Tooltip": "Access Token过期时间",
|
"Token expire - Tooltip": "Access Token过期时间",
|
||||||
"Token format": "Access Token格式",
|
"Token format": "Access Token格式",
|
||||||
"Token format - Tooltip": "Access Token格式",
|
"Token format - Tooltip": "Access Token格式",
|
||||||
"rule": "规则"
|
"Rule": "规则",
|
||||||
|
"None": "关闭",
|
||||||
|
"Always": "始终开启"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "位大小",
|
"Bit size": "位大小",
|
||||||
|
Reference in New Issue
Block a user