javascript - 发送 Email-NodeMailer 时,将标头发送到客户端后无法设置标头
问题描述
我正在尝试创建一个登录/注册,用户必须在继续之前确认他们的电子邮件,尽管我收到了确认电子邮件,并且一切正常,但我的控制台中也出现以下错误:
Cannot set headers after they are sent to the client
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at ServerResponse.header (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\response.js:267:15)
at errorHandler (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\server\error\errorHandler.js:20:39)
at Layer.handle_error (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\layer.js:71:5)
at trim_prefix (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:315:13)
at C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:284:7
at Function.process_params (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:335:12)
at next (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:275:10)
at C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:635:15
at next (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\index.js:260:14)
at next (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\node_modules\express\lib\router\route.js:127:14)
at exports.login (C:\Users\Administrator\Documents\Web Dev\Portfolio\FullStack\complete-auth\server\controllers\authControllers.js:131:5)
当代码如下时,我从我的登录控制器发送这封电子邮件isVerify=false
:
模型
const crypto = require("crypto");
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: [true, "Please enter your username"],
},
email: {
type: String,
required: [true, "Please enter your email"],
unique: true,
lowercase: true,
},
password: {
type: String,
required: [true, "Please enter a valid password"],
minlength: 8,
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
isVerified: {
type: Boolean,
required: true,
default: false,
},
resetPasswordToken: String,
resetPasswordExpired: Date,
verifyEmailToken: String,
verifyEmailExpired: Date,
});
// Hashing Password
UserSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// Checking if password entered is correct or not
UserSchema.methods.matchPasswords = async function (password) {
return await bcrypt.compare(password, this.password);
};
// Converting user data into JSON WEB TOKEN
UserSchema.methods.getSignedJwtAccessToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_ACCESS_SECRET, {
expiresIn: process.env.JWT_ACCESS_EXPIRE,
});
};
UserSchema.methods.getSignedJwtRefreshToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_REFRESH_SECRET, {
expiresIn: process.env.JWT_REFRESH_EXPIRE,
});
};
UserSchema.methods.getResetPasswordToken = function () {
const resetToken = crypto.randomBytes(20).toString("hex");
// Hash token (private key) and save to database
this.resetPasswordToken = crypto
.createHash("sha256")
.update(resetToken)
.digest("hex");
this.resetPasswordExpired = Date.now() + 10 * (60 * 1000); // Ten Minutes
return resetToken;
};
UserSchema.methods.getVerifyEmailToken = function () {
const confirmToken = crypto.randomBytes(20).toString("hex");
// Hash token (private key) and save to database
this.verifyEmailToken = crypto
.createHash("sha256")
.update(confirmToken)
.digest("hex");
this.verifyEmailExpired = Date.now() + 10 * (60 * 1000); // Ten Minutes
return confirmToken;
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
登录控制器
// @description: Login
// @route: POST /api/login
// @access: Public
exports.login = async (req, res, next) => {
const { email, password } = req.body;
if (!email || !password) {
return next(new ErrorResponse("Please enter credentials properly", 400));
}
try {
const user = await User.findOne({ email }).select("+password");
if (!user) {
return next(new ErrorResponse("Email not registered", 401));
}
const isMatch = await user.matchPasswords(password);
if (!isMatch) {
return next(new ErrorResponse("Invalid Password", 401));
}
// Sending Email if not Verified
if (!user.isVerified) {
try {
const confirmToken = user.getVerifyEmailToken();
await user.save();
const confirmUrl = `http://localhost:3000/confirmation/${confirmToken}`;
console.log("confirmURL: ", confirmUrl);
const html = `
<h1>VERIFY YOUR EMAIL</h1>
<p>Please verify your Email by clicking on the following link:</p>
<a href=${confirmUrl} clicktracking=off>${confirmUrl}</a>
`;
const message = `Verify your Email. Click on the fllowing link: ${confirmUrl}`;
try {
await sendEmail({
to: user.email,
subject: "VERIFICATION MAIL",
text: message,
html: html,
});
res.status(200).json({
success: true,
data: "A verification mail is send to your email. Please confirm before login",
});
} catch (err) {
user.verifyEmailToken = undefined;
user.verifyEmailExpired = undefined;
await user.save();
console.log("E^: ", err);
next(new ErrorResponse("Email could not be sent", 500));
}
} catch (error) {
next(error);
}
}
sendToken(user, 200, res);
} catch (error) {
next(error);
}
};
const sendToken = (user, statusCode, res) => {
const accessToken = user.getSignedJwtAccessToken();
const refreshToken = user.getSignedJwtRefreshToken();
refreshTokens.push(refreshToken);
res
.status(statusCode)
.cookie("accessToken", accessToken, {
expires: new Date(new Date().getTime() + 30 * 1000),
sameSite: "strict",
httpOnly: true,
}) // Dummie Cookie
.cookie("authSession", true, {
expires: new Date(new Date().getTime() + 30 * 1000),
})
.cookie("refreshToken", refreshToken, {
expires: new Date(new Date().getTime() + 31557600000),
sameSite: "strict",
httpOnly: true,
})
.cookie("refreshTokenID", true, {
expires: new Date(new Date().getTime() + 31557600000),
})
.json({ success: true });
};
发送电子邮件.js
const nodemailer = require("nodemailer");
const sendEmail = (options) => {
const transporter = nodemailer.createTransport({
service: process.env.EMAIL_SERVICE,
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
const mailOptions = {
from: process.env.EMAIL_FROM,
to: options.to,
subject: options.subject,
html: options.text,
};
transporter.sendMail(mailOptions, function (err, info) {
if (err) {
console.log(err);
} else {
console.log(info);
}
});
};
module.exports = sendEmail;
解决方案
您已使用此代码设置对客户端的响应。
res.status(200).json({
success: true,
data: "A verification mail is send to your email. Please confirm before login",
});
但是您有这种方法sendToken(user, 200, res);
,我假设您向客户发送了另一个信息,或者您在该方法中有另一种或res.json()
一种。res.send()
res.body()
将标头发送给客户端后,您无法再次设置标头。
你可以在你的后面加一个return语句res.status().json()
,这样sendToken()
就不会被执行了。
或者,也许您可以使用自己的方式。
res.status(200).json({
success: true,
data: "A verification mail is send to your email. Please confirm before login",
});
return;
推荐阅读
- javascript - 为什么 jslint 不能与 npm 包一起使用
- php - Symfony4 路由参数顺序
- android - Android Studio 3.3.2 kotlin 编译器警告
- math - 负二项式回归假设检验
- java - Firebase Query 无法在 Xiaomi Redmi Y1 (7.1.2 OS) 中获取数据
- reactjs - 将 react-dnd 与 cytoscape.js 一起使用
- quartz.net-3.0 - Quartz.net:GetDefaultScheduler() 的类型
- android - 我正在使用 Android Studio 创建一个应用程序。我遇到的情况是我试图使用来自另一个活动的图像
- c# - 将长度为 n 的棒材最佳切割成 3 种可能 4 种尺寸中的任何一种
- angular - 带有授权标头的 Angular 7 http(发布)-> 错误 500