nodejs mongoose - how to check items in the database before login - javascript

In my project, I've different roles (seller/user/admin)and i want to check the role and redirect to specific page if they are seller for example.
I struggle on how i can check the role in Mongo DB before the login. My login page is basic email-password and submit button.
for my signup all is good, it's use the correct model and post it in the DB.
here are some pieces of my code:
(client model)
userSchema.statics.login = async function (email, password, role) {
const user = await this.findOne({ email });
if (user) {
const auth = await bcrypt.compare(password, user.password);
if (auth) {
return user;
}
throw Error("incorrect password");
}
throw Error("incorrect email");
};
const ClientModel = mongoose.model("client", userSchema, "users");
login controller:
module.exports.clientSignIn = async (req, res) => {
const { email, password } = req.body;
try {
const user = await LoginModel.login(email, password);
const token = createToken(user._id);
res.cookie("jwt", token, { httpOnly: true, maxAge });
res.redirect('/success');
} catch (err) {
console.log(err.message);
}
};
thanks in advance for your help, if you need more info please feel free to ask

Following #EAzevedo 's advice.
i just change my Controller
module.exports.clientSignIn = async (req, res) => {
const { email, password } = req.body;
try {
const user = await LoginModel.login(email, password);
const token = createToken(user._id);
res.cookie("jwt", token, { httpOnly: true, maxAge });
if (user.role == "client") {
res.redirect("/success");
} else if (user.role == "technicien") {
res.redirect("/success-technicien");
} else if (user.role == "superuser") {
res.redirect("/success-admin");
};
} catch (err) {
const errors = signInErrors(err);
res.status(200).json({ errors });
}
};

when you get the user , you should have field for the role ,
then check which role logged in and redirect him to where he needs to be

Related

Unable to verify emails with JWT, need to find user with email but can't send email in the email

I am trying to verify user emails with JWT. My current set up is that a JWT is sent to a user when they try to log in if they do not have a confirmed email.
When the email is sent it composes a URL with the token and then sends the request to the server to verify the email. It worked great in postman as I could easily add the email that I want to verify in the body. But I can't think of a way how to do it in the browser.
This is the code that should verify the email.
confirmEmail = async (req, res, next) => {
const { email } = req.body
const param = req.params.token
const user = await userModel.findOne({email})
if(!user)
{
throw new HttpException(401, 'User not found')
}
if(user.confirmed)
{
throw new HttpException(401, 'User already confirmed')
}
if(!user.confirmed)
{
const confirmJWT = jwt.verify(param, process.env.SECRET_JWT)
if(!confirmJWT)
{
throw new HttpException(200, 'Token invalid')
}
const result = await userModel.emailConfirmed(email)
}
res.send('Database updated.')
}
This is the code that generates the JWT and sends it in an email.
if(!user.confirmed)
{
const emailToken = jwt.sign(
{
email: user.email
},
process.env.SECRET_JWT,
{
expiresIn: '15m'
}
)
console.log(emailToken)
emailModel.verifyEmail(email, emailToken)
throw new HttpException(401, 'Email not confirmed')
}
I was wondering if there is any way I can use the just the token to find the email of the user or is that not possible with JWT?
export const verifyEmail = () => {
try
{
return API()
.post(`/api/confirm/:token`, {}, {
params: {
token: store.user.authToken
},
email: store.user.email
})
.then(({data: userData}) => {
console.log('worked')
})
}
catch(error)
{
console.log(error)
}
}
import { verifyEmail } from '../../services/authAPI'
import { useUserStore } from '../../stores/user'
const store = useUserStore()
export default {
data()
{
return {
email: store.user.email
}
},
methods: {
async handleSubmit()
{
try
{
const response = await verifyEmail(this.email)
}
catch(err)
{
console.log(err)
}
}
}
}
</script>
Basically you do not need to send the email in the body as already encoded the email into the JWT. Once you do const verifiedToken = jwt.sign(token, secret key) You can do verifiedToken.email to grab the email.

How do i Validate old password while updating user password?

What's the proper way to validate old user password while updating new password?
So far i have tried and always get error: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
What i did:
I tried using bcrypt to compare the old password from req.body with user existing password and then hash with bcrypt before saving. Comparing the password using bcrypt gave the error above. Not comparing the old password at all and just saving new password works properly.
My code:
exports.updatePassword = async (req, res) => {
try {
const { oldPassword, password } = req.body;
let updatedPassword = {
password: password,
};
const user = await User.findOneAndUpdate(
{ _id: req.params.userId },
{ $set: updatedPassword },
{ new: true, useFindAndModify: false }
);
// validate old password
bcrypt.compare(oldPassword, user.password, function (err, match) {
if (!match || err)
return res.status(400).send('Please enter correct old password');
});
//hash password and save user
bcrypt.genSalt(12, function (err, salt) {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
user.save();
return res.json({user});
});
});
} catch (err) {
console.log(err);
return res.status(400).send('Something went wrong. Try again');
}
};
The issue is that the updatePassword function is ending before you actually process everything. To avoid nested function calls and returns, use the async methods provided by bcrypt (also check their recomendation on using async vs sync).
Regarding the code itself, you are updating the user's password before checking if the password is valid. You should get the user from the db, check if the current password matches, and only then insert the new hashed password into the db.
exports.updatePassword = async (req, res) => {
const { oldPassword, password } = req.body;
try {
// get user
const user = await User.findById(req.params.userId);
if (!user) {
return res.status(400).send('User not found');
}
// validate old password
const isValidPassword = await bcrypt.compare(oldPassword, user.password);
if (!isValidPassword) {
return res.status(400).send('Please enter correct old password');
}
// hash new password
const hashedPassword = await bcrypt.hash(password, 12);
// update user's password
user.password = hashedPassword;
const updatedUser = await user.save();
return res.json({ user: updatedUser });
} catch (err) {
console.log(err);
return res.status(500).send('Something went wrong. Try again');
}
};

Forgot password functionality using NodeJs/Knex/Nodemailer and it is not working properly

Note: this is my first time posting, if you have feedback please let me know
Goal: I am building some endpoints that let a user reset their password if they forgot it. Flow would look like this:
User doesn't know password so they click on forgot password.
User types in email and clicks send
User receives email with link to reset password. Clicks on link and is redirected to type in their new password.
They click 'save' and they are redirected to login to sign in with their new password
I am using Insomnia to hit the endpoints for testing.
Things that are working:
When providing an email to reset password, Nodemailer does send out an email.
When updating the password it does show 'password updated' and gives a 200 status.
Bugs:
After trying to log in with that new password, it is not saving to the database. Only the old password will allow you to log back in.
Things I have tried:
I tried changing my user.model to use my findByEmail function and ran into some weird bugs, which then led me down a rabbit hold of issues.
I tried console logging quite a few things to see if I could trace the path.
I tried changing the user.update function but was not able to get it to work.
Here is my code:
Any guidance would be appreciated. If you need to look at any other files please let me know.
Forgot.password.js
const router = require('express').Router();
const crypto = require('crypto')
const User = require('../models/users.model')
const nodemailer = require('nodemailer')
router.post('/forgotpassword', (req, res) => {
let {
email
} = req.body
console.log(req.body)
// if (req.body.email === '') {
// res.status(400).json({ message: 'Email is required'})
// } console.error(req.body.email)
User.findBy({
email
})
.first()
.then(user => {
if (user === null) {
res.status(403).json({
message: 'Email not in db'
})
} else {
const token = crypto.randomBytes(20).toString('hex')
User.update({
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 3600000,
})
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: `${process.env.EMAIL_USER}`,
pass: `${process.env.EMAIL_PASS}`
}
})
const mailOptions = {
from: `${process.env.EMAIL_USER}`,
to: `${user.email}`,
subject: '[Promoquo] Reset Password Link',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n' +
`http://localhost:5000/reset/${token}\n\n` +
'If you did not request this, please ignore this email and your password will remain unchanged.\n',
}
transporter.sendMail(mailOptions, (err, res) => {
if (err) {
console.log('ERROR coming from forgot.password js and it sucks', err)
} else {
console.log('here is the res', res)
res.status(200).json({
message: 'recovery email sent hell yes'
})
}
})
}
res.status(200).json({
message: 'Reset password email has been sent WOOHOO 🎉'
})
})
.catch(error => {
res.status(500).json({
message: 'ERROR on last catch forgotpassword.js, likely no user exists',
error
})
console.log(error)
})
})
module.exports = router
Update.password.js
const router = require('express').Router();
const passport = require('passport')
const bcrypt = require('bcrypt')
const User = require('../models/users.model')
const BCRYPT_SALT_ROUNDS = 12
router.put('/updatePasswordViaEmail', (req, res) => {
User.find({
where: {
username: req.body.username,
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: Date.now() + 3600000,
}
})
.then(user => {
if (user == null) {
console.error('password reset link has expired')
res.status(403).json({ message: 'Password reset link is invalid or has expired' })
} else if (user != null) {
console.log('user exists in db')
bcrypt.hash(req.body.password, BCRYPT_SALT_ROUNDS)
.then(hashedPassword => {
User.update({
password: hashedPassword,
resetPasswordToken: null,
resetPasswordExpires: null,
})
})
.then(() => {
console.log('log for THEN updating password')
res.status(200).json({ message: 'password updated' })
})
} else {
console.error('no user exists in db to update')
res.status(401).json({ message: 'no user exists in db to update'})
}
})
})
module.exports = router
Users.model.js
const db = require('../dbConfig')
module.exports = {
add,
find,
findBy,
findById,
findByEmail,
findByType,
update
};
function find() {
return db('users').select('id', 'username', 'email', 'password');
}
function findBy(filter) {
return db('users').where(filter);
}
async function add(user) {
const [id] = await db('users').insert(user);
return findById(id);
}
function findById(id) {
return db('users').where({ id }).first();
}
function findByEmail(email) {
return db('users').where({ email }).first();
}
function findByType(type) {
return db('users').where({ type }).first();
}
function update(changes, id) {
return db('users').where({ id }).update(changes)
}
20200913211559_users.js (this is the table)
exports.up = function(knex) {
return knex.schema.createTable('users', tbl => {
tbl.increments();
tbl.string('firstname', 30).notNullable();
tbl.string('lastname', 30).notNullable();
tbl.string('username', 30).notNullable()
tbl.string('email', 50).notNullable()
tbl.string('password', 128).notNullable();
tbl.string('type').notNullable();
tbl.boolean('confirmed').defaultTo('false');
tbl.string('resetPasswordToken');
tbl.date('resetPasswordExpires');
})
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('users')
};
Your User.update() lines aren't running (you either need to return their promises into the chains of promises, or hook into their callbacks). async/await is your friend here to avoid "callback hell."
const user = await User.find({
where: {
username: req.body.username,
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: Date.now() + 3600000,
}
})
if (!user) { /* ... */ }
const token = crypto.randomBytes(20).toString('hex')
await User.update({ // await here!
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 3600000,
})

How do I retrieve an encrypted password from my database with bcrypt?

I have an application with a register and login form. I've managed to get the encrypted password into the database, but i can't seem to get it to work when I want to compare it when logging in. How would I implement bcrypt in my login post method?
Here is my register post where the password is stored successfully:
router.post('/register', (req, res) => {
bcrypt.hash(req.body.password, 10).then((hash) => {
let userData = req.body;
let user = new User(userData);
user.password = hash;
user.save((error, registeredUser) => {
if (error) {
console.log(error);
} else {
let payload = {subject: registeredUser._id};
let token = jwt.sign(payload, 'secretKey');
res.status(200).send({token});
}
});
});
});
And here is my login post:
router.post('/login', (req, res) => {
let userData = req.body;
User.findOne({email: userData.email}, (error, user) => {
if (error) {
console.log(error);
} else {
if(!user) {
res.status(401).send('Invalid Email');
} else
if (user.password !== userData.password) {
res.status(401).send('Invalid Password');
} else {
let payload = {subject: user._id};
let token = jwt.sign(payload, 'secretKey');
res.status(200).send({token});
}
}
});
});
Few things to note here.
Password are usually encrypted using one-way hashing function, which means you shouldn't be expecting to decrypt the saved password back to original text.
In a one way hash function, same hash (encryption output) is created for same input, every time. Eg: If you can encrypt the word "mysimplepassword", the output is going to be the same "xjjklqjlj34309dskjle4" (just a sample) every time.
The method of checking password in such scenarios is:
(a) Store the encrypted password (hash) when its first provided (usually during sign up)
(b) During login, receive the password as input and encrypt it using same encryption method, to obtain the hash
(c) Compare the hash
If you are using bcrypt, you can use bcrypt.compare() to perform these operations
I figured it out and am now comparing the hashed passwords successfully. Here is the new login post:
router.post('/login', (req, res) => {
let userData = req.body;
User.findOne({email: userData.email}, (error, user) => {
if (error) {
console.log(error);
} else {
if(!user) {
res.status(401).send('Invalid Email');
} else {
bcrypt.compare(req.body.password, user.password, function (err, result) {
if (result == false) {
res.status(401).send('Invalid Password');
} else {
let payload = {subject: user._id};
let token = jwt.sign(payload, 'secretKey');
res.status(200).send({token});
}
});
}}
});
});

Using async/await correctly in the context of token validation

I'm building an Express.js backend for a website validating users using Google Sign-in.
And, I'm trying to create a RESTful API call which:
Takes in an IDtoken.
Validates that token using Google's OAuth2 Library.
Checks to see if a user is in a MongoDB Collection and then:
Either sends back the access token if true, or returns nothing if false.
This is what I have so far...
The post request:
//recieve token id from frontend, verify it, and send session back in response
router.post('/google', (req, res) => {
const idToken = req.body.tokenID;
const accessToken = req.body.accessToken;
const client = new OAuth2Client(keys.google.clientID);
const session = verify(idToken, accessToken, client).catch(console.error);
console.log('Session: ', session)
return res.send(session);
});
verify() :
//verify token
async function verify(idToken, accessToken, client) {
const ticket = await client.verifyIdToken({
idToken: idToken,
audience: keys.google.clientID,
});
const payload = ticket.getPayload();
const email = payload['email'];
if (findUser(email)) {
return {
email: email,
accessToken: accessToken
};
} else {
return null;
}
}
findUser() :
//find user
function findUser(email) {
User.find({email: email}, (error, user) => {
if(error) {
console.log(error);
return false;
} else if (user.length === 0) {
console.log('this user is not in the database');
return false;
} else {
console.log('this user is in the database');
return true;
}
});
}
For an output regardless of whether the user is in the DB or not I always get:
Session: Promise { <pending> }
this user is in the database
or
Session: Promise { <pending> }
this user is not in the database
I know that since verify() is an async function I need to get it to resolve its promise before assigning it to a variable, but I'm at a loss for how to do it correctly, and unfortunately, I can't make verify() into a synchronous function as then it does not create the ticket object correctly before calling getPayload() from Google's Library.
Any ideas for how to make all of this happen in the correct order?
Thank you.
Could you please try:
//recieve token id from frontend, verify it, and send session back in response
router.post('/google', async (req, res) => {
const idToken = req.body.tokenID;
const accessToken = req.body.accessToken;
const client = new OAuth2Client(keys.google.clientID);
const session = await verify(idToken, accessToken, client).catch(console.error);
console.log('Session: ', session)
return res.send(session);
});
Update -
Find User:
//find user
async function findUser(email) {
const user = await User.find({email: email}, (error, user) => {
if(error) {
console.log(error);
return false;
} else if (user.length === 0) {
console.log('this user is not in the database');
return false;
} else {
console.log('this user is in the database');
return true;
}
});
return user;
}

Categories

Resources