首页 > web前端 > js教程 > 正文

如何使用 Mern stack 和 cloudinary 云存储忘记密码?

王林
发布: 2024-07-17 18:20:36
原创
1030 人浏览过

How to forgot password using Mern stack with cloudinary cloud storage?

简介
在这篇博文中,我们将逐步介绍使用 MERN 堆栈(MongoDB、Express.js、React、Node.js)和 Next.js 设置“忘记密码”功能的过程。我们将涵盖从使用 Node.js 和 Express 设置后端到使用 Next.js 构建前端的所有内容。最后,您将全面了解如何实现安全且用户友好的密码重置功能。

先决条件 JavaScript 和 React 的基础知识。
了解 Node.js 和 Express.js。
熟悉 MongoDB 和 Mongoose。
Next.js 经验。
第 1 步:设置后端
1.1.初始化 Node.js 项目
为您的项目创建一个新目录并初始化 Node.js 项目:

mkdir mern-password-reset
cd mern-password-reset
npm init -y
登录后复制

1.2。安装依赖项 安装必要的依赖项:

npm install express mongoose bcryptjs jsonwebtoken nodemailer dotenv 
登录后复制
express:Node.js 的 Web 框架。

mongoose:MongoDB 对象建模工具。
bcryptjs:哈希密码的库。
jsonwebtoken:用于生成和验证 JWT 令牌。
nodemailer:用于发送电子邮件。
dotenv:管理环境变量。

1.3。设置环境变量 在项目的根目录中创建一个 .env 文件来存储环境变量:

PORT=5000
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret
EMAIL_USER=your_email@example.com
EMAIL_PASS=your_email_password
登录后复制

1.4。设置 Express 服务器 创建一个 server.js 文件并设置基本的 Express 服务器:

const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');

const app = express();

dotenv.config();

const port = process.env.PORT || 5001;

//middlewares
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());

mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

登录后复制

1.5。创建用户模型 创建一个 models 目录并为 User 模型添加 User.js 文件:

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema(
  {
    firstName: { type: String, require: true },
    lastName: { type: String, require: true },
    email: { type: String, require: true, unique: true },
    password: { type: String, require: true },
    mobileNo: { type: String, require: true },
    image: { type: String, default: "" },
    dob: { type: String, require: true },
  },
  { timestamps: true }
);

const User = mongoose.model("user", userSchema);

module.exports = User;
登录后复制
1.6。创建身份验证路由

创建一个routes目录并添加一个auth.js文件用于身份验证路由:

const express = require("express");
const {
  register,
  login,
  requestPasswordResetController,
  resetPasswordController,
} = require("../controllers/user");

const router = express.Router();

router.post("/login", login);
router.post("/requestPasswordReset", requestPasswordResetController);
router.post("/resetPassword", resetPasswordController);

module.exports = router;
登录后复制

1.7。添加 Cloudinary 用于上传个人资料图片 安装 Cloudinary 必要的依赖项:

npm install cloudinary multer multer-storage-cloudinary
登录后复制
    cloudinary:基于云的图像和视频管理。
  • multer:用于处理多部分/表单数据的中间件。
  • multer-storage-cloudinary:multer 和 cloudinary 之间的集成。

1.8。配置 Cloudinary 创建config目录并添加cloudinaryConfig.js文件:

const cloudinary = require('cloudinary').v2;
const { CloudinaryStorage } = require('multer-storage-cloudinary');

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

const storage = new CloudinaryStorage({
  cloudinary,
  params: {
    folder: 'profile_pics',
    allowedFormats: ['jpg', 'png'],
  },
});

module.exports = { cloudinary, storage };
登录后复制
更新您的 .env 文件以包含 Cloudinary 配置:


CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
登录后复制

1.9。定义令牌架构 创建 models 目录并添加 tokenSchema.js 文件:

const mongoose = require("mongoose");

const tokenSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, require: true, ref: "User" },
  token: { type: String, require: true },
  createdAt: { type: Date, default: Date.now, expires: 3600 },
});

const Token = mongoose.model("Token", tokenSchema);

module.exports = Token;
登录后复制

1.10。实施身份验证服务 创建 services 目录并添加 authService.js 文件:

const Token = require("../models/tokenSchema");
const User = require("../models/userSchema");
const crypto = require("crypto");
const bcrypt = require("bcryptjs");
const sendEmail = require("../config/sendEmail");

const requestPasswordReset = async (email) => {
  try {
    const user = await User.findOne({ email });

    if (!user) {
      throw new Error("User doesn't exist");
    }
    const name = `${user.firstName} ${user.lastName}`;

    let token = await Token.findOne({ userId: user._id });
    if (token) await token.deleteOne();
    let resetToken = crypto.randomBytes(32).toString("hex");
    const salt = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(resetToken, salt);

    const newToken = await Token.create({
      userId: user._id,
      token: hash,
      createdAt: Date.now(),
    });

    const link = `${process.env.CLIENT_URL}/forgot-password/reset?token=${resetToken}&id=${user._id}`;
    const htmlContent = `
      <h1>Hi, ${name}</h1>
      <p>You requested to reset your password.</p>
      <p>Please, click the link below to reset your password.</p>
      <a href="${link}">Reset Password</a>`;
    sendEmail(`${email}`, "Request to reset password", htmlContent);
    return link;
  } catch (error) {
    console.log(error.message);
  }
};

const resetPassword = async (userId, token, password) => {
  try {
    let passwordResetToken = await Token.findOne({ userId });
    if (!passwordResetToken) {
      throw new Error("Invalid or expired password reset token");
    }

    const isValid = await bcrypt.compare(token, passwordResetToken.token);

    if (!isValid) {
      throw new Error("Invalid or expired password reset token");
    }

    const salt = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(password, salt);

    await User.updateOne(
      { _id: userId },
      { $set: { password: hash } },
      { new: true }
    );

    const user = await User.findById({ _id: userId });
    const name = `${user.firstName} ${user.lastName}`;

    const htmlContent = `<h1>Hi, ${name}</h1>
      <p>Your password reset successfully.</p>`;

    sendEmail(user.email, "Password Reset Successfully", htmlContent);
    await passwordResetToken.deleteOne();
    return "Password Reset Successfully";
  } catch (error) {
    console.log(error.message);
  }
};

module.exports = { requestPasswordReset, resetPassword };

登录后复制
  • requestPasswordReset(email):生成密码重置令牌,将其保存在数据库中,并向用户发送包含重置链接的电子邮件。

  • resetPassword(userId, token, password):验证令牌,散列新密码,更新数据库中的用户密码,并发送确认电子邮件。

1.11。实现用户控制器 创建controllers目录并添加userController.js文件:

const User = require("../models/userSchema");
const bcrypt = require("bcryptjs");
const generateToken = require("../config/generateToken");
const { requestPasswordReset, resetPassword } = require("../services/authService");
const Token = require("../models/tokenSchema");
const cloudinary = require("cloudinary").v2;

const register = async (req, res) => {
  try {
    const {
      firstName,
      lastName,
      email,
      password,
      confirmPassword,
      mobileNo,
      dob,
    } = req.body;

    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(password, salt);

    const image = req.file.path;
    const imagePublicId = req.file.filename;
    const emailExist = await User.findOne({ email });

    if (emailExist) {
      await cloudinary.uploader.destroy(imagePublicId);
      res.status(409).send({ message: "User with this email already exists" });
    }

    if (password != confirmPassword) {
      await cloudinary.uploader.destroy(imagePublicId);
      res
        .status(400)
        .send({ message: "Password and Confirm password did not match" });
    }

    if (!emailExist && password === confirmPassword) {
      const newUser = await User.create({
        firstName: firstName,
        lastName: lastName,
        email: email,
        password: hashedPassword,
        mobileNo: mobileNo,
        dob: dob,
        image: image,
      });
      res.send({
        firstName: firstName,
        lastName: lastName,
        email: email,
        password: hashedPassword,
        mobileNo: mobileNo,
        dob: dob,
        token: generateToken(newUser._id),
      });
    }
  } catch (error) {
    if (req.file && req.file.filename) {
      await cloudinary.uploader.destroy(req.file.filename);
    }
    console.log(error);
  }
};

const login = async (req, res) => {
  try {
    const { email, password } = req.body;

    const user = await User.findOne({ email });

    if (!user) {
      res.status(404).send({ message: "Email id is wrong" });
    }

    if (user) {
      const validPassword = await bcrypt.compare(password, user.password);

      if (!validPassword) {
        res.status(401).send({ message: "Password is wrong" });
      } else {
        res.status(200).send({
          user: user,
          token: generateToken(user._id),
        });
      }
    }
  } catch (error) {
    console.log(error);
  }
};

const requestPasswordResetController = async (req, res) => {
  try {
    const email = req.body.email;
    const user = await User.findOne({ email });

    if (!user) {
      res.status(404).send({ message: "User not found" });
    }

    const requestPasswordResetService = await requestPasswordReset(email);

    return res.status(200).send(requestPasswordResetService);
  } catch (error) {
    res.status(500).send({ message: error.message });
    console.log(error.message);
  }
};

const resetPasswordController = async (req, res) => {
  try {
    const userId = req.body.userId;
    const token = req.body.token;
    const password = req.body.password;

    let passwordResetToken = await Token.findOne({ userId });
    const resetPasswordService = await resetPassword(userId, token, password);

    if (!passwordResetToken) {
      res.status(404).send({ message: "Token not found or expired" });
    }
    return res.status(200).send(resetPasswordService);
  } catch (error) {
    console.log(error.message);
    return res.status(500).send({ message: error.message });
  }
};

module.exports = {
  register,
  login,
  requestPasswordResetController,
  resetPasswordController,
};

登录后复制
    注册(请求,资源) 处理新用户的注册。以下是其功能的详细说明:
    输入:期望来自请求正文 (req.body) 的数据,其中包含用户信息,例如名字、姓氏、电子邮件、密码、confirmPassword、mobileNo、dob 和可选的图像(用于个人资料图片)。
  • 步骤:
  • 使用 bcrypt 对密码进行哈希处理以确保安全。

  • 检查数据库中是否已存在电子邮件 (User.findOne({ email }))。

  • 如果电子邮件存在,它会响应 409 状态(冲突)。

  • 如果密码(密码和确认密码)不匹配,则会响应 400 状态(错误请求)。

  • 如果一切有效,它会使用 User.create() 和散列密码在 MongoDB 中创建一个新的用户文档,并将个人资料图片上传到 Cloudinary。

  • 响应成功消息或用户数据以及使用generateToken(newUser._id)生成的JWT令牌。

  • 登录(请求,res)

  • 处理用户登录身份验证。其工作原理如下:

  • 输入:需要来自请求正文 (req.body) 的电子邮件和密码。

  • 步骤:

  • 通过电子邮件在数据库中查找用户 (User.findOne({ email }))。

  • 如果未找到用户,则响应 404 状态(未找到)。

  • 使用 bcrypt 将提供的密码与存储的散列密码进行比较。

  • 如果密码匹配,则生成 JWT 令牌 (generateToken(user._id)) 并将其与响应中的用户数据一起发送。

  • 如果密码不匹配,则响应 401 状态(未经授权)。

  • requestPasswordResetController(req, res)

  • Initiates the password reset process for a user:

  • Input: Expects email from the request body (req.body).

  • Steps:

  • Finds a user in the database by email (User.findOne({ email })).

  • If no user is found, responds with a 404 status (not found).

  • Calls requestPasswordReset(email) from authService to initiate the password reset process.

  • Sends a response with the result of the password reset request.

    1. resetPasswordController(req, res) Handles resetting a user's password:
  • Input: Expects userId, token, and password from the request body (req.body).

  • Steps:

  • Finds a password reset token for the user (Token.findOne({ userId })).

  • If no token is found, responds with a 404 status (not found).

  • Verifies the provided token against the stored hashed token using bcrypt.

  • If the token is valid, hashes the new password using bcrypt and updates the user's password in the database (User.updateOne()).

  • Sends a password reset confirmation email using sendEmail() and deletes the used password reset token (passwordResetToken.deleteOne()).

  • Responds with a success message.

1.12. Send Email Utility
Create a config directory and add a sendEmail.js file:

const nodemailer = require("nodemailer");

const sendEmail = async (email, subject, text) => {
  try {
    const transporter = nodemailer.createTransport({
      host: process.env.HOST,
      service: process.env.SERVICE,
      port: 465,
      secure: true,
      auth: {
        user: process.env.USER,
        pass: process.env.PASS,
      },
    });

    await transporter.sendMail({
      from: "Your App Name",
      to: email,
      subject: subject,
      html: text,
    });

    console.log("Email sent successfully");
  } catch (error) {
    console.log(error, "Email not sent");
  }
};

module.exports = sendEmail;
登录后复制
  • The sendEmail function integrates Nodemailer to handle email sending operations in your application. It's configured with SMTP settings using environment variables for security, sends HTML-formatted emails, and includes error handling to manage potential transmission issues effectively. This abstraction simplifies email-related tasks across your application, ensuring reliable communication with users.

1.13. Integrate Routes into the Server and cloudinary cloud image update
Update server.js to include the authentication routes:

const express = require("express");
const dotenv = require("dotenv");
const cors = require("cors");
const connectDB = require("./db/db");
const authRoutes = require("./routes/user");
const bodyParser = require("body-parser");
const multer = require("multer");
const { storage } = require("./config/storage");
const { register } = require("./controllers/user");
const employeeRoutes = require("./routes/employee");
const transactionRoutes = require("./routes/transaction");

const app = express();

dotenv.config();

const port = process.env.PORT || 5001;

//middlewares
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
const upload = multer({ storage });

//routes
app.use("/api/auth", authRoutes);

//routes with files

app.post("/api/auth/register", upload.single("file"), register);

mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

登录后复制

Step 2: Setting Up the Frontend
2.1. Initialize the Next.js Project
Create a new Next.js project:

npx create-next-app@latest mern-password-reset-client
cd mern-password-reset-client
登录后复制

2.2. Install Dependencies
Install the necessary dependencies:

npm install redux react-redux @reduxjs/toolkit react-toastify @heroicons/react @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome axios next-themes
登录后复制

2.3. Create Pages and Components
Create pages and components for registration, login, forgot password, and reset password.

app/register/page.js

import Register from "@/components/Auth/Register";
import React from "react";

const page = () => {
  return (
    <div>
      <Register />
    </div>
  );
};

export default page;

登录后复制

component/register.jsx

"use client";

import React, { useEffect, useState } from "react";
import styles from "./auth.module.css";
import Logo from "../misc/Logo/Logo";
import Link from "next/link";
import axios from "axios";
import { redirect } from "next/navigation";
import { Bounce, toast } from "react-toastify";
import { useTheme } from "next-themes";
import { useDispatch, useSelector } from "react-redux";
import { setRegister } from "@/redux/slice/authSlice";

const Register = () => {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmpassword] = useState("");
  const [mobileNo, setMobileNo] = useState("");
  const [dob, setDob] = useState("");
  const [file, setFile] = useState("");
  const [empData, setEmpData] = useState();
  const { theme } = useTheme();
  const token = useSelector((state) => state.auth.token);
  const dispatch = useDispatch();

  const mergeEmployeeDetails = async () => {
    try {
      const config = {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        },
      };
      const result = await axios.get(
        "http://localhost:5000/api/employee/employeeDetails",
        config
      );

      setEmpData(result);
    } catch (error) {
      console.log(error);
    }
  };

  const handleRegister = async () => {
    const formData = new FormData();

    formData.append("firstName", firstName);
    formData.append("lastName", lastName);
    formData.append("email", email);
    formData.append("password", password);
    formData.append("confirmPassword", confirmPassword);
    formData.append("mobileNo", mobileNo);
    formData.append("dob", dob);
    formData.append("file", file);

    try {
      const config = {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      };
      const result = await axios.post(
        "http://localhost:5000/api/auth/register",
        formData,
        config
      );
      const regUser = result.data;
      console.log(regUser);

      if (regUser) {
        dispatch(
          setRegister({
            token: regUser.token,
          })
        );
      }
      toast.success("Successfully Registered", {
        position: "top-right",
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark",
        transition: Bounce,
      });
      mergeEmployeeDetails();
      redirect("/login");
    } catch (error) {
      toast.error(error.response.data.message, {
        position: "top-right",
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark",
        transition: Bounce,
      });
      console.log(error.response.data);
    }
  };


  useEffect(() => {
    if (token) {
      mergeEmployeeDetails();
    }
  }, [token]);

  return (
    <div className={styles.container}>
      <div className={styles.auth_container}>
        <div className={styles.auth}>
          <div className={styles.auth_headers}>
            <div className={styles.upper_part}>
              <div className={styles.upper_part_text}>Welcome to</div>{" "}
              <div>
                <Logo />
              </div>
            </div>
            <div className={styles.lower_part}>
              Register yourself to N&N finance
            </div>
          </div>
          <div className={styles.auth_form}>
            <div className={styles.input_group}>
              <div className={styles.input}>
                <div className={styles.inputTwo}>
                  <div>
                    <div>
                      <label htmlFor="">First Name</label>
                    </div>
                    <div>
                      {" "}
                      <input
                        type="text"
                        placeholder="Enter your first name"
                        name="firstName"
                        value={firstName}
                        onChange={(e) => setFirstName(e.target.value)}
                      />
                    </div>
                  </div>
                  <div>
                    <div>
                      <label htmlFor="">Last Name</label>
                    </div>
                    <div>
                      {" "}
                      <input
                        type="text"
                        placeholder="Enter your last name"
                        name="lastName"
                        value={lastName}
                        onChange={(e) => setLastName(e.target.value)}
                      />
                    </div>
                  </div>
                </div>
              </div>
              <div className={styles.input}>
                <div>
                  <label htmlFor="">Email</label>
                </div>
                <div>
                  <input
                    type="email"
                    placeholder="Enter your email"
                    name="email"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                  />
                </div>
              </div>
              <div className={styles.input}>
                <div>
                  <label htmlFor="">Mobile No</label>
                </div>
                <div>
                  <input
                    type="text"
                    placeholder="Enter your mobile number"
                    name="mobileNo"
                    value={mobileNo}
                    onChange={(e) => setMobileNo(e.target.value)}
                  />
                </div>
              </div>
              <div className={styles.input}>
                <div>
                  <label htmlFor="">Password</label>
                </div>
                <div>
                  <input
                    type="password"
                    placeholder="Enter password"
                    name="password"
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                  />
                </div>
              </div>
              <div className={styles.input}>
                <div>
                  {" "}
                  <label htmlFor="">Confirm Password</label>
                </div>
                <div>
                  <input
                    type="password"
                    placeholder="Confirm Password"
                    name="confirmPassword"
                    value={confirmPassword}
                    onChange={(e) => setConfirmpassword(e.target.value)}
                  />
                </div>
              </div>
              <div className={styles.input}>
                <div>
                  <label htmlFor="">Date of Birth</label>
                  <input
                    type="date"
                    placeholder="Enter your DOB"
                    name="dob"
                    value={dob}
                    onChange={(e) => setDob(e.target.value)}
                  />
                </div>
              </div>
              <div className={styles.input}>
                <div>
                  <input
                    type="file"
                    placeholder="Enter your image"
                    name="file"
                    onChange={(e) => setFile(e.target.files[0])}
                  />
                </div>
              </div>
              <div className={styles.btn}>
                <button onClick={handleRegister}>Register</button>
              </div>
            </div>
            <div className={styles.bottom_part}>
              Already have account? <Link href="/">Login</Link>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Register;
登录后复制

app/login/page.js

"use client";

import Login from "@/components/Auth/Login";
import { setLogin } from "@/redux/slice/authSlice";
import { handleLogin } from "@/services/api";
import axios from "axios";
import { useTheme } from "next-themes";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { Bounce, toast } from "react-toastify";

export default function Home() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const dispatch = useDispatch();
  const router = useRouter();
  const theme = useTheme();

  return (
    <>
      <Login
        email={email}
        password={password}
        setEmail={setEmail}
        setPassword={setPassword}
        handleLogin={() =>
          handleLogin(email, password, dispatch, router, theme)
        }
      />
    </>
  );
}
登录后复制

component/login.jsx

"use client";
import React from "react";
import styles from "./auth.module.css";
import Link from "next/link";
import Logo from "../misc/Logo/Logo";

const Login = ({ email, password, setEmail, setPassword, handleLogin }) => {
  return (
    <div className={styles.auth_container_login}>
      <div className={styles.auth}>
        <div className={styles.auth_headers}>
          <div className={styles.upper_part}>
            <div className={styles.upper_part_text}>Welcome to</div>{" "}
            <div>
              <Logo />
            </div>
          </div>
          <div className={styles.lower_part}>
            Logged In yourself to N&N finance
          </div>
        </div>
        <div className={styles.auth_form}>
          <div className={styles.input_group}>
            <div className={styles.input}>
              <div>
                <label htmlFor="">Email</label>
              </div>
              <div>
                <input
                  type="email"
                  placeholder="Enter your email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                />
              </div>
            </div>
            <div className={styles.input}>
              <div>
                <label htmlFor="">Password</label>
              </div>
              <div>
                <input
                  type="password"
                  placeholder="Enter password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
              </div>
            </div>

            <div className={styles.btn}>
              <button onClick={handleLogin}>Login</button>
            </div>
          </div>
          <div className={styles.forgot_password}>
            <Link href="/forgot-password">Forgot Password?</Link>
          </div>
          <div className={styles.bottom_part}>
            Already have account? <Link href="/register">Sign Up</Link>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Login;
登录后复制

service/api.js

import axios from "axios";
import { Bounce, toast } from "react-toastify";
import { setLogin } from "@/redux/slice/authSlice";

export const handleLogin = async (email, password, dispatch, router, theme) => {
  try {
    const config = {
      headers: {
        "Content-Type": "application/json",
      },
    };

    const result = await axios.post(
      "http://localhost:5000/api/auth/login",
      { email, password },
      config
    );
    router.push("/dashboard");

    const loggedIn = result.data;

    if (loggedIn) {
      dispatch(
        setLogin({
          user: loggedIn.user,
          token: loggedIn.token,
        })
      );
    }

    toast.success("Successfully Logged In", {
      position: "top-right",
      autoClose: 5000,
      hideProgressBar: false,
      closeOnClick: true,
      pauseOnHover: true,
      draggable: true,
      progress: undefined,
      theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark",
      transition: Bounce,
    });
  } catch (error) {
    toast.error(error.response.data.message, {
      position: "top-right",
      autoClose: 5000,
      hideProgressBar: false,
      closeOnClick: true,
      pauseOnHover: true,
      draggable: true,
      progress: undefined,
      theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "system",
      transition: Bounce,
    });
    console.log(error.response.data);
  }
};
登录后复制

app/forgot-password.js

"use client";

import ForgotPasswordRequest from "@/components/Auth/ForgotPasswordRequest";
import EmailSuccess from "@/components/misc/authModal/EmailSuccess";
import axios from "axios";
import { useTheme } from "next-themes";
import React, { useEffect, useState } from "react";
import { Bounce, toast } from "react-toastify";

const ForgotPassword = () => {
  const [email, setEmail] = useState("");
  const theme = useTheme();
  const [isEmailSent, setIsEmailSent] = useState(false);

  const verifyEmail = async () => {
    try {
      const result = await axios.post(
        "http://localhost:5000/api/auth/requestPasswordReset",
        { email }
      );

      toast.success(
        "Password reset code has been sent successfully to your email",
        {
          position: "top-right",
          autoClose: 5000,
          hideProgressBar: false,
          closeOnClick: true,
          pauseOnHover: true,
          draggable: true,
          progress: undefined,
          theme:
            theme === "light" ? "light" : theme === "dark" ? "dark" : "system",
          transition: Bounce,
        }
      );

      setIsEmailSent(true);

      console.log(result);
    } catch (error) {
      toast.error(error.response?.data.message, {
        position: "top-right",
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme:
          theme === "light" ? "light" : theme === "dark" ? "dark" : "system",
        transition: Bounce,
      });
      console.log("error: ", error);
    }
  };

  useEffect(() => {
    localStorage.setItem("email", email);
  }, [email]);

  return (
    <>
      {!isEmailSent ? (
        <ForgotPasswordRequest
          email={email}
          setEmail={setEmail}
          verifyEmail={verifyEmail}
        />
      ) : (
        <EmailSuccess email={email} />
      )}
    </>
  );
};

export default ForgotPassword;
登录后复制

components/forgotPassword/ForgotPassword.jsx

"use client";
import React from "react";
import styles from "./auth.module.css";
import Link from "next/link";
import Logo from "../misc/Logo/Logo";
import { toast } from "react-toastify";

const ForgotPasswordRequest = ({ email, setEmail, verifyEmail }) => {
  return (
    <div className={styles.auth_container_login}>
      <div className={styles.auth}>
        <div className={styles.auth_headers}>
          <div className={styles.upper_part}>
            <div className={styles.upper_part_text}></div>{" "}
            <div>
              <Logo />
            </div>
          </div>
          <div className={styles.lower_part}>Find Your Account</div>
        </div>
        <div className={styles.auth_form}>
          <div className={styles.input_group}>
            <div className={styles.input}>
              <div>
                <label htmlFor="">
                  Please enter your email address or mobile number to search for
                  your account.
                </label>
              </div>
              <div>
                <input
                  type="email"
                  placeholder="Enter your email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                />
              </div>
            </div>

            <div className={styles.btn}>
              <button onClick={verifyEmail}>Confirm your email address</button>
            </div>
          </div>
          <div className={styles.bottom_part}>
            <Link href="/">Go Back</Link>
          </div>
        </div>
      </div>
    </div>
  );
};

export default ForgotPasswordRequest;
登录后复制

components/forgotPassword/EmailSuccess.jsx

import React from "react";
import styles from "./emailSuccess.module.css";
import Image from "next/image";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEnvelope } from "@fortawesome/free-solid-svg-icons";
import Link from "next/link";
import { ArrowLongLeftIcon } from "@heroicons/react/24/outline";

const EmailSuccess = ({ email }) => {
  const openEmailApp = () => {
    // Mailto link to open email client
    window.location.href = "mailto:";
  };
  return (
    <div className={styles.container}>
      <div className={styles.card_email}>
        <div className={styles.card_top}>
          <FontAwesomeIcon icon={faEnvelope} shake size="3x" color="#03c03c" />
        </div>
        <div className={styles.card_middle}>
          <div className={styles.text}>We sent a password reset link to</div>
          <div className={styles.text_email}>{email}</div>
          <div className={styles.btn}>
            <button onClick={openEmailApp}>Open email app</button>
          </div>
        </div>
        <div className={styles.card_bottom}>
          <div className={styles.text_resend}>
            Did not receive the email? <button>Click to resend</button>
          </div>
          <div className={styles.backTO}>
            <ArrowLongLeftIcon
              style={{
                height: "1rem",
                width: "1rem",
                cursor: "pointer",
                marginRight: "10px",
              }}
            />{" "}
            <Link href="/">Back to login</Link>
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmailSuccess;
登录后复制

app/forgot-password/reset/page.jsx

"use client";

import ResetPasswordLayout from "@/components/Auth/ResetPasswordLayout";
import PasswordResetSuccess from "@/components/misc/authModal/passwordResetSuccess/PasswordResetSuccess";
import React, { useEffect, useState } from "react";
import { Bounce, toast } from "react-toastify";
import axios from "axios";
import { useRouter, useSearchParams } from "next/navigation";
import { useTheme } from "next-themes";
import { useDispatch } from "react-redux";
import { handleLogin } from "@/services/api";

const ResetPassword = () => {
  const [isSuccess, setIsSuccess] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmpassword] = useState("");
  const searchParams = useSearchParams();
  const token = searchParams.get("token");
  const userId = searchParams.get("id");
  const [message, setMessage] = useState("");
  const dispatch = useDispatch();
  const router = useRouter();
  const theme = useTheme();

  useEffect(() => {
    if (password && confirmPassword && password === confirmPassword) {
      setMessage("Password Matched");

      const hideTimeout = setTimeout(() => {
        setMessage("");
      }, 3000);

      return () => clearTimeout(hideTimeout);
    }

    setEmail(localStorage.getItem("email"));
  }, [password, confirmPassword]);

  const resetPassword = async () => {
    try {
      const result = await axios.post(
        "http://localhost:5000/api/auth/resetPassword",
        { userId, token, password }
      );

      toast.success("Password Successfully changed", {
        position: "top-right",
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark",
        transition: Bounce,
      });

      console.log(result);
      setIsSuccess(true);
    } catch (error) {
      toast.error(error.response?.data.message, {
        position: "top-right",
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme:
          theme === "light" ? "light" : theme === "dark" ? "dark" : "system",
        transition: Bounce,
      });
    }
  };
  return (
    <>
      {!isSuccess ? (
        <ResetPasswordLayout
          password={password}
          confirmPassword={confirmPassword}
          setPassword={setPassword}
          setConfirmpassword={setConfirmpassword}
          resetPassword={resetPassword}
          message={message}
        />
      ) : (
        <PasswordResetSuccess
          handleLogin={() =>
            handleLogin(email, password, dispatch, router, theme)
          }
        />
      )}
    </>
  );
};

export default ResetPassword;
登录后复制

components/forgotPassword/ResetPasswordLayout.jsx

import React, { useEffect, useState } from "react";
import styles from "./auth.module.css";
import Link from "next/link";
import Logo from "../misc/Logo/Logo";

const ResetPasswordLayout = ({
  password,
  confirmPassword,
  setPassword,
  setConfirmpassword,
  resetPassword,
  message,
}) => {
  return (
    <div className={styles.auth_container_login}>
      <div className={styles.auth}>
        <div className={styles.auth_headers}>
          <div className={styles.upper_part}>
            <div className={styles.upper_part_text}></div>{" "}
            <div>
              <Logo />
            </div>
          </div>
          <div className={styles.lower_part}>Set New Password</div>
          <div
            className={styles.lower_part}
            style={{ fontSize: "12px", marginTop: "5px" }}
          >
            Your new password must be different from previously used password.
          </div>
        </div>
        <div className={styles.auth_form}>
          <div className={styles.input_group}>
            <div className={styles.input}>
              <div>
                <label htmlFor="">Password</label>
              </div>
              <div>
                <input
                  type="password"
                  placeholder="Enter your new password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
              </div>
              <div className={styles.input}>
                <div>
                  <label htmlFor="">Confirm Password</label>
                </div>
                <div>
                  <input
                    type="password"
                    placeholder="Confirm your new password"
                    value={confirmPassword}
                    onChange={(e) => setConfirmpassword(e.target.value)}
                  />
                </div>
                {password != confirmPassword && confirmPassword != "" ? (
                  <div className={styles.warning}>
                    Password and Confirm password did not match
                  </div>
                ) : confirmPassword != "" ? (
                  <div className={styles.success}>{message}</div>
                ) : null}
              </div>
            </div>

            <div className={styles.btn}>
              <button onClick={resetPassword}>Reset Password</button>
            </div>
          </div>
          <div className={styles.bottom_part}>
            <Link href="/">Back to login</Link>
          </div>
        </div>
      </div>
    </div>
  );
};

export default ResetPasswordLayout;
登录后复制

components/forgotPassword/PasswordResetSuccess.jsx

import React from "react";
import styles from "./passwordResetSuccess.module.css";
import { ArrowLongLeftIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleCheck } from "@fortawesome/free-solid-svg-icons";

const PasswordResetSuccess = ({ handleLogin }) => {
  return (
    <div className={styles.container}>
      <div className={styles.card_email}>
        <div className={styles.card_top}>
          <FontAwesomeIcon
            icon={faCircleCheck}
            beat
            size="3x"
            color="#03c03c"
          />{" "}
        </div>
        <div className={styles.card_middle}>
          <div className={styles.bigText}>Password reset</div>
          <div className={styles.text}>
            Your password has been successfully reset.
          </div>
          <div className={styles.text_email}>
            Click below to login magically.
          </div>
          <div className={styles.btn}>
            <button onClick={handleLogin}>Continue</button>
          </div>
        </div>
        <div className={styles.card_bottom}>
          <div className={styles.backTO}>
            <ArrowLongLeftIcon
              style={{
                height: "1rem",
                width: "1rem",
                cursor: "pointer",
                marginRight: "10px",
              }}
            />{" "}
            <Link href="/">Back to login</Link>
          </div>
        </div>
      </div>
    </div>
  );
};

export default PasswordResetSuccess;
登录后复制

**Conclusion
**In conclusion, implementing a forgot password feature in a MERN stack application using Next.js on the frontend and Node.js with Express on the backend involves creating secure routes for password reset requests and token management, integrating email notifications with Nodemailer, and ensuring robust data security with bcrypt for password hashing and token validation. This approach enhances user experience by providing a reliable mechanism for resetting passwords while maintaining data integrity and security throughout the process.

以上是如何使用 Mern stack 和 cloudinary 云存储忘记密码?的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!