imeeting/frontend/src/pages/auth/reset-password/index.tsx

152 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import {Alert, Button, Form, Input, Typography, message} from "antd";
import { LockOutlined, LogoutOutlined } from "@ant-design/icons";
import {useEffect, useMemo, useState} from "react";
import { useNavigate } from "react-router-dom";
import { getCurrentUser, updateMyPassword } from "@/api";
import {fetchPublicPasswordPolicy, type PasswordPolicyPublic} from "@/api/auth";
import {buildPasswordPolicyValidator, buildPolicyHints} from "@/utils/password";
import AuthShell from "../components/AuthShell";
const {Text} = Typography;
type ResetPasswordFormValues = {
oldPassword: string;
newPassword: string;
confirmPassword: string;
};
const RESET_HIGHLIGHTS = [
"首次登录必须完成密码更新,避免继续使用初始凭证。",
"修改成功后保持当前登录态,无需再次验证。",
"若不是本人操作,请立即退出并联系管理员。"
];
export default function ResetPassword() {
const [loading, setLoading] = useState(false);
const [policy, setPolicy] = useState<PasswordPolicyPublic | null>(null);
const [username, setUsername] = useState<string>();
const navigate = useNavigate();
const [form] = Form.useForm<ResetPasswordFormValues>();
const policyHints = useMemo(() => buildPolicyHints(policy), [policy]);
useEffect(() => {
const init = async () => {
const [profileResult, policyResult] = await Promise.allSettled([getCurrentUser(), fetchPublicPasswordPolicy()]);
if (profileResult.status === "fulfilled") {
setUsername(profileResult.value.username);
}
setPolicy(policyResult.status === "fulfilled" ? policyResult.value : null);
};
void init();
}, []);
const goToLogin = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
localStorage.removeItem("displayName");
localStorage.removeItem("username");
localStorage.removeItem("availableTenants");
localStorage.removeItem("activeTenantId");
sessionStorage.removeItem("userProfile");
navigate("/login", { replace: true });
};
const onFinish = async (values: ResetPasswordFormValues) => {
setLoading(true);
try {
await updateMyPassword({
oldPassword: values.oldPassword,
newPassword: values.newPassword
});
const profile = await getCurrentUser();
sessionStorage.setItem("userProfile", JSON.stringify(profile));
window.dispatchEvent(new Event("user-profile-updated"));
message.success("密码已更新");
navigate("/", { replace: true });
} finally {
setLoading(false);
}
};
return (
<AuthShell
eyebrow="首次登录校验"
asideTitle="先完成一次安全换密,再进入系统"
asideDescription="初始密码只用于验证当前账号的合法持有者。设置新的个人密码后,系统会继续保持登录并同步最新的账户资料。"
asideHighlights={RESET_HIGHLIGHTS}
title="修改初始密码"
subtitle="请输入当前密码并设置一个符合策略的新密码。"
footer={<Text type="secondary">退</Text>}
>
<Alert
showIcon
type="info"
message="密码更新后将直接进入系统首页"
description="新密码提交成功后立即生效,旧密码会同时失效。"
className="auth-form__notice"
/>
<Form form={form} layout="vertical" onFinish={onFinish} requiredMark={false} className="auth-form">
<Form.Item label="当前密码" name="oldPassword" rules={[{required: true, message: "请输入当前密码"}]}>
<Input.Password size="large" prefix={<LockOutlined/>} autoComplete="current-password"/>
</Form.Item>
{policyHints.length > 0 ? (
<div className="auth-form__policy-hints">
<span className="auth-form__policy-hints-title"></span>
<ul className="auth-form__policy-list">
{policyHints.map((hint) => (
<li key={hint}>{hint}</li>
))}
</ul>
</div>
) : null}
<Form.Item
label="新密码"
name="newPassword"
validateFirst
rules={[
{required: true, min: 6, message: "新密码至少 6 位"},
{validator: buildPasswordPolicyValidator(policy, () => username)}
]}
>
<Input.Password size="large" prefix={<LockOutlined/>} autoComplete="new-password"/>
</Form.Item>
<Form.Item
label="确认新密码"
name="confirmPassword"
dependencies={["newPassword"]}
rules={[
{required: true, message: "请再次输入新密码"},
({getFieldValue}) => ({
validator(_, value) {
if (!value || getFieldValue("newPassword") === value) {
return Promise.resolve();
}
return Promise.reject(new Error("两次输入的新密码不一致"));
}
})
]}
>
<Input.Password size="large" prefix={<LockOutlined/>} autoComplete="new-password"/>
</Form.Item>
<div className="auth-form__actions">
<Button type="primary" htmlType="submit" block size="large" loading={loading}>
</Button>
<Button block size="large" icon={<LogoutOutlined/>} onClick={goToLogin}>
退
</Button>
</div>
</Form>
</AuthShell>
);
}