From ea68e6c2dc13894a8edaa8b21601954c12c80f5f Mon Sep 17 00:00:00 2001 From: DacongDA Date: Sat, 19 Jul 2025 01:12:07 +0800 Subject: [PATCH] feat: support inline-captcha in login page (#3970) --- web/src/auth/LoginPage.js | 44 +++++++++----- web/src/common/modal/CaptchaModal.js | 90 ++++++++++++++++++++-------- web/src/table/SigninTable.js | 10 ++++ 3 files changed, 104 insertions(+), 40 deletions(-) diff --git a/web/src/auth/LoginPage.js b/web/src/auth/LoginPage.js index 81d8a3ce..d215f864 100644 --- a/web/src/auth/LoginPage.js +++ b/web/src/auth/LoginPage.js @@ -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) } ); @@ -819,6 +827,8 @@ class LoginPage extends React.Component { ); + } else if (signinItem.name === "Captcha" && signinItem.rule === "inline") { + return this.renderCaptchaModal(application, true); } else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) { return (
@@ -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; diff --git a/web/src/common/modal/CaptchaModal.js b/web/src/common/modal/CaptchaModal.js index 16dab111..2c20e297 100644 --- a/web/src/common/modal/CaptchaModal.js +++ b/web/src/common/modal/CaptchaModal.js @@ -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 ( + + + } + placeholder={i18next.t("general:Captcha")} + onChange={(e) => onChange(e.target.value)} + /> + + + captcha + + + ); + } return (
@@ -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 ( - -
- { - renderCaptcha() - } -
-
- ); + if (noModal) { + return renderCaptcha(); + + } else { + return ( + +
+ { + renderCaptcha() + } +
+
+ ); + } }; export const CaptchaRule = { diff --git a/web/src/table/SigninTable.js b/web/src/table/SigninTable.js index 3a938ddb..144bb2d1 100644 --- a/web/src/table/SigninTable.js +++ b/web/src/table/SigninTable.js @@ -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; }