mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-01 18:40:18 +08:00
feat: show Organization.PasswordOptions in login UI (#3913)
This commit is contained in:
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
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 ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as Util from "./Util";
|
||||
@ -385,30 +385,48 @@ class ForgetPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="newPassword"
|
||||
hidden={this.state.current !== 2}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validateTrigger: "onChange",
|
||||
validator: (rule, value) => {
|
||||
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
||||
if (errorMsg === "") {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(errorMsg);
|
||||
}
|
||||
<Popover placement="right" content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
|
||||
<Form.Item
|
||||
name="newPassword"
|
||||
hidden={this.state.current !== 2}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validateTrigger: "onChange",
|
||||
validator: (rule, value) => {
|
||||
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
||||
if (errorMsg === "") {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(errorMsg);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={i18next.t("general:Password")}
|
||||
/>
|
||||
</Form.Item>
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
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>
|
||||
</Popover>
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
dependencies={["newPassword"]}
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
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 AuthBackend from "./AuthBackend";
|
||||
import * as ProviderButton from "./ProviderButton";
|
||||
@ -607,28 +607,45 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
} else if (signupItem.name === "Password") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="password"
|
||||
className="signup-password"
|
||||
label={signupItem.label ? signupItem.label : i18next.t("general:Password")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
validateTrigger: "onChange",
|
||||
validator: (rule, value) => {
|
||||
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
||||
if (errorMsg === "") {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(errorMsg);
|
||||
}
|
||||
<Popover placement="right" content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
|
||||
<Form.Item
|
||||
name="password"
|
||||
className="signup-password"
|
||||
label={signupItem.label ? signupItem.label : i18next.t("general:Password")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
validateTrigger: "onChange",
|
||||
validator: (rule, value) => {
|
||||
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
|
||||
if (errorMsg === "") {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(errorMsg);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password className="signup-password-input" placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<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>
|
||||
</Popover>
|
||||
);
|
||||
} else if (signupItem.name === "Confirm password") {
|
||||
return (
|
||||
|
@ -13,6 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
import {CheckCircleTwoTone, CloseCircleTwoTone} from "@ant-design/icons";
|
||||
|
||||
function isValidOption_AtLeast6(password) {
|
||||
if (password.length < 6) {
|
||||
@ -52,6 +54,33 @@ function isValidOption_NoRepeat(password) {
|
||||
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) {
|
||||
if (password.length === 0) {
|
||||
return i18next.t("login:Please input your password!");
|
||||
@ -61,14 +90,6 @@ export function checkPasswordComplexity(password, options) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const checkers = {
|
||||
AtLeast6: isValidOption_AtLeast6,
|
||||
AtLeast8: isValidOption_AtLeast8,
|
||||
Aa123: isValidOption_Aa123,
|
||||
SpecialChar: isValidOption_SpecialChar,
|
||||
NoRepeat: isValidOption_NoRepeat,
|
||||
};
|
||||
|
||||
for (const option of options) {
|
||||
const checkerFunc = checkers[option];
|
||||
if (checkerFunc) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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 React from "react";
|
||||
import * as UserBackend from "../../backend/UserBackend";
|
||||
@ -35,6 +35,8 @@ export const PasswordModal = (props) => {
|
||||
const [rePasswordValid, setRePasswordValid] = React.useState(false);
|
||||
const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState("");
|
||||
const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState("");
|
||||
const [passwordPopoverOpen, setPasswordPopoverOpen] = React.useState(false);
|
||||
const [passwordPopover, setPasswordPopover] = React.useState();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (organization) {
|
||||
@ -130,12 +132,26 @@ export const PasswordModal = (props) => {
|
||||
</Row>
|
||||
) : null}
|
||||
<Row style={{width: "100%", marginBottom: "20px"}}>
|
||||
<Input.Password
|
||||
addonBefore={i18next.t("user:New Password")}
|
||||
placeholder={i18next.t("user:input password")}
|
||||
onChange={(e) => {handleNewPassword(e.target.value);}}
|
||||
status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined}
|
||||
/>
|
||||
<Popover placement="right" content={passwordPopover} open={passwordPopoverOpen}>
|
||||
<Input.Password
|
||||
addonBefore={i18next.t("user:New Password")}
|
||||
placeholder={i18next.t("user:input password")}
|
||||
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}
|
||||
/>
|
||||
</Popover>
|
||||
</Row>
|
||||
{!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>}
|
||||
<Row style={{width: "100%", marginBottom: "20px"}}>
|
||||
|
Reference in New Issue
Block a user