feat: support inline-captcha in login page (#3970)

This commit is contained in:
DacongDA
2025-07-19 01:12:07 +08:00
committed by GitHub
parent 7aa0b2e63f
commit ea68e6c2dc
3 changed files with 104 additions and 40 deletions

View File

@@ -437,18 +437,26 @@ class LoginPage extends React.Component {
values["password"] = passwordCipher;
}
const captchaRule = this.getCaptchaRule(this.getApplicationObj());
if (captchaRule === CaptchaRule.Always) {
this.setState({
openCaptchaModal: true,
values: values,
});
return;
} else if (captchaRule === CaptchaRule.Dynamic) {
this.checkCaptchaStatus(values);
return;
} else if (captchaRule === CaptchaRule.InternetOnly) {
this.checkCaptchaStatus(values);
return;
const application = this.getApplicationObj();
const noModal = application?.signinItems.map(signinItem => signinItem.name === "Captcha" && signinItem.rule === "inline").includes(true);
if (!noModal) {
if (captchaRule === CaptchaRule.Always) {
this.setState({
openCaptchaModal: true,
values: values,
});
return;
} else if (captchaRule === CaptchaRule.Dynamic) {
this.checkCaptchaStatus(values);
return;
} else if (captchaRule === CaptchaRule.InternetOnly) {
this.checkCaptchaStatus(values);
return;
}
} else {
values["captchaType"] = this.state?.captchaValues?.captchaType;
values["captchaToken"] = this.state?.captchaValues?.captchaToken;
values["clientSecret"] = this.state?.captchaValues?.clientSecret;
}
}
this.login(values);
@@ -775,7 +783,7 @@ class LoginPage extends React.Component {
</>
}
{
this.renderCaptchaModal(application)
application?.signinItems.map(signinItem => signinItem.name === "Captcha" && signinItem.rule === "inline").includes(true) ? null : this.renderCaptchaModal(application, false)
}
</Form.Item>
);
@@ -819,6 +827,8 @@ class LoginPage extends React.Component {
</Form.Item>
</div>
);
} else if (signinItem.name === "Captcha" && signinItem.rule === "inline") {
return this.renderCaptchaModal(application, true);
} else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
return (
<div key={resultItemKey} dangerouslySetInnerHTML={{__html: signinItem.customCss}} />
@@ -964,7 +974,7 @@ class LoginPage extends React.Component {
});
}
renderCaptchaModal(application) {
renderCaptchaModal(application, noModal) {
if (this.getCaptchaRule(this.getApplicationObj()) === CaptchaRule.Never) {
return null;
}
@@ -993,6 +1003,12 @@ class LoginPage extends React.Component {
owner={provider.owner}
name={provider.name}
visible={this.state.openCaptchaModal}
noModal={noModal}
onUpdateToken={(captchaType, captchaToken, clientSecret) => {
this.setState({captchaValues: {
captchaType, captchaToken, clientSecret,
}});
}}
onOk={(captchaType, captchaToken, clientSecret) => {
const values = this.state.values;
values["captchaType"] = captchaType;

View File

@@ -20,7 +20,7 @@ import {CaptchaWidget} from "../CaptchaWidget";
import {SafetyOutlined} from "@ant-design/icons";
export const CaptchaModal = (props) => {
const {owner, name, visible, onOk, onCancel, isCurrentProvider} = props;
const {owner, name, visible, onOk, onUpdateToken, onCancel, isCurrentProvider, noModal} = props;
const [captchaType, setCaptchaType] = React.useState("none");
const [clientId, setClientId] = React.useState("");
@@ -36,16 +36,16 @@ export const CaptchaModal = (props) => {
const defaultInputRef = React.useRef(null);
useEffect(() => {
if (visible) {
if (visible || noModal) {
loadCaptcha();
} else {
handleCancel();
setOpen(false);
}
}, [visible]);
}, [visible, noModal]);
useEffect(() => {
if (captchaToken !== "" && captchaType !== "Default") {
if (captchaToken !== "" && captchaType !== "Default" && !noModal) {
handleOk();
}
}, [captchaToken]);
@@ -81,6 +81,36 @@ export const CaptchaModal = (props) => {
};
const renderDefaultCaptcha = () => {
if (noModal) {
return (
<Row style={{textAlign: "center"}}>
<Col
style={{flex: noModal ? "70%" : "100%"}}>
<Input
ref={defaultInputRef}
value={captchaToken}
prefix={<SafetyOutlined />}
placeholder={i18next.t("general:Captcha")}
onChange={(e) => onChange(e.target.value)}
/>
</Col>
<Col
style={{
flex: noModal ? "30%" : "100%",
}}
>
<img src={`data:image/png;base64,${captchaImg}`}
onClick={loadCaptcha}
style={{
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: "20px",
width: "100%",
}} alt="captcha" />
</Col>
</Row>
);
}
return (
<Col style={{textAlign: "center"}}>
<div style={{display: "inline-block"}}>
@@ -113,6 +143,9 @@ export const CaptchaModal = (props) => {
const onChange = (token) => {
setCaptchaToken(token);
if (noModal) {
onUpdateToken?.(captchaType, token, clientSecret);
}
};
const renderCaptcha = () => {
@@ -153,28 +186,33 @@ export const CaptchaModal = (props) => {
return null;
};
return (
<Modal
closable={true}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
open={open}
okText={i18next.t("general:OK")}
cancelText={i18next.t("general:Cancel")}
width={350}
footer={renderFooter()}
onCancel={handleCancel}
afterClose={handleCancel}
onOk={handleOk}
>
<div style={{marginTop: "20px", marginBottom: "50px"}}>
{
renderCaptcha()
}
</div>
</Modal>
);
if (noModal) {
return renderCaptcha();
} else {
return (
<Modal
closable={true}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
open={open}
okText={i18next.t("general:OK")}
cancelText={i18next.t("general:Cancel")}
width={350}
footer={renderFooter()}
onCancel={handleCancel}
afterClose={handleCancel}
onOk={handleOk}
>
<div style={{marginTop: "20px", marginBottom: "50px"}}>
{
renderCaptcha()
}
</div>
</Modal>
);
}
};
export const CaptchaRule = {

View File

@@ -49,6 +49,9 @@ class SigninTable extends React.Component {
updateField(table, index, key, value) {
table[index][key] = value;
if (key === "name" && value === "Captcha") {
table[index]["rule"] = "pop up";
}
this.updateTable(table);
}
@@ -114,6 +117,7 @@ class SigninTable extends React.Component {
{name: "Forgot password?", displayName: i18next.t("login:Forgot password?")},
{name: "Login button", displayName: i18next.t("login:Signin button")},
{name: "Signup link", displayName: i18next.t("general:Signup link")},
{name: "Captcha", displayName: i18next.t("general:Captcha")},
];
const getItemDisplayName = (text) => {
@@ -249,6 +253,12 @@ class SigninTable extends React.Component {
{id: "small", name: i18next.t("application:Small icon")},
];
}
if (record.name === "Captcha") {
options = [
{id: "pop up", name: i18next.t("application:Pop up")},
{id: "inline", name: i18next.t("application:Inline")},
];
}
if (options.length === 0) {
return null;
}