feat: show Organization.PasswordOptions in login UI (#3913)

This commit is contained in:
DacongDA
2025-06-28 22:13:00 +08:00
committed by GitHub
parent f4ad2b4034
commit 568c0e2c3d
4 changed files with 133 additions and 61 deletions

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Form, Input, Row, Select, Steps} from "antd"; import {Button, Col, Form, Input, Popover, Row, Select, Steps} from "antd";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Util from "./Util"; import * as Util from "./Util";
@ -385,6 +385,7 @@ class ForgetPage extends React.Component {
}, },
]} ]}
/> />
<Popover placement="right" content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
<Form.Item <Form.Item
name="newPassword" name="newPassword"
hidden={this.state.current !== 2} hidden={this.state.current !== 2}
@ -407,8 +408,25 @@ class ForgetPage extends React.Component {
<Input.Password <Input.Password
prefix={<LockOutlined />} prefix={<LockOutlined />}
placeholder={i18next.t("general:Password")} placeholder={i18next.t("general:Password")}
onChange={(e) => {
this.setState({
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value),
});
}}
onFocus={() => {
this.setState({
passwordPopoverOpen: true,
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("newPassword") ?? ""),
});
}}
onBlur={() => {
this.setState({
passwordPopoverOpen: false,
});
}}
/> />
</Form.Item> </Form.Item>
</Popover>
<Form.Item <Form.Item
name="confirm" name="confirm"
dependencies={["newPassword"]} dependencies={["newPassword"]}

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Form, Input, Radio, Result, Row, Select, message} from "antd"; import {Button, Form, Input, Popover, Radio, Result, Row, Select, message} from "antd";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton"; import * as ProviderButton from "./ProviderButton";
@ -607,6 +607,7 @@ class SignupPage extends React.Component {
} }
} else if (signupItem.name === "Password") { } else if (signupItem.name === "Password") {
return ( return (
<Popover placement="right" content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
<Form.Item <Form.Item
name="password" name="password"
className="signup-password" className="signup-password"
@ -627,8 +628,24 @@ class SignupPage extends React.Component {
]} ]}
hasFeedback hasFeedback
> >
<Input.Password className="signup-password-input" placeholder={signupItem.placeholder} /> <Input.Password className="signup-password-input" placeholder={signupItem.placeholder} onChange={(e) => {
this.setState({
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value),
});
}}
onFocus={() => {
this.setState({
passwordPopoverOpen: true,
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("password") ?? ""),
});
}}
onBlur={() => {
this.setState({
passwordPopoverOpen: false,
});
}} />
</Form.Item> </Form.Item>
</Popover>
); );
} else if (signupItem.name === "Confirm password") { } else if (signupItem.name === "Confirm password") {
return ( return (

View File

@ -13,6 +13,8 @@
// limitations under the License. // limitations under the License.
import i18next from "i18next"; import i18next from "i18next";
import React from "react";
import {CheckCircleTwoTone, CloseCircleTwoTone} from "@ant-design/icons";
function isValidOption_AtLeast6(password) { function isValidOption_AtLeast6(password) {
if (password.length < 6) { if (password.length < 6) {
@ -52,6 +54,33 @@ function isValidOption_NoRepeat(password) {
return ""; return "";
} }
const checkers = {
AtLeast6: isValidOption_AtLeast6,
AtLeast8: isValidOption_AtLeast8,
Aa123: isValidOption_Aa123,
SpecialChar: isValidOption_SpecialChar,
NoRepeat: isValidOption_NoRepeat,
};
function getOptionDescription(option, password) {
switch (option) {
case "AtLeast6": return i18next.t("user:The password must have at least 6 characters");
case "AtLeast8": return i18next.t("user:The password must have at least 8 characters");
case "Aa123": return i18next.t("user:The password must contain at least one uppercase letter, one lowercase letter and one digit");
case "SpecialChar": return i18next.t("user:The password must contain at least one special character");
case "NoRepeat": return i18next.t("user:The password must not contain any repeated characters");
}
}
export function renderPasswordPopover(options, password) {
return <div style={{width: 240}} >
{options.map((option, idx) => {
return <div key={idx}>{checkers[option](password) === "" ? <CheckCircleTwoTone twoToneColor={"#52c41a"} /> :
<CloseCircleTwoTone twoToneColor={"#ff4d4f"} />} {getOptionDescription(option, password)}</div>;
})}
</div>;
}
export function checkPasswordComplexity(password, options) { export function checkPasswordComplexity(password, options) {
if (password.length === 0) { if (password.length === 0) {
return i18next.t("login:Please input your password!"); return i18next.t("login:Please input your password!");
@ -61,14 +90,6 @@ export function checkPasswordComplexity(password, options) {
return ""; return "";
} }
const checkers = {
AtLeast6: isValidOption_AtLeast6,
AtLeast8: isValidOption_AtLeast8,
Aa123: isValidOption_Aa123,
SpecialChar: isValidOption_SpecialChar,
NoRepeat: isValidOption_NoRepeat,
};
for (const option of options) { for (const option of options) {
const checkerFunc = checkers[option]; const checkerFunc = checkers[option];
if (checkerFunc) { if (checkerFunc) {

View File

@ -12,7 +12,7 @@
// 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, Col, Input, Modal, Popover, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
import * as UserBackend from "../../backend/UserBackend"; import * as UserBackend from "../../backend/UserBackend";
@ -35,6 +35,8 @@ export const PasswordModal = (props) => {
const [rePasswordValid, setRePasswordValid] = React.useState(false); const [rePasswordValid, setRePasswordValid] = React.useState(false);
const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState(""); const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState("");
const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState(""); const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState("");
const [passwordPopoverOpen, setPasswordPopoverOpen] = React.useState(false);
const [passwordPopover, setPasswordPopover] = React.useState();
React.useEffect(() => { React.useEffect(() => {
if (organization) { if (organization) {
@ -130,12 +132,26 @@ export const PasswordModal = (props) => {
</Row> </Row>
) : null} ) : null}
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>
<Popover placement="right" content={passwordPopover} open={passwordPopoverOpen}>
<Input.Password <Input.Password
addonBefore={i18next.t("user:New Password")} addonBefore={i18next.t("user:New Password")}
placeholder={i18next.t("user:input password")} placeholder={i18next.t("user:input password")}
onChange={(e) => {handleNewPassword(e.target.value);}} onChange={(e) => {
handleNewPassword(e.target.value);
setPasswordPopoverOpen(true);
setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, e.target.value));
}}
onFocus={() => {
setPasswordPopoverOpen(true);
setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, newPassword));
}}
onBlur={() => {
setPasswordPopoverOpen(false);
}}
status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined} status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined}
/> />
</Popover>
</Row> </Row>
{!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>} {!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>}
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>