Maison interface Web js tutoriel Comment oublier le mot de passe en utilisant la pile Mern avec le stockage cloud cloud ?

Comment oublier le mot de passe en utilisant la pile Mern avec le stockage cloud cloud ?

Jul 17, 2024 pm 06:20 PM

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

Introduction
Dans cet article de blog, nous expliquerons le processus de configuration d'une fonctionnalité « Mot de passe oublié » à l'aide de la pile MERN (MongoDB, Express.js, React, Node.js) avec Next.js. Nous couvrirons tout, de la configuration du backend avec Node.js et Express à la création du frontend avec Next.js. À la fin, vous comprendrez parfaitement comment mettre en œuvre une fonctionnalité de réinitialisation de mot de passe sécurisée et conviviale.

Prérequis
Connaissance de base de JavaScript et de React.
Compréhension de Node.js et Express.js.
Familiarité avec MongoDB et Mongoose.
Expérience avec Next.js.
Étape 1 : Configuration du backend
1.1. Initialisez le projet Node.js
Créez un nouveau répertoire pour votre projet et initialisez un projet Node.js :

mkdir mern-password-reset
cd mern-password-reset
npm init -y
Copier après la connexion

1.2. Installer les dépendances
Installez les dépendances nécessaires :

npm install express mongoose bcryptjs jsonwebtoken nodemailer dotenv 
Copier après la connexion

express : Framework Web pour Node.js.
mangouste : outil de modélisation d'objets MongoDB.
bcryptjs : Bibliothèque pour hacher les mots de passe.
jsonwebtoken : pour générer et vérifier les jetons JWT.
nodemailer : Pour envoyer des e-mails.
dotenv : Pour gérer les variables d'environnement.

1.3. Configurer les variables d'environnement
Créez un fichier .env à la racine de votre projet pour stocker vos variables d'environnement :

PORT=5000
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret
EMAIL_USER=your_email@example.com
EMAIL_PASS=your_email_password
Copier après la connexion

1.4. Configurer le serveur express
Créez un fichier server.js et configurez le serveur Express de base :

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}`);
});

Copier après la connexion

1.5. Créer un modèle utilisateur
Créez un répertoire models et ajoutez un fichier User.js pour le modèle User :

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;
Copier après la connexion

1.6. Créer des routes d'authentification
Créez un répertoire de routes et ajoutez un fichier auth.js pour les routes d'authentification :

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;
Copier après la connexion

1.7. Ajouter Cloudinary pour le téléchargement de photos de profil
Installez les dépendances nécessaires pour Cloudinary :

npm install cloudinary multer multer-storage-cloudinary
Copier après la connexion
  • cloudinary : gestion d'images et de vidéos basée sur le cloud.
  • multer : Middleware pour la gestion des données multipart/form.
  • multer-storage-cloudinary : Intégration entre multer et cloudinary.

1.8. Configurer Cloudinary
Créez un répertoire de configuration et ajoutez un fichier 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 };
Copier après la connexion

Mettez à jour votre fichier .env pour inclure la configuration Cloudinary :

CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
Copier après la connexion

1.9. Définir le schéma de jeton
Créez un répertoire models et ajoutez un fichier 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;
Copier après la connexion

1.10. Implémenter le service d'authentification
Créez un répertoire de services et ajoutez un fichier 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 };

Copier après la connexion
  • requestPasswordReset(email) : génère un jeton de réinitialisation de mot de passe, l'enregistre dans la base de données et envoie un e-mail à l'utilisateur avec le lien de réinitialisation.

  • resetPassword(userId, token, password) : vérifie le jeton, hache le nouveau mot de passe, met à jour le mot de passe de l'utilisateur dans la base de données et envoie un e-mail de confirmation.

1.11. Implémentation du contrôleur utilisateur
Créez un répertoire de contrôleurs et ajoutez un fichier 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,
};

Copier après la connexion
  1. s'inscrire(req, res) Gère l’enregistrement d’un nouvel utilisateur. Voici un aperçu de ses fonctionnalités :
  • Entrée : attend les données du corps de la demande (req.body) contenant des informations sur l'utilisateur telles que le prénom, le nom, l'e-mail, le mot de passe, le mot de passe de confirmation, le numéro de mobile, le dob et éventuellement l'image (pour la photo de profil).
  • Étapes :
  • Hache le mot de passe en utilisant bcrypt pour la sécurité.

  • Vérifie si l'e-mail existe déjà dans la base de données (User.findOne({ email })).

  • Si l'email existe, il répond avec un statut 409 (conflit).

  • Si les mots de passe (password et confirmPassword) ne correspondent pas, il répond avec un statut 400 (mauvaise demande).

  • Si tout est valide, il crée un nouveau document utilisateur dans MongoDB en utilisant User.create() avec un mot de passe haché et télécharge la photo de profil sur Cloudinary.

  • Répond avec un message de réussite ou des données utilisateur ainsi qu'un jeton JWT généré à l'aide de generateToken(newUser._id).

  • connexion (req, res)

  • Gère l'authentification de connexion des utilisateurs. Voici comment cela fonctionne :

  • Entrée : attend l'e-mail et le mot de passe du corps de la demande (req.body).

  • Étapes :

  • Trouve un utilisateur dans la base de données par email (User.findOne({ email })).

  • Si aucun utilisateur n'est trouvé, répond avec un statut 404 (introuvable).

  • Compare le mot de passe fourni avec le mot de passe haché stocké à l'aide de bcrypt.

  • Si les mots de passe correspondent, génère un jeton JWT (generateToken(user._id)) et l'envoie avec les données utilisateur dans la réponse.

  • Si les mots de passe ne correspondent pas, répond avec un statut 401 (non autorisé).

  • 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;
Copier après la connexion
  • 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}`);
});

Copier après la connexion

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
Copier après la connexion

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
Copier après la connexion

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;

Copier après la connexion

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;
Copier après la connexion

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)
        }
      />
    </>
  );
}
Copier après la connexion

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;
Copier après la connexion

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);
  }
};
Copier après la connexion

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;
Copier après la connexion

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;
Copier après la connexion

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;
Copier après la connexion

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;
Copier après la connexion

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;
Copier après la connexion

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;
Copier après la connexion

**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.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

<🎜>: Grow A Garden - Guide de mutation complet
3 Il y a quelques semaines By DDD
<🎜>: Bubble Gum Simulator Infinity - Comment obtenir et utiliser les clés royales
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Système de fusion, expliqué
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers of the Witch Tree - Comment déverrouiller le grappin
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Tutoriel Java
1666
14
Tutoriel PHP
1273
29
Tutoriel C#
1253
24
Moteurs JavaScript: comparaison des implémentations Moteurs JavaScript: comparaison des implémentations Apr 13, 2025 am 12:05 AM

Différents moteurs JavaScript ont des effets différents lors de l'analyse et de l'exécution du code JavaScript, car les principes d'implémentation et les stratégies d'optimisation de chaque moteur diffèrent. 1. Analyse lexicale: convertir le code source en unité lexicale. 2. Analyse de la grammaire: générer un arbre de syntaxe abstrait. 3. Optimisation et compilation: générer du code machine via le compilateur JIT. 4. Exécuter: Exécutez le code machine. Le moteur V8 optimise grâce à une compilation instantanée et à une classe cachée, SpiderMonkey utilise un système d'inférence de type, résultant en différentes performances de performances sur le même code.

Python vs JavaScript: la courbe d'apprentissage et la facilité d'utilisation Python vs JavaScript: la courbe d'apprentissage et la facilité d'utilisation Apr 16, 2025 am 12:12 AM

Python convient plus aux débutants, avec une courbe d'apprentissage en douceur et une syntaxe concise; JavaScript convient au développement frontal, avec une courbe d'apprentissage abrupte et une syntaxe flexible. 1. La syntaxe Python est intuitive et adaptée à la science des données et au développement back-end. 2. JavaScript est flexible et largement utilisé dans la programmation frontale et côté serveur.

De C / C à JavaScript: comment tout cela fonctionne De C / C à JavaScript: comment tout cela fonctionne Apr 14, 2025 am 12:05 AM

Le passage de C / C à JavaScript nécessite de s'adapter à la frappe dynamique, à la collecte des ordures et à la programmation asynchrone. 1) C / C est un langage dactylographié statiquement qui nécessite une gestion manuelle de la mémoire, tandis que JavaScript est dynamiquement typé et que la collecte des déchets est automatiquement traitée. 2) C / C doit être compilé en code machine, tandis que JavaScript est une langue interprétée. 3) JavaScript introduit des concepts tels que les fermetures, les chaînes de prototypes et la promesse, ce qui améliore la flexibilité et les capacités de programmation asynchrones.

Javascript et le web: fonctionnalité de base et cas d'utilisation Javascript et le web: fonctionnalité de base et cas d'utilisation Apr 18, 2025 am 12:19 AM

Les principales utilisations de JavaScript dans le développement Web incluent l'interaction client, la vérification du formulaire et la communication asynchrone. 1) Mise à jour du contenu dynamique et interaction utilisateur via les opérations DOM; 2) La vérification du client est effectuée avant que l'utilisateur ne soumette les données pour améliorer l'expérience utilisateur; 3) La communication de rafraîchissement avec le serveur est réalisée via la technologie AJAX.

JavaScript en action: Exemples et projets du monde réel JavaScript en action: Exemples et projets du monde réel Apr 19, 2025 am 12:13 AM

L'application de JavaScript dans le monde réel comprend un développement frontal et back-end. 1) Afficher les applications frontales en créant une application de liste TODO, impliquant les opérations DOM et le traitement des événements. 2) Construisez RestulAPI via Node.js et Express pour démontrer les applications back-end.

Comprendre le moteur JavaScript: détails de l'implémentation Comprendre le moteur JavaScript: détails de l'implémentation Apr 17, 2025 am 12:05 AM

Comprendre le fonctionnement du moteur JavaScript en interne est important pour les développeurs car il aide à écrire du code plus efficace et à comprendre les goulots d'étranglement des performances et les stratégies d'optimisation. 1) Le flux de travail du moteur comprend trois étapes: analyse, compilation et exécution; 2) Pendant le processus d'exécution, le moteur effectuera une optimisation dynamique, comme le cache en ligne et les classes cachées; 3) Les meilleures pratiques comprennent l'évitement des variables globales, l'optimisation des boucles, l'utilisation de const et de locations et d'éviter une utilisation excessive des fermetures.

Python vs JavaScript: communauté, bibliothèques et ressources Python vs JavaScript: communauté, bibliothèques et ressources Apr 15, 2025 am 12:16 AM

Python et JavaScript ont leurs propres avantages et inconvénients en termes de communauté, de bibliothèques et de ressources. 1) La communauté Python est amicale et adaptée aux débutants, mais les ressources de développement frontal ne sont pas aussi riches que JavaScript. 2) Python est puissant dans les bibliothèques de science des données et d'apprentissage automatique, tandis que JavaScript est meilleur dans les bibliothèques et les cadres de développement frontaux. 3) Les deux ont des ressources d'apprentissage riches, mais Python convient pour commencer par des documents officiels, tandis que JavaScript est meilleur avec MDNWEBDOCS. Le choix doit être basé sur les besoins du projet et les intérêts personnels.

Python vs JavaScript: environnements et outils de développement Python vs JavaScript: environnements et outils de développement Apr 26, 2025 am 12:09 AM

Les choix de Python et JavaScript dans les environnements de développement sont importants. 1) L'environnement de développement de Python comprend Pycharm, Jupyternotebook et Anaconda, qui conviennent à la science des données et au prototypage rapide. 2) L'environnement de développement de JavaScript comprend Node.js, VScode et WebPack, qui conviennent au développement frontal et back-end. Le choix des bons outils en fonction des besoins du projet peut améliorer l'efficacité du développement et le taux de réussite du projet.

See all articles