How to send authentication email with sendgrid - node.js

I have these lines of code, but having difficulty sending mail using SendGrid. I need a working guide to what can be done.
const sgMail = require('#sendgrid/mail')
sgMail.setApiKey(process.env.MAIL_KEY)
const { name, email, password } = req.body;
console.log(name, email, password)
const errors = validationResult(req);
if (!errors.isEmpty()) {
const firstError = errors.array().map(error => error.msg)[0];
return res.status(422).json({
errors: firstError
});
} else {
User.findOne({
email
}).exec((err, user) => {
if (user) {
return res.status(400).json({
errors: 'Email is taken'
});
}
});
const token = jwt.sign(
{
name,
email,
password
},
process.env.JWT_ACCOUNT_ACTIVATION,
{
expiresIn: '5m'
}
);
const emailData = {
from: process.env.EMAIL_FROM,
to: email,
subject: 'Account activation link',
html: `
<h1>Please use the following to activate your account</h1>
<p>${process.env.CLIENT_URL}/users/activate/${token}</p>
<hr />
<p>This email may containe sensetive information</p>
<p>${process.env.CLIENT_URL}</p>
`
};
sgMail.send(emailData)
.then(sent => {
return res.json({
message: `Email has been sent to ${email}`
});
})
.catch(err => {
console.log(err)
return res.status(400).json({
success: false,
errors: errorHandler(err)
});
});
The above code is the code I have, but I get the error described in the image which is the catch to the .then of the sgMail.send. I get a forbidden error in the console. I don't know where that is really coming from

Related

How to save the Reset Password Nodejs and MongoDb

I am using SendGrid to send the user the reset password link that goes with two parameters (The user._id and token). I have another component that saves the user's changed the password but all I get is an error user. save is not a function
Email helper Code.
import sendGrid from "#sendgrid/mail";
export class sendGridEmail {
static async sendResetPasswordEmail(email, token, id) {
sendGrid.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: `${email}`,
from: `${process.env.VERIFIED_SENDER}`, // Change to your verified sender
subject: "RESET YOUR PASSWORD",
text: `Follow this link to reset your password: ${process.env.BASE_URL}/${id}/${token}`,
};
return sendGrid
.send(msg)
.then(() => {
console.log(`password rest link has been sent to: ${email}`);
})
.catch((err) => {
console.log(err);
});
}
sendLink Component
export const resetUserPassword = asynchandler(async (req, res) => {
const { email } = req.body;
const user = await userModel.findOne({ email });
if (!user) {
res.status(404);
res.json({ message: "the email provided was not found" });
} else if (user) {
const token = AuthToken(user._id);
try {
await sendGridEmail.sendResetPasswordEmail(user.email, token, user._id);
res.status(200);
res.json({
message: `a link to reset your password has been sent to: ${user.email}`,
});
} catch (error) {
res.status(500);
res.json({ message: error });
}
} else {
res.status(500);
res.json({ message: "Internal Server Error" });
}
});
The Component that tries to update the password in the Database but I get an error user.save() is not a function
export const saveResetPassword = asynchandler(async (req, res) => {
const { id, authorization } = req.params;
const user = userModel.findOne(req.params.id);
const private_key=process.env.PRIVATE_KEY
const payload = jwt.verify(authorization, private_key);
if (user._id === id || payload.id) {
try {
user.password = req.body.password;
await user.save();
} catch (error) {
res.status(404);
res.json({ message: `an error occured: ${error}` });
}
}else{
res.status(500)
res.json({message: "an error occured"})
}
});
My Routes
import { loginUser, registerUser, resetUserPassword, saveResetPassword } from "./controllers/user.controller.js";
export const Routes =(app)=>{
app.get("/health", (req,res) => {
res.send(200).json({message:"Server health check is Ok"});
});
// user api's
app.post('/api/registeruser', registerUser);
app.post('/api/loginuser', loginUser);
app.post('/api/password-reset', resetUserPassword);
app.post("/api/save-password/:id/:authorization", saveResetPassword);
}
const user = await userModel.findOne(req.params.id);
You forgot await, model.findOne() returns a Promise

forgotten password with node and sendgride issue

I have tried to set up the forgot password backend as follows but it seems not to work.
exports.forgotPassword = (req, res) => {
const { email } = req.body.email;
User.findOne({ email }, (err, user) => {
if (err || !user) {
return res.status(401).json({
error: 'User with that email does not exist'
});
}
const token = jwt.sign({ _id: user._id }, process.env.JWT_RESET_PASSWORD, { expiresIn: '10m' });
// email
const emailData = {
from: process.env.EMAIL_FROM,
to: email,
subject: `Password reset link`,
html: `
<p>Please use the following link to reset your password:</p>
<p>${process.env.CLIENT_URL}/auth/password/reset/${token}</p>
<hr />
<p>This email may contain sensetive information</p>
`
};
// populating the db > user > resetPasswordLink
return user.updateOne({ resetPasswordLink: token }, (err, success) => {
if (err) {
return res.json({ error: errorHandler(err) });
} else {
sgMail.send(emailData).then(sent => {
return res.json({
message: `Email has been sent to ${email}. Follow the instructions to reset your password. Link expires in 10min.`
});
});
}
});
});
};
Test on postman showing sending without success and error
up on canceling the continues sending a request in postman, there is no error in postman console. However, my terminal console has this funny response
I will appreciate any help.
Thank you.
Since express doesn't know when to go from a function to another function, you need to call the next() argument, passed to these functions (in this case: forgotPasswordValidator and forgotPassword). You can find more on that here: http://expressjs.com/en/guide/using-middleware.html
The router:
const validationRules = () => {
return [
check('email')
.notEmpty().withMessage('Please add an email address')
.isEmail().withMessage('Must be a valid email address')
]
}
router.put("/forgot-password", validationRules(), forgotPasswordValidator, forgotPassword);
forgotPasswordValidator middleware function:
const { validationResult } = require('express-validator');
exports.forgotPasswordValidator = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
} else next()
};
The forgotPassword function seems fine, but if you have any more problems feel free to write a comment.

Solved: my code is unable to encrypt the password to save in mongoDB after email activation token is decrypted in node js, how can i solve it?

i am trying to implement jwt authentication by MERN and in nodejs i have used an email activation link to save the user email and password in mongodb. here is my working example for registering the user and activating the user. i am using sendgrid for the email.
//for email verification test
signup = async (req, res) => {
const { username, email, password, passwordCheck, displayName } = req.body;
//validate
if (!username || !email || !password || !passwordCheck)
return res.status(400).json({
msg: "not all fields have been entered.",
});
if (password.length < 8)
return res.status(400).json({
msg: "The password needs to be at least 8 characters long.",
});
if (password !== passwordCheck)
return res
.status(400)
.json({ msg: "Enter the same password twice for verification." });
const existingUser = await User.findOne({ email: email });
if (existingUser)
return res
.status(400)
.json({ msg: "An account with this email already exists." });
const existingUserName = await User.findOne({ username: username });
if (existingUserName)
return res
.status(400)
.json({ msg: "An account with this username already exists." });
if (!displayName) displayName = email;
const token = jwt.sign(
{ username, email, password, passwordCheck, displayName },
process.env.JWT_SECRET,{expiresIn:'1000m'}
);
const msg = {
to: email, //receiver's email
from: "no-reply#test.com", // Change to your verified sender
subject: `Email verification link ${displayName}`,
text: "testing from local",
html: `<h2>Hi ${displayName}</h2> <br/>sends a message for verification test: http://localhost:3000/authentication/activate/${token}</p> <br/><p>Have a nice day</p>`,
};
sgMail.setApiKey(process.env.SENDGRID_SECRET_API);
sgMail
.send(msg)
.then((result) => {
res.json({ message: "Email activation link has been sent" });
})
.catch((error) => {
console.error(error);
res.status(500).json("Error");
});
}
router.post("/register", signup);
userActivation = (req, res)=>{
const { token } = req.body;
if(token){
jwt.verify(token, process.env.JWT_SECRET,function(err,decodeToken){
if(err){
return res.status(400).json({error:'Incorrect or expired link.'})
}
const { username, email, password, passwordCheck, displayName }=decodeToken;
const newUser = new User({
username,
email,
password,
displayName,
});
newUser.save((err,success)=>{
if(err){
console.log("Error in signup with account activation",err)
return res.status(400).json({error:"Error activating account"})
}
res.json({
message:"signup Success!!"
})
});
} );
} else{
return res.json({error:"Something went wrong"})
}
}
router.post("/email-activate",userActivation)
while using the postman to save the user with activation key, in mongodb the password is saving in plain text. i don want it to be saved in plain text because of security issue. i want it to be saved in encrypted and tried to use the below code:
userActivation = async (req, res) => {
const { token } = req.body;
if (token) {
jwt.verify(token, process.env.JWT_SECRET, function (err, decodeToken) {
if (err) {
return res.status(400).json({ error: "Incorrect or expired link." });
}
const { username, email, password, displayName } = decodeToken;
console.log(password)
User.findOne({ email }).exec((err, user) => {
if (user) {
return res.status(400).json({ error: "Username with this email exists." })
}
const salt = bcrypt.genSalt();
bcrypt.hash(password, salt, (err, passwordHash)=>{
const newUser = new User({
username,
email,
password: passwordHash,
displayName,
});
console.log(password)
console.log(passwordHash)
newUser.save((err, success) => {
if (err) {
console.log("Error in signup with account activation", err);
return res.status(400).json({ error: "Error activating account" });
}
res.json({
message: "signup Success!!",
});
})
})
})
})
}
}
when i start my server and try to sign in with activation key through postman it sends me an activation link. when i try to send the post request through the postman in activation link,postman shows 404 status with "error activating account" and node index shows the following error:
The server has started on port: 5000
MongoDB connected
**the real password is showing undecoded**
**the real password is showing undecoded**
undefined
Error in signup with account activation Error: user validation failed: password: Path `password` is required.
at ValidationError.inspect (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\error\validation.js:47:26)
at formatValue (internal/util/inspect.js:731:31)
at inspect (internal/util/inspect.js:295:10)
at formatWithOptionsInternal (internal/util/inspect.js:1958:40)
at formatWithOptions (internal/util/inspect.js:1842:10)
at Object.value (internal/console/constructor.js:306:14)
at Object.log (internal/console/constructor.js:341:61)
at C:\Myfiles\Reactjs\Projects\test-projects\routes\userRouter.js:243:37
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\model.js:4863:16
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\helpers\promiseOrCallback.js:16:11
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\model.js:4886:21
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\model.js:500:16
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:247:48
at next (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:168:27)
at next (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:170:9)
at Kareem.execPost (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:218:3) {
errors: {
password: ValidatorError: Path `password` is required.
at validate (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\schematype.js:1256:13)
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\schematype.js:1239:7
at Array.forEach (<anonymous>)
at SchemaString.SchemaType.doValidate (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\schematype.js:1184:14)
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\document.js:2502:18
at processTicksAndRejections (internal/process/task_queues.js:75:11) {
properties: [Object],
kind: 'required',
path: 'password',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'user validation failed'
}
According to above try the code is unable to encrypt the password and hence it can't be saved in mongodb. what did i do wrong to encode the password?
so how can i solve it?
thanx in advance
const passwordHash = bcrypt.hash(password, salt);
Here bcrypt.hash returning promise either your can use async/await or use .then().
userActivation = async (req, res) => {
const { token } = req.body;
if (token) {
jwt.verify(token, process.env.JWT_SECRET, function (err, decodeToken) {
if (err) {
return res.status(400).json({ error: "Incorrect or expired link." });
}
const { username, email, password, displayName } = decodeToken;
console.log(password);
User.findOne({ email }).exec((err, user) => {
if (user) {
return res.status(400).json({ error: "Username with this email exists." })
}
//Use genSaltSync when you donot want to use await or your can use await bcrypt.genSalt()
const salt = bcrypt.genSaltSync(10);
bcrypt.hash(password, salt, (err, passwordHash)=>{
const newUser = new User({
username,
email,
password: passwordHash,
displayName,
});
newUser.save((err, success) => {
if (err) {
console.log("Error in signup with account activation", err);
return res.status(400).json({ error: "Error activating account" });
}
res.json({
message: "signup Success!!",
});
})
})
})
})
}
}
Try this code once just put await before bcrypt and made function async.

Invalid email or password

don't know whats going on wrong,when i am trying to post request through postman i am getting an error like "Invalid email or password". in sign in. please help
signup
below is my signup request where i am doing my signup validation.
const User = require('../model/user');
const bcrypt = require('bcryptjs');
exports.signup = (req, res) => {
const { name, email, password } = req.body;
if (!name || !email || !password) {
res.status(422).json({
error: "please add all field"
})
}
User.findOne({ email: email })
.then((SavedUser) => {
if (SavedUser) {
return res.status(400).json({
error: "User already exists that email"
})
}
const user = new User({
email,
password,
name
})
user.save()
.then(user => {
res.json({
message: "saved Successfully"
})
.catch(err => {
console.log(err);
})
})
.catch(err => {
console.log(err);
})
})
}
Signin
below is my signin form where i doing my signin operation
exports.signin = (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
res.status(422).json({
error: "please enter email and password"
})
}
User.findOne({ email: email })
.then(SavedUser => {
if (!SavedUser) {
return res.status(400).json({
error: "invalid email or password"
})
}
bcrypt.compare(password, SavedUser.password)
.then(doMatch => {
if (doMatch) {
res.json({
message: "Successfully Signed in"
})
}
else {
return res.status(422).json({
error: "Invalid email or password"
})
}
})
.catch(err => {
console.log(err);
})
})
}
It seems you're not hasing the password, when creating a new mongoose user-object. Obvioulsy, bcrypt.compare(password, SavedUser.password) will then fail. Try to do it like this (note I'm using async/await here instead of promises directly):
password = await bcrypt.hash(password, 10);
const user = new User({
email,
password,
name
});
you didn't bcrypt your password at the time of saving.
You can make a pre save function in your schema like this.
// Hash the plain text password before saving
User.pre("save", async function (next) {
const user = this;
try {
if (user.isModified("password")) {
user.password = await bcrypt.hash(user.password, 8);
}
next();
} catch (error) {
next(error);
}
});

Why does transaction fails with already rolled back when user inputs in wrong email

I'm authorizing emails in the database but when I input wrong email it throws Transaction cannot be rolled back because it has been finished with state: commit
export const signin = async (req, res) => {
const { email, password } = req.body;
const transaction = await db.sequelize.transaction();
try {
const user = await db.User.findOne({ where: { email } }, { transaction });
await transaction.commit();
const passBool = Auth.comparePassword(password, user.password);
if (user && passBool) {
return res.json({
success: 1,
user,
message: 'signed in'
});
}
res.json({ success: 0, message: 'wrong username or password' });
} catch (ex) {
await transaction.rollback();
res.json({ success: 0, message: 'wrong username or password' });
}
};
I'm not sure exactly why rolling back doesn't work in your example, but you can try:
A transaction should be passed in the options object of the query:
const user = await db.User.findOne({ where: { email }, transaction });
You can try using managed transaction, to avoid manual handling of commit/rollback:
export const signin = async (req, res) => {
const { email, password } = req.body;
db.sequelize.transaction(async transaction => {
const user = await db.User.findOne({ where: { email }, transaction });
const passBool = Auth.comparePassword(password, user.password);
if (user && passBool) {
return res.json({
success: 1,
user,
message: 'signed in'
});
}
res.json({ success: 0, message: 'wrong username or password' });
}).catch(err => {
res.json({ success: 0, message: 'wrong username or password' });
});
};
solved this, it would fail if i had inserted in wrong request body params

Resources