node.js 中的忘记密码和密码重置流程

2025-06-10

node.js 中的忘记密码和密码重置流程

大家好,今天我们将在 Node.js 中实现通过电子邮件重置密码的功能。如果用户忘记了密码,我们会向其邮箱账户发送一个链接。用户可以通过该链接添加新密码。如果您只是想了解这个概念的工作原理,可以从“模型”部分开始。

那么让我们开始编码...

演示视频


项目 Github 链接

应用程序概述:
项目结构 下表显示了导出的 Rest API 的概述
项目结构

方法 网址 行动
邮政 /用户 创建用户
邮政 /密码重置 发送密码重置链接
邮政 /密码重置/:用户ID/:令牌 重置用户密码

创建 Node.js 应用程序

$ mkdir node-email-password-reset
$ cd node-email-password-reset
$ npm init --yes
$ npm install express mongoose dotenv nodemailer joi
Enter fullscreen mode Exit fullscreen mode

Express:Express 是一款精简灵活的 Node.js Web 应用框架。Mongoose :
Mongoose是一个用于 MongoDB 和 Node.js 的对象数据建模 (ODM) 库
。Nodemailer:Nodemailer 允许我们发送电子邮件。Joi :
Joi是一种对象模式描述语言,也是 JavaScript 对象的验证器。Dotenv
它从 .env 文件加载环境变量。

包.json

{
  "name": "node-email-password-reset",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^9.0.2",
    "express": "^4.17.1",
    "joi": "^17.4.0",
    "mongoose": "^5.12.10",
    "nodemailer": "^6.6.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

设置 Express Web 服务器
在根文件夹中,让我们创建index.js文件:

require("dotenv").config();
const express = require("express");
const app = express();

app.use(express.json());

const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Listening on port ${port}...`));
Enter fullscreen mode Exit fullscreen mode

配置环境变量
在根文件夹中,让我们创建.env文件:

DB = // mongodb url
HOST = // email host
USER = // email id
PASS = // email password
SERVICE = // email service
BASE_URL = "http://localhost:8080/api"
Enter fullscreen mode Exit fullscreen mode

配置 MongoDB 数据库
在根文件夹中,让我们创建db.js文件:

const mongoose = require("mongoose");

module.exports = connection = async () => {
    try {
        const connectionParams = {
            useNewUrlParser: true,
            useCreateIndex: true,
            useUnifiedTopology: true,
        };
        await mongoose.connect(process.env.DB, connectionParams);
        console.log("connected to database.");
    } catch (error) {
        console.log(error, "could not connect database.");
    }
};
Enter fullscreen mode Exit fullscreen mode

index.js中导入db.js并调用它:

//....
const connection = require("./db");
const express = require("express");
const app = express();

connection();

app.use(express.json());
//....
Enter fullscreen mode Exit fullscreen mode

定义模型
在根目录中创建模型文件夹。models
/user.js如下所示:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Joi = require("joi");

const userSchema = new Schema({
    name: {
        type: String,
        required: true,
    },
    email: {
        type: String,
        required: true,
    },
    password: {
        type: String,
        required: true,
    },
});

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

const validate = (user) => {
    const schema = Joi.object({
        name: Joi.string().required(),
        email: Joi.string().email().required(),
        password: Joi.string().required(),
    });
    return schema.validate(user);
};

module.exports = { User, validate };
Enter fullscreen mode Exit fullscreen mode

models/token.js文件如下:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

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

module.exports = mongoose.model("token", tokenSchema);
Enter fullscreen mode Exit fullscreen mode

配置电子邮件传输器
在根目录中创建utils文件夹。utils
/sendEmail.js文件如下所示:

const nodemailer = require("nodemailer");

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

        await transporter.sendMail({
            from: process.env.USER,
            to: email,
            subject: subject,
            text: text,
        });

        console.log("email sent sucessfully");
    } catch (error) {
        console.log(error, "email not sent");
    }
};

module.exports = sendEmail;
Enter fullscreen mode Exit fullscreen mode

定义路由
在根目录中创建routes文件夹。routes
/users.js文件如下:

const { User, validate } = require("../models/user");
const express = require("express");
const router = express.Router();

router.post("/", async (req, res) => {
    try {
        const { error } = validate(req.body);
        if (error) return res.status(400).send(error.details[0].message);

        const user = await new User(req.body).save();

        res.send(user);
    } catch (error) {
        res.send("An error occured");
        console.log(error);
    }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

routes/passwordReset.js文件如下:

const { User } = require("../models/user");
const Token = require("../models/token");
const sendEmail = require("../utils/sendEmail");
const crypto = require("crypto");
const Joi = require("joi");
const express = require("express");
const router = express.Router();

router.post("/", async (req, res) => {
    try {
        const schema = Joi.object({ email: Joi.string().email().required() });
        const { error } = schema.validate(req.body);
        if (error) return res.status(400).send(error.details[0].message);

        const user = await User.findOne({ email: req.body.email });
        if (!user)
            return res.status(400).send("user with given email doesn't exist");

        let token = await Token.findOne({ userId: user._id });
        if (!token) {
            token = await new Token({
                userId: user._id,
                token: crypto.randomBytes(32).toString("hex"),
            }).save();
        }

        const link = `${process.env.BASE_URL}/password-reset/${user._id}/${token.token}`;
        await sendEmail(user.email, "Password reset", link);

        res.send("password reset link sent to your email account");
    } catch (error) {
        res.send("An error occured");
        console.log(error);
    }
});

router.post("/:userId/:token", async (req, res) => {
    try {
        const schema = Joi.object({ password: Joi.string().required() });
        const { error } = schema.validate(req.body);
        if (error) return res.status(400).send(error.details[0].message);

        const user = await User.findById(req.params.userId);
        if (!user) return res.status(400).send("invalid link or expired");

        const token = await Token.findOne({
            userId: user._id,
            token: req.params.token,
        });
        if (!token) return res.status(400).send("Invalid link or expired");

        user.password = req.body.password;
        await user.save();
        await token.delete();

        res.send("password reset sucessfully.");
    } catch (error) {
        res.send("An error occured");
        console.log(error);
    }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

如果您在前端使用此路由,则可能需要在passwordReset.js中添加另一个路由。我们只需要在链接有效时显示密码重置表单。因此,我们需要一个 GET 路由来验证链接并显示密码重置表单。

在 index.js 中导入路由

//...
const passwordReset = require("./routes/passwordReset");
const users = require("./routes/users");
const connection = require("./db");
//.....

app.use(express.json());

app.use("/api/users", users);
app.use("/api/password-reset", passwordReset);
//....
Enter fullscreen mode Exit fullscreen mode

就这样,在 Postman 中测试 API。如果您发现任何错误或需要改进代码,请在评论中告诉我。为了更好地理解,请观看 YouTube 视频。订阅我的 YouTube 频道,每周获取更多知识内容。

Arigato gozaimasu..🙂

鏂囩珷鏉ユ簮锛�https://dev.to/mrcyberwolf/how-to-implement-password-reset-via-email-in-node-js-132m
PREV
ES6 是 Node 的必由之路
NEXT
使用 🍃 MongoDB Atlas、💙 Microsoft Azure ML/AI 和 ⚡ 无服务器函数构建自定义 WordPress Nodejs 应用!🛡 🎛 Ⓦ ⚡ 🤖 🎯