diff --git a/web/src/auth/ForgetPage.js b/web/src/auth/ForgetPage.js
index d627d973..b49fe48d 100644
--- a/web/src/auth/ForgetPage.js
+++ b/web/src/auth/ForgetPage.js
@@ -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 {
},
]}
/>
-
{
- const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
- if (errorMsg === "") {
- return Promise.resolve();
- } else {
- return Promise.reject(errorMsg);
- }
+
+ {
+ const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
+ if (errorMsg === "") {
+ return Promise.resolve();
+ } else {
+ return Promise.reject(errorMsg);
+ }
+ },
},
- },
- ]}
- hasFeedback
- >
- }
- placeholder={i18next.t("general:Password")}
- />
-
+ ]}
+ hasFeedback
+ >
+ }
+ 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,
+ });
+ }}
+ />
+
+
{
- const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
- if (errorMsg === "") {
- return Promise.resolve();
- } else {
- return Promise.reject(errorMsg);
- }
+
+ {
+ const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
+ if (errorMsg === "") {
+ return Promise.resolve();
+ } else {
+ return Promise.reject(errorMsg);
+ }
+ },
},
- },
- ]}
- hasFeedback
- >
-
-
+ ]}
+ hasFeedback
+ >
+ {
+ 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,
+ });
+ }} />
+
+
);
} else if (signupItem.name === "Confirm password") {
return (
diff --git a/web/src/common/PasswordChecker.js b/web/src/common/PasswordChecker.js
index b458a04a..1a95cfdd 100644
--- a/web/src/common/PasswordChecker.js
+++ b/web/src/common/PasswordChecker.js
@@ -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
+ {options.map((option, idx) => {
+ return
{checkers[option](password) === "" ? :
+ } {getOptionDescription(option, password)}
;
+ })}
+
;
+}
+
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) {
diff --git a/web/src/common/modal/PasswordModal.js b/web/src/common/modal/PasswordModal.js
index 0bf68ce1..36570c2e 100644
--- a/web/src/common/modal/PasswordModal.js
+++ b/web/src/common/modal/PasswordModal.js
@@ -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) => {
) : null}
- {handleNewPassword(e.target.value);}}
- status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined}
- />
+
+ {
+ 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}
+ />
+
{!newPasswordValid && newPasswordErrorMessage && {newPasswordErrorMessage}
}