You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js/Express重置密码接口报Cannot set headers错误求助

解决重置密码接口的"Cannot set headers after they are sent to the client"错误

问题原因

当邮箱对应的用户不存在时,你在admin.auth().getUserByEmail的catch回调里执行了res.redirect,但这个return仅终止了catch回调函数,外层的异步函数还会继续执行,最终走到后续的else分支再次调用res.redirect,导致重复发送HTTP响应,触发该错误。

修复方案

将.then/.catch的链式调用改为try/catch结构,统一控制流程分支,确保每个请求只会发送一次响应。同时梳理逻辑,避免冗余的响应触发点。

修改后的代码

app.post(
  "/requireNewPassword",
  tryCatch(async (req, res) => {
    const { email, modulName, continueUrl } = req.body;

    let ipAddress = "";
    try {
      const ipRes = await axios.get("https://api.ipify.org");
      ipAddress = ipRes.data;
    } catch (err) {
      console.log(err);
    }

    const [linkPass] = await getLinkPasses(email, "last");
    let linkAvailable = false;

    // 检查重置链接是否存在且未过期
    if (!linkPass || linkPass.expiration < Date.now()) {
      linkAvailable = false;
      let user;
      try {
        // 尝试获取用户信息
        user = await admin.auth().getUserByEmail(email);
      } catch (err) {
        // 用户不存在的情况
        if (err.code === "auth/user-not-found") {
          linkAvailable = true;
          console.log(`${email} 不存在!`);
          await generateLinkPass(email, 0, false);
          return res.redirect(`/?email=${email}&linkAvailable=${linkAvailable}`);
        }
        // 其他错误情况
        console.log(err.message);
        return res.redirect("partials/error", { err });
      }

      // 用户存在的情况
      if (user) {
        const link_id = await generateLinkPass(email, 5, true);
        let pwdResetLink;
        try {
          // 生成Firebase重置链接
          const firebaseLink = await admin.auth().generatePasswordResetLink(email);
          const queryParams = new URLSearchParams(firebaseLink);
          const oobCode = queryParams.get("oobCode");
          const apiKey = queryParams.get("apiKey");
          pwdResetLink = `${URL_PWD}/resetPassword/?link_id=${link_id}&email=${email}&apiKey=${apiKey}&oobCode=${oobCode}&continueUrl=${continueUrl}`;
          console.log("密码重置链接已创建");
        } catch (err) {
          // 生成重置链接失败的情况
          const errorMsg = JSON.parse(
            err.errorInfo.message.match(/Raw server response: "(.*)"/)[1]
          ).error.message;
          console.log(errorMsg);
          return res.redirect("partials/error", { err: errorMsg });
        }

        if (pwdResetLink) {
          try {
            // 发送重置邮件
            await axios.post(`${URL_EFDS}/sendEmailTemplate`, {
              recipient: {
                name: user.displayName || "John Doe",
                email: email,
                ip_address: ipAddress,
              },
              recoveryLink: pwdResetLink,
              moduleName: modulName,
              apiKey: EFDS_APIKEY,
              templateType: "forgottenPassword",
            });
            console.log(`密码重置邮件已发送至 ${email}`);
          } catch (err) {
            console.log(err.data);
          }
          return res.redirect(`/?email=${email}&linkAvailable=${linkAvailable}`);
        } else {
          console.log("pwdResetLink 不存在");
          return res.redirect("partials/error", { err: "无法生成密码重置链接" });
        }
      }
    } else {
      // 已有有效重置链接的情况
      console.log("已有可用的有效重置链接");
      linkAvailable = true;
      return res.redirect(`/?email=${email}&linkAvailable=${linkAvailable}`);
    }
  })
);

关键改动说明

  1. 替换链式调用为try/catch:将axios.get、admin.auth().getUserByEmail、admin.auth().generatePasswordResetLink、axios.post的链式调用都改为try/catch,统一处理错误并终止流程,避免代码继续执行到后续的响应逻辑。
  2. 合并用户不存在的处理逻辑:在getUserByEmail的catch块中直接完成用户不存在时的所有操作(生成linkPass、重定向),并return终止函数,不会再走到后续的else分支。
  3. 确保单一响应出口:每个分支都通过return res.redirect终止函数,避免同一次请求多次发送响应。

内容的提问来源于stack exchange,提问作者Bátorfalvi Géza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 13:01:11