Bläddra i källkod

密保重置密码

charles_c 7 månader sedan
förälder
incheckning
d1d8faca04

+ 12 - 16
src/app/api/auth/register/route.js

@@ -8,23 +8,17 @@ export async function POST(request) {
   await dbConnect();
 
   try {
-    const { username, password, role } = await request.json();
-
-    let response;
-
-    // 检查所有必要字段是否都已提供
-    if (!username || !password) {
-      return NextResponse.json(
-        { error: "Username and password are required" },
-        { status: 400 }
-      );
-    }
+    const { username, password, securityQuestion, securityAnswer, role } =
+      await request.json();
 
     // 检查用户是否已存在
     const existingUser = await User.findOne({ username });
     if (existingUser) {
-      response = NextResponse.json(
-        { success: false, error: "用户已存在" },
+      const response = NextResponse.json(
+        {
+          success: false,
+          error: "用户已存在",
+        },
         { status: 400 }
       );
       return setCORSHeaders(response);
@@ -37,20 +31,22 @@ export async function POST(request) {
     const newUser = new User({
       username,
       password: hashedPassword,
+      securityQuestion,
+      securityAnswer,
       role: role || "user", // 如果没有提供角色,默认为"user"
     });
 
     await newUser.save();
 
-    response = NextResponse.json({
+    const response = NextResponse.json({
       success: true,
-      message: "User registered successfully",
+      message: "用户注册成功",
       status: 200,
     });
 
     return setCORSHeaders(response);
   } catch (error) {
-    console.log("error");
+    console.error("Registration error:", error);
     return handleError(error);
   }
 }

+ 48 - 0
src/app/api/auth/reset-password/route.js

@@ -0,0 +1,48 @@
+import { NextResponse } from "next/server";
+import bcrypt from "bcryptjs";
+import dbConnect from "../../../lib/dbConnect";
+import User from "../../../models/User";
+import { setCORSHeaders, handleError } from "../../../lib/apiUtils";
+
+export async function POST(request) {
+  await dbConnect();
+
+  try {
+    const { username, newPassword } = await request.json();
+
+    // 查找用户
+    const user = await User.findOne({ username });
+    if (!user) {
+      const response = NextResponse.json(
+        {
+          success: false,
+          error: "用户不存在",
+        },
+        { status: 400 }
+      );
+      return setCORSHeaders(response);
+    }
+
+    // 加密新密码
+    const hashedPassword = await bcrypt.hash(newPassword, 10);
+
+    // 更新用户密码
+    user.password = hashedPassword;
+    await user.save();
+
+    const response = NextResponse.json({
+      success: true,
+      message: "密码重置成功",
+    });
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("Password reset error:", error);
+    return handleError(error);
+  }
+}
+
+export async function OPTIONS() {
+  const response = new NextResponse(null, { status: 204 });
+  return setCORSHeaders(response);
+}

+ 58 - 0
src/app/api/auth/verify-security/route.js

@@ -0,0 +1,58 @@
+// pages/api/auth/reset-password.js
+
+import { NextResponse } from "next/server";
+import bcrypt from "bcryptjs";
+import dbConnect from "../../../lib/dbConnect";
+import User from "../../../models/User";
+import { setCORSHeaders, handleError } from "../../../lib/apiUtils";
+
+export async function POST(request) {
+  await dbConnect();
+
+  try {
+    const { username, securityQuestion, securityAnswer } = await request.json();
+
+    // 查找用户
+    const user = await User.findOne({ username });
+    if (!user) {
+      const response = NextResponse.json(
+        {
+          success: false,
+          error: "用户不存在",
+        },
+        { status: 400 }
+      );
+      return setCORSHeaders(response);
+    }
+
+    // 验证安全问题
+    if (
+      user.securityQuestion !== securityQuestion ||
+      user.securityAnswer !== securityAnswer
+    ) {
+      const response = NextResponse.json(
+        {
+          success: false,
+          error: "安全问题验证失败",
+        },
+        { status: 400 }
+      );
+      return setCORSHeaders(response);
+    }
+
+    const response = NextResponse.json({
+      success: true,
+      message: "密保问题验证通过",
+    });
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("Password reset error:", error);
+    return handleError(error);
+  }
+}
+
+export async function OPTIONS() {
+  const response = new NextResponse(null, { status: 204 });
+  return setCORSHeaders(response);
+}

+ 1 - 2
src/app/api/user/route.js

@@ -160,8 +160,7 @@ export async function PUT(request) {
     console.log("Update data:", updateData);
 
     if (updateData.password) {
-      const salt = await bcrypt.genSalt(10);
-      updateData.password = await bcrypt.hash(updateData.password, salt);
+      updateData.password = await bcrypt.hash(updateData.password, 10);
     }
 
     const updatedUser = await User.findByIdAndUpdate(id, updateData, {

+ 47 - 70
src/app/login/page.js

@@ -3,70 +3,52 @@ import { useState } from "react";
 import Head from "next/head";
 import Alert from "../ui/components/Alert";
 import { useRouter } from "next/navigation";
+import Link from "next/link";
 
-export default function AuthPage() {
+export default function LoginPage() {
   const router = useRouter();
-
-  const [isLogin, setIsLogin] = useState(true);
   const [username, setUsername] = useState("");
   const [password, setPassword] = useState("");
   const [alert, setAlert] = useState(null);
 
   const handleSubmit = async (e) => {
     e.preventDefault();
-    const values = { username, password };
 
     try {
-      const endpoint = isLogin ? "/api/auth/login" : "/api/auth/register";
-      const response = await fetch(endpoint, {
+      const response = await fetch("/api/auth/login", {
         method: "POST",
         headers: {
           "Content-Type": "application/json",
         },
-        body: JSON.stringify(values),
+        body: JSON.stringify({ username, password }),
       });
 
       const res = await response.json();
 
       if (res.success) {
-        if (isLogin) {
-          console.log("登录成功", res.user);
-          setAlert({ type: "success", message: "登录成功" });
-          localStorage.setItem("currentUser", JSON.stringify(res.user));
-          setTimeout(() => {
-            router.push("/");
-          }, 1000);
-        } else {
-          setAlert({ type: "success", message: "注册成功" });
-          setIsLogin(true);
-        }
+        setAlert({ type: "success", message: "登录成功" });
+        localStorage.setItem("currentUser", JSON.stringify(res.user));
+        setTimeout(() => {
+          router.push("/");
+        }, 1000);
       } else {
         setAlert({ type: "error", message: res.error });
-        if (isLogin) {
-          setTimeout(() => {
-            router.push("/login");
-          }, 2000);
-        }
       }
     } catch (error) {
-      console.error(
-        "Error during " + (isLogin ? "login" : "registration") + ":",
-        error
-      );
-      setAlert({ type: "error", message: "操作失败,请重试" });
+      console.error("Login error:", error);
+      setAlert({ type: "error", message: "登录失败,请重试" });
     }
   };
 
   return (
     <div className="min-h-screen bg-gray-100 flex flex-col justify-center px-4 sm:px-6 lg:px-8">
       <Head>
-        <title>{isLogin ? "登录" : "注册"}</title>
-        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <title>登录</title>
       </Head>
 
       <div className="w-full max-w-md mx-auto">
         <h2 className="mt-6 text-center text-2xl sm:text-3xl font-extrabold text-gray-900">
-          {isLogin ? "登录您的账户" : "创建新账户"}
+          登录您的账户
         </h2>
       </div>
 
@@ -80,17 +62,14 @@ export default function AuthPage() {
               >
                 用户名
               </label>
-              <div className="mt-1">
-                <input
-                  id="username"
-                  name="username"
-                  autoComplete="tel"
-                  required
-                  className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 text-base"
-                  value={username}
-                  onChange={(e) => setUsername(e.target.value)}
-                />
-              </div>
+              <input
+                id="username"
+                type="text"
+                required
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={username}
+                onChange={(e) => setUsername(e.target.value)}
+              />
             </div>
 
             <div>
@@ -100,28 +79,22 @@ export default function AuthPage() {
               >
                 密码
               </label>
-              <div className="mt-1">
-                <input
-                  id="password"
-                  name="password"
-                  type="password"
-                  autoComplete="current-password"
-                  required
-                  className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 text-base"
-                  value={password}
-                  onChange={(e) => setPassword(e.target.value)}
-                />
-              </div>
+              <input
+                id="password"
+                type="password"
+                required
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+              />
             </div>
 
-            <div>
-              <button
-                type="submit"
-                className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
-              >
-                {isLogin ? "登录" : "注册"}
-              </button>
-            </div>
+            <button
+              type="submit"
+              className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
+            >
+              登录
+            </button>
           </form>
 
           <div className="mt-6">
@@ -130,19 +103,23 @@ export default function AuthPage() {
                 <div className="w-full border-t border-gray-300" />
               </div>
               <div className="relative flex justify-center text-sm">
-                <span className="px-2 bg-white text-gray-500">
-                  {isLogin ? "还没有账户?" : "已经有账户?"}
-                </span>
+                <span className="px-2 bg-white text-gray-500">或者</span>
               </div>
             </div>
 
-            <div className="mt-6">
-              <button
-                onClick={() => setIsLogin(!isLogin)}
+            <div className="mt-6 space-y-4">
+              <Link
+                href="/register"
+                className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
+              >
+                创建新账户
+              </Link>
+              <Link
+                href="/reset-password"
                 className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
               >
-                {isLogin ? "创建新账户" : "登录已有账户"}
-              </button>
+                忘记密码?
+              </Link>
             </div>
           </div>
         </div>

+ 14 - 2
src/app/models/User.js

@@ -4,10 +4,22 @@ const ROLES = ["user", "admin"];
 
 const UserSchema = new mongoose.Schema(
   {
-    username: { type: String, required: true, unique: true },
-    password: { type: String, required: true },
+    username: {
+      type: String,
+      required: true,
+      unique: true,
+    },
+    password: {
+      type: String,
+      required: true,
+    },
     points: { type: Number, default: 0 },
     role: { type: String, enum: ROLES, default: "user" },
+    securityQuestion: {
+      type: String,
+      required: true,
+    },
+    securityAnswer: { type: String, required: true },
   },
   { timestamps: true }
 );

+ 217 - 0
src/app/register/page.js

@@ -0,0 +1,217 @@
+"use client";
+import { useState } from "react";
+import Head from "next/head";
+import Alert from "../ui/components/Alert";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+
+const SECURITY_QUESTIONS = [
+  "您的出生地是?",
+  "您的母亲的姓名是?",
+  "您的最喜欢的颜色是?",
+  "您的第一所学校名称是?",
+  "您的宠物名字是?",
+  "自定义问题",
+];
+
+export default function RegisterPage() {
+  const router = useRouter();
+  const [username, setUsername] = useState("");
+  const [password, setPassword] = useState("");
+  const [confirmPassword, setConfirmPassword] = useState("");
+  const [selectedQuestion, setSelectedQuestion] = useState(
+    SECURITY_QUESTIONS[0]
+  );
+  const [customQuestion, setCustomQuestion] = useState("");
+  const [securityAnswer, setSecurityAnswer] = useState("");
+  const [alert, setAlert] = useState(null);
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+
+    if (username.length < 3) {
+      setAlert({ type: "error", message: "用户名至少需要3个字符" });
+      return;
+    }
+
+    if (password.length < 6) {
+      setAlert({ type: "error", message: "密码至少需要6个字符" });
+      return;
+    }
+
+    if (password !== confirmPassword) {
+      setAlert({ type: "error", message: "两次输入的密码不一致" });
+      return;
+    }
+
+    try {
+      const response = await fetch("/api/auth/register", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({
+          username,
+          password,
+          securityQuestion:
+            selectedQuestion === "自定义问题"
+              ? customQuestion
+              : selectedQuestion,
+          securityAnswer,
+        }),
+      });
+
+      const res = await response.json();
+
+      if (res.success) {
+        setAlert({ type: "success", message: "注册成功" });
+        setTimeout(() => {
+          router.push("/login");
+        }, 1500);
+      } else {
+        setAlert({ type: "error", message: res.error });
+      }
+    } catch (error) {
+      console.error("Registration error:", error);
+      setAlert({ type: "error", message: "注册失败,请重试" });
+    }
+  };
+
+  return (
+    <div className="min-h-screen bg-gray-100 flex flex-col justify-center px-4 sm:px-6 lg:px-8">
+      <Head>
+        <title>注册</title>
+      </Head>
+
+      <div className="w-full max-w-md mx-auto">
+        <h2 className="mt-6 text-center text-2xl sm:text-3xl font-extrabold text-gray-900">
+          创建新账户
+        </h2>
+      </div>
+
+      <div className="mt-8 w-full max-w-md mx-auto">
+        <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
+          <form className="space-y-6" onSubmit={handleSubmit}>
+            <div>
+              <label className="block text-sm font-medium text-gray-700">
+                用户名
+              </label>
+              <input
+                type="text"
+                required
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={username}
+                onChange={(e) => setUsername(e.target.value)}
+              />
+            </div>
+
+            <div>
+              <label className="block text-sm font-medium text-gray-700">
+                密码
+              </label>
+              <input
+                type="password"
+                required
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+              />
+            </div>
+
+            <div>
+              <label className="block text-sm font-medium text-gray-700">
+                确认密码
+              </label>
+              <input
+                type="password"
+                required
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={confirmPassword}
+                onChange={(e) => setConfirmPassword(e.target.value)}
+              />
+            </div>
+
+            <div>
+              <label className="block text-sm font-medium text-gray-700">
+                密保问题
+              </label>
+              <select
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={selectedQuestion}
+                onChange={(e) => setSelectedQuestion(e.target.value)}
+              >
+                {SECURITY_QUESTIONS.map((question) => (
+                  <option key={question} value={question}>
+                    {question}
+                  </option>
+                ))}
+              </select>
+            </div>
+
+            {selectedQuestion === "自定义问题" && (
+              <div>
+                <label className="block text-sm font-medium text-gray-700">
+                  自定义问题内容
+                </label>
+                <input
+                  type="text"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                  value={customQuestion}
+                  onChange={(e) => setCustomQuestion(e.target.value)}
+                  required
+                />
+              </div>
+            )}
+
+            <div>
+              <label className="block text-sm font-medium text-gray-700">
+                密保答案
+              </label>
+              <input
+                type="text"
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                value={securityAnswer}
+                onChange={(e) => setSecurityAnswer(e.target.value)}
+                required
+              />
+            </div>
+
+            <button
+              type="submit"
+              className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
+            >
+              注册
+            </button>
+          </form>
+
+          <div className="mt-6">
+            <div className="relative">
+              <div className="absolute inset-0 flex items-center">
+                <div className="w-full border-t border-gray-300" />
+              </div>
+              <div className="relative flex justify-center text-sm">
+                <span className="px-2 bg-white text-gray-500">已有账户?</span>
+              </div>
+            </div>
+
+            <div className="mt-6">
+              <Link
+                href="/login"
+                className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
+              >
+                返回登录
+              </Link>
+            </div>
+          </div>
+        </div>
+      </div>
+      {alert && (
+        <Alert
+          message={alert.message}
+          type={alert.type}
+          onClose={() => setAlert(null)}
+        />
+      )}
+    </div>
+  );
+}

+ 266 - 0
src/app/reset-password/page.js

@@ -0,0 +1,266 @@
+"use client";
+import { useState } from "react";
+import Head from "next/head";
+import Alert from "../ui/components/Alert";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+
+const SECURITY_QUESTIONS = [
+  "您的出生地是?",
+  "您的母亲的姓名是?",
+  "您的最喜欢的颜色是?",
+  "您的第一所学校名称是?",
+  "您的宠物名字是?",
+  "自定义问题",
+];
+
+export default function ResetPasswordPage() {
+  const router = useRouter();
+
+  const [step, setStep] = useState(1);
+  const [username, setUsername] = useState("");
+  const [selectedQuestion, setSelectedQuestion] = useState(
+    SECURITY_QUESTIONS[0]
+  );
+  const [customQuestion, setCustomQuestion] = useState("");
+  const [securityAnswer, setSecurityAnswer] = useState("");
+  const [newPassword, setNewPassword] = useState("");
+  const [confirmPassword, setConfirmPassword] = useState("");
+  const [alert, setAlert] = useState(null);
+
+  const handleVerification = async (e) => {
+    e.preventDefault();
+
+    try {
+      const response = await fetch("/api/auth/verify-security", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({
+          username,
+          securityQuestion:
+            selectedQuestion === "自定义问题"
+              ? customQuestion.trim()
+              : selectedQuestion,
+          securityAnswer: securityAnswer.trim(),
+        }),
+      });
+
+      const data = await response.json();
+
+      if (data.success) {
+        setAlert({ type: "success", message: "验证成功" });
+        setStep(2);
+      } else {
+        setAlert({
+          type: "error",
+          message: data.message || "验证失败,请重试",
+        });
+      }
+    } catch (error) {
+      setAlert({ type: "error", message: "发生错误,请稍后重试" });
+    }
+  };
+
+  const handleResetPassword = async (e) => {
+    e.preventDefault();
+
+    if (newPassword.length < 6) {
+      setAlert({ type: "error", message: "密码至少需要6个字符" });
+      return;
+    }
+
+    if (newPassword !== confirmPassword) {
+      setAlert({ type: "error", message: "两次输入的密码不一致" });
+      return;
+    }
+
+    try {
+      const response = await fetch("/api/auth/reset-password", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({
+          username,
+          newPassword,
+        }),
+      });
+
+      const data = await response.json();
+
+      if (data.success) {
+        setAlert({ type: "success", message: "密码重置成功" });
+        setTimeout(() => router.push("/login"), 1500);
+      } else {
+        setAlert({
+          type: "error",
+          message: data.message || "密码重置失败,请重试",
+        });
+      }
+    } catch (error) {
+      setAlert({ type: "error", message: "发生错误,请稍后重试" });
+    }
+  };
+
+  return (
+    <div className="min-h-screen bg-gray-100 flex flex-col justify-center px-4 sm:px-6 lg:px-8">
+      <Head>
+        <title>重置密码</title>
+      </Head>
+
+      <div className="w-full max-w-md mx-auto">
+        <h2 className="mt-6 text-center text-2xl sm:text-3xl font-extrabold text-gray-900">
+          重置密码
+        </h2>
+      </div>
+
+      <div className="mt-8 w-full max-w-md mx-auto">
+        <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
+          {step === 1 ? (
+            <form className="space-y-6" onSubmit={handleVerification}>
+              <div>
+                <label className="block text-sm font-medium text-gray-700">
+                  用户名
+                </label>
+                <input
+                  type="text"
+                  required
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                  value={username}
+                  onChange={(e) => setUsername(e.target.value)}
+                />
+              </div>
+
+              <div>
+                <label className="block text-sm font-medium text-gray-700">
+                  密保问题
+                </label>
+                <select
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                  value={selectedQuestion}
+                  onChange={(e) => setSelectedQuestion(e.target.value)}
+                >
+                  {SECURITY_QUESTIONS.map((question) => (
+                    <option key={question} value={question}>
+                      {question}
+                    </option>
+                  ))}
+                </select>
+              </div>
+
+              {selectedQuestion === "自定义问题" && (
+                <div>
+                  <label className="block text-sm font-medium text-gray-700">
+                    自定义问题内容
+                  </label>
+                  <input
+                    type="text"
+                    className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                    value={customQuestion}
+                    onChange={(e) => setCustomQuestion(e.target.value)}
+                    required
+                  />
+                </div>
+              )}
+
+              <div>
+                <label className="block text-sm font-medium text-gray-700">
+                  密保答案
+                </label>
+                <input
+                  type="text"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                  value={securityAnswer}
+                  onChange={(e) => setSecurityAnswer(e.target.value)}
+                  required
+                />
+              </div>
+
+              <button
+                type="submit"
+                className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
+              >
+                验证身份
+              </button>
+            </form>
+          ) : (
+            <form className="space-y-6" onSubmit={handleResetPassword}>
+              <div>
+                <label className="block text-sm font-medium text-gray-700">
+                  新密码
+                </label>
+                <input
+                  type="password"
+                  required
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                  value={newPassword}
+                  onChange={(e) => setNewPassword(e.target.value)}
+                />
+              </div>
+
+              <div>
+                <label className="block text-sm font-medium text-gray-700">
+                  确认新密码
+                </label>
+                <input
+                  type="password"
+                  required
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
+                  value={confirmPassword}
+                  onChange={(e) => setConfirmPassword(e.target.value)}
+                />
+              </div>
+
+              <button
+                type="submit"
+                className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
+              >
+                重置密码
+              </button>
+            </form>
+          )}
+
+          <div className="mt-6">
+            <div className="relative">
+              <div className="absolute inset-0 flex items-center">
+                <div className="w-full border-t border-gray-300" />
+              </div>
+              <div className="relative flex justify-center text-sm">
+                <span className="px-2 bg-white text-gray-500">
+                  {step === 1 ? "记起密码了?" : "需要重新验证?"}
+                </span>
+              </div>
+            </div>
+
+            <div className="mt-6">
+              {step === 1 ? (
+                <Link
+                  href="/login"
+                  className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
+                >
+                  返回登录
+                </Link>
+              ) : (
+                <button
+                  onClick={() => setStep(1)}
+                  className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
+                >
+                  返回验证
+                </button>
+              )}
+            </div>
+          </div>
+        </div>
+      </div>
+      {alert && (
+        <Alert
+          message={alert.message}
+          type={alert.type}
+          onClose={() => setAlert(null)}
+        />
+      )}
+    </div>
+  );
+}