Mongoose Ignore Required - node.js

I have this User schema:
email: {
type: String,
required: true
},
name: {
type: String,
required: true
},
password: {
type: String,
required: true
}
When you do a POST (/api/user-add), I want all the fields to be required. But when I do a login (/api/login) then I only need the email and password fields. My problem is, in my login code I eventually get to this function:
staffSchema.methods.generateToken = function(callback) {
var token = jwt.sign(this._id.toHexString(), config.SECRET);
this.token = token;
this.save(function(err, staff) {
if (err) return callback(err);
callback(null, staff);
});
}
And here it thows an error because the name field is required. How do I bypass this. I am looking for something like this I assume:
this.save(function(err, staff) {
if (err) return callback(err);
callback(null, staff);
}).ignoreRequired('name');

When You Login using JWT token this is a basic example to generate token and authenticate user without store token
Note :
Example to authenticate the user without store token in DB
*Login Method
const jwt = require('./jwt');
userCtr.authenticate = (req, res) => {
const {
email, password,
} = req.body;
const query = {
email: email,
};
User.findOne(query)
.then((user) => {
if (!user) {
//return error user not found.
} else {
if (passwordHash.verify(password, user.password)) { // verify password
const token = jwt.getAuthToken({ id: user._id });
const userData = _.omit(user.toObject(), ['password']); // return user data
return res.status(200).json({ token, userData });
}
//return error password not match
}
})
.catch((err) => {
});
};
*jwt.js
const jwt = require('jwt-simple');
const logger = require('./logger');
const jwtUtil = {};
jwtUtil.getAuthToken = (data) => {
return jwt.encode(data, process.env.JwtSecret);
};
jwtUtil.decodeAuthToken = (token) => {
if (token) {
try {
return jwt.decode(token, process.env.JwtSecret);
} catch (err) {
logger.error(err);
return false;
}
}
return false;
};
module.exports = jwtUtil;
*use middleware to prevent another route to access.
userRouter.post('/update-profile', middleware.checkUser, userCtr.updateProfile);
*middleWare.js
middleware.checkUser = (req, res, next) => {
const { headers } = req;
if (_.isEmpty(headers.authorization)) {
//return error
} else {
const decoded = jwt.decodeAuthToken(headers.authorization.replace('Bearer ', ''));
if (decoded) {
User.findOne({ _id: decoded.id })
.then((user) => {
if (user) {
req.user = user;
next();
} else {
//error
}
})
.catch((err) => {
//errror
});
req.user = decoded;
} else {
//error
}
}
};

Related

BCrypt does not compare password correctly

I am currently working on an API on node.js. I have seen other similar posts but the solution does not work for me. Below are my codes to retrieve user by email and compare the password. I enter the correct credentials but the compare is always returning false. Am I missing out something?
const bcrypt = require('bcrypt');
const saltRounds = 10;
app.post('/auth', function (req, response) {
let query = `select * from users where email = "${req.body.email}"`;
console.warn(req.body.email);
databaseConnector.query(query, (error, result) => {
if (error) {
console.log(error, 'Error occurred with /auth/ API...');
}
if (result.length > 0) {
console.warn(req.body.password, result[0].password);
bcrypt.compare(req.body.password, result[0].password, function (err, res) {
console.warn(res, 'bcryot response')
// if res == true, password matched
// else wrong password
if (res) {
var token = jwt.sign({ userID: result.id }, 'todo-app-super-shared-secret', { expiresIn: '2h' });
response.send({
token: token,
id: result[0].id,
firstName: result[0].firstName
})
}
else {
return response.sendStatus(401);
}
});
}
else {
return response.sendStatus(401);
}
})
});

Nodemailer verification using jwt

I'm trying to make e-mail verification using nodemailer and jwt key. The problem is that when I'm trying to verify jwt key, id of user is always undefined.
What am I doing wrong?
app.post("/api/createAccount", async (req, res) => {
const { login, password } = req.body;
const newUser = await user.create({
login: login,
password: password,
});
jwt.sign(
{ userId: newUser.id },
"SECRETKEY",
{
expiresIn: "7d",
},
(err, token) => {
const url = `http://localhost:5000/api/confirmMail/${token}`;
const options = {
from: "xxx",
to: login,
subject: "verifyacc",
html: `${url} `,
};
transporter.sendMail(options, function (err, info) {
if (err) {
console.log(err);
} else {
console.log(info);
}
});
}
);
});
app.get("/api/confirmMail/:token", async (req, res) => {
try {
const {
userId: { id },
} = jwt.verify(req.params.token, "SECRETKEY");
await user.update({ confirmed: 1 }, { where: { id: id } });
} catch (err) {
console.log(err);
}
return res.redirect("http://localhost:3000/login");
});
The error :
err: Error: WHERE parameter "id" has invalid "undefined" value
The payload of the token you are creating is
{
userId: "foobar"
}
But in the deconstruction during verification
const { userId: { id } } = jwt.verify(...);
you expect it to be
{
userId: {
id: "foobar"
}
}
because the deconstruction you used, roughly translates to
const tmp = jwt.verify(...);
const id = tmp.userId.id;
Thus id is of course undefined, as tmp.userId is (probably) a string or a number, which doesn't have an id property.
Use
const { userId: id} = jwt.verify(...);
await user.update({ confirmed: 1 }, { where: { id } });
which roughly translates to
const tmp = jwt.verify(...);
const id = tmp.userId;
or alternatively
const { userId} = jwt.verify(...);
await user.update({ confirmed: 1 }, { where: { id: userId } });

Am I sending JWT token correctly and is it being received and stored correctly? NodeJS backend & React frontend

I am very new to both nodeJS and React, and am currently trying to get my head around user authentication. I am able to register new users and login to my backend via postman/rapid api, but cannot login correctly via my react front end. Code below:
Front End Requests
import axios from 'axios'
import {AuthModel, UserModel} from './_models'
const API_URL = process.env.REACT_APP_API_URL
export const GET_USER_BY_ACCESSTOKEN_URL = `${API_URL}/auth/signin`
export const LOGIN_URL = `${API_URL}/auth/signin`
export const REGISTER_URL = `${API_URL}/auth/signup`
export const REQUEST_PASSWORD_URL = `${API_URL}/forgot_password`
// Server should return AuthModel
export function login(email: string, password: string) {
return axios.post<AuthModel>(LOGIN_URL, {
email,
password,
})
}
// Server should return AuthModel
export function register(
email: string,
firstname: string,
lastname: string,
password: string,
password_confirmation: string
) {
return axios.post(REGISTER_URL, {
email,
firstname: firstname,
lastname: lastname,
password,
password_confirmation,
})
}
// Server should return object => { result: boolean } (Is Email in DB)
export function requestPassword(email: string) {
return axios.post<{result: boolean}>(REQUEST_PASSWORD_URL, {
email,
})
}
export function getUserByToken(token: string) {
return axios.post<UserModel>(GET_USER_BY_ACCESSTOKEN_URL, {
api_token: token,
})
}
Backend:
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
const db = require("../models");
const User = db.user;
const Role = db.role;
verifyToken = (req, res, next) => {
let token = req.session.token;
if (!token) {
return res.status(403).send({ message: "No token provided!" });
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.status(401).send({ message: "Unauthorized!" });
}
req.userId = decoded.id;
next();
});
};
isAdmin = (req, res, next) => {
User.findById(req.userId).exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
Role.find(
{
_id: { $in: user.roles },
},
(err, roles) => {
if (err) {
res.status(500).send({ message: err });
return;
}
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === "admin") {
next();
return;
}
}
res.status(403).send({ message: "Require Admin Role!" });
return;
}
);
});
};
isModerator = (req, res, next) => {
User.findById(req.userId).exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
Role.find(
{
_id: { $in: user.roles },
},
(err, roles) => {
if (err) {
res.status(500).send({ message: err });
return;
}
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === "moderator") {
next();
return;
}
}
res.status(403).send({ message: "Require Moderator Role!" });
return;
}
);
});
};
const authJwt = {
verifyToken,
isAdmin,
isModerator,
};
module.exports = authJwt;
const config = require("../config/auth.config");
const db = require("../models");
const User = db.user;
const Role = db.role;
var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");
exports.signup = (req, res) => {
const user = new User({
username: req.body.email,
firstname: req.body.firstname,
lastname: req.body.lastname,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8),
});
user.save((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
if (req.body.roles) {
Role.find(
{
name: { $in: req.body.roles },
},
(err, roles) => {
if (err) {
res.status(500).send({ message: err });
return;
}
user.roles = roles.map((role) => role._id);
user.save((err,data) => {
if (err) {
res.status(500).send({ message: err });
return;
}
res.send({message: "User registered successfully?"});
});
}
);
} else {
Role.findOne({ name: "user" }, (err, role) => {
if (err) {
res.status(500).send({ message: err });
return;
}
user.roles = [role._id];
user.save((err,data) => {
if (err) {
res.status(500).send({ message: err });
return;
}
var token = jwt.sign({ id: data._id }, config.secret, {
expiresIn: 86400, // 24 hours
});
var authorities = [];
// This is what I mean
req.session.token = token;
res.status(200).send({
id: user._id,
username: user.username,
firstname: user.firstname,
lastname: user.lastname,
email: user.email,
});
//res.send({message: "User registered successfully? No 2"});
});
});
}
});
};
exports.signin = (req, res) => {
User.findOne({
username: req.body.email,
})
.populate("roles", "-__v")
.exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
var passwordIsValid = bcrypt.compareSync(
req.body.password,
user.password
);
if (!passwordIsValid) {
return res.status(401).send({ message: "Invalid Password!" });
}
var token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 86400, // 24 hours
});
var authorities = [];
for (let i = 0; i < user.roles.length; i++) {
authorities.push("ROLE_" + user.roles[i].name.toUpperCase());
}
req.session.token = token;
res.status(200).send({
id: user._id,
username: user.username,
firstname: user.firstname,
lastname: user.lastname,
email: user.email,
roles: authorities,
});
});
};
exports.signout = async (req, res) => {
try {
req.session = null;
return res.status(200).send({ message: "You've been signed out!" });
} catch (err) {
this.next(err);
}
};
exports.allAccess = (req, res) => {
res.status(200).send("Public Content.");
};
exports.userBoard = (req, res) => {
res.status(200).send("User Content.");
};
exports.adminBoard = (req, res) => {
res.status(200).send("Admin Content.");
};
exports.moderatorBoard = (req, res) => {
res.status(200).send("Moderator Content.");
};
Any guidance would be greatly appreciated!

Check if user exists in router before creation in database with Sequelize

I have a function to check if a user exists, and a function to create a new user in my User model.
What I want to do is call them in the router to check if a user with the email adress in req.body already exists.
If it does, I want to return a message, and if not, I want to create the user.
When I try to call the route in Postman, I get this error in node console :
node_modules/express/lib/response.js:257
var escape = app.get('json escape')
TypeError: Cannot read properties of undefined (reading 'get')
User model :
const Sequelize = require("sequelize");
const connexion = require("../database");
const User = connexion.define(
"users",
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
email: {
type: Sequelize.STRING(100),
allowNull: false,
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
},
{
freezeTableName: true
}
);
function checkUser(userEmail) {
const findUser = User.findOne({ where: { userEmail } }).catch((err) => {
console.log(err);
});
if (findUser) {
return res.json({ message: "Cette adresse email est déjà enregistrée" });
} else {
return false;
}
}
function createUser(userData) {
console.log(userData);
User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
module.exports = { createUser, checkUser };
user controller :
const createUser = require("../models/User");
const bcrypt = require("bcrypt");
const saltRounds = 10;
addUser = async (req, res) => {
try {
const userData = req.body;
console.log(req.body);
bcrypt.hash(userData.password, saltRounds, async function (err, hash) {
userData.password = hash;
const newUser = await createUser(req.body);
res.status(201).json({ newUser });
});
} catch (err) {
console.log(err);
res.status(500).json("Server error");
}
};
module.exports = addUser;
user router :
const express = require("express");
const router = express.Router();
const addUser = require("../controllers/userController");
const { checkUser } = require("../models/User");
router.post("/", async (req, res) => {
const { email } = req.body;
const alreadyExists = await checkUser(email);
if (!alreadyExists) {
addUser(req.body);
}
});
module.exports = router;
EDIT : Finally I'm trying a more simple way. I will do the check part directly into the createUser function.
But now, it creates the user even if the email already exists ^^
async function createUser(userData) {
console.log(userData);
const findUser = await User.findOne({ where: userData.email }).catch(
(err) => {
console.log(err);
}
);
findUser
? console.log(findUser)
: User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
i think the problem is with this part you are trying to use res but it doesn't exist in your checkUser function
if (findUser) {
return res.json({ message: "Cette adresse email est déjà enregistrée" });
} else {
return false;
}
try this instead
if (findUser) {
return true });
} else {
return false;
}
UPDATE to fix the problem of user creation if it already exists
async function createUser(userData) {
console.log(userData);
const findUser = await User.findOne({ where: userData.email }).catch(
(err) => {
console.log(err);
}
);
if(!findUser){
findUser
? console.log(findUser)
: User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
}
Problem solved by doing this (thanks super sub for your help):
async function createUser(userData) {
console.log(userData);
const email = userData.email;
const findUser = await User.findOne({ where: { email } }).catch((err) => {
console.log(err);
});
if (!findUser) {
User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
}

How to implement Node.JS model using Sequelize

I have a project with the below Model, Controller, and Route File for user. I am wanting to implement sequelize, which I have managed to partially achieve using the account files below, however, I am struggling to work out how to implement logging in and ensuring a request has a valid token usijng user.ensureToken which would become account.ensureToken.
I'm fairly new to Node.Js so I'm not even sure where to start
user.model.js
const bcrypt = require('bcrypt');
const sql = require("./db.js");
const HashBits = require("../config/auth.config.js");
const faker = require('faker');
// constructor
const User = function(user) {
this.first_name = user.first_name;
this.last_name = user.last_name;
this.mob_no = user.mob_no;
this.user_name = user.user_name;
this.password = user.password;
};
User.create = (newUser, result) => {
bcrypt.hash(newUser.password, HashBits.saltRounds, (err, hash) => {
newUser.password = hash;
sql.query("INSERT INTO users SET ?", newUser, (err, res) => {
if (err) {
// console.log("error: ", err);
result(err, null);
return;
}
newID = res.insertId;
// console.log("created user: ", { id: res.insertId, ...newUser });
result(null, { id: res.insertId, ...newUser });
});
});
};
User.authenticate = (user,result) => {
// sql.query(`SELECT * FROM customers WHERE id = ${customerId}`, (err, res) => {
sql.query(`SELECT * FROM users WHERE user_name = '${user.user_name}'`, (err, res) => {
// sql.query("SELECT * FROM users ", (err, res) => {
if (err) {
// console.log("error: ", err);
result(err, null);
return;
}
if(res.length !== 1){
// console.log("error: found multiple users");
result("error: found multiple users", null);
return;
}
// console.log("Found user: ",res[0]);
bcrypt.compare(user.password, res[0].password, function(err, res2) {
if(res2){
// console.log("Yes");
result(null,res[0]);
}else{
// console.log("On ya bike");
result("ERROR",null);
// return;
}
});
});
};
module.exports = User;
user.controller.js
const User = require("../models/user.model.js");
var jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
// Create and Save a new User
exports.create = (req, res) => {
// Validate request
if (!req.body) {
res.status(400).send({
message: "Content can not be empty!"
});
}
// Create a User
const user = new User({
first_name: req.body.first_name,
last_name: req.body.last_name,
mob_no: req.body.mob_no,
user_name: req.body.user_name,
password: req.body.password
});
// Save User in the database
User.create(user, (err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
else res.send(data);
});
};
exports.authenticate = (req,res) => {
if (!req.body) {
res.status(400).send({
message: "Content can not be empty!"
});
}
const user = new User({
user_name: req.body.user_name,
password: req.body.password
});
User.authenticate(user, (err,data) => {
if(err)
res.status(500).send({
message:
err.message || "Some error occurred while authenticating the User."
});
else {
var token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 86400 // 24 hours
});
// res.send(data);
res.status(200).send({
id: data.id,
username: data.user_name,
accessToken: token
});
}
});
};
exports.ensureToken = (req, res, next) => {
let token = req.headers["x-access-token"];
if (!token) {
return res.status(403).send({
message: "No token provided!"
});
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Unauthorized!"
});
}
req.userId = decoded.id;
next();
});
}
user.routes.js
module.exports = app => {
const users = require("../controllers/user.controller.js");
// Create a new User
app.post("/User", users.create);
// Login
app.post("/User/Login", users.authenticate);
};
account.model.js
const bcrypt = require("bcrypt");
module.exports = (sequelize, Sequelize) => {
const Account = sequelize.define("account", {
firstName: {
type: Sequelize.STRING
},
username: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING,
set(value){
const hash = bcrypt.hashSync(value, 10);
this.setDataValue('password', hash);
}
}
});
return Account;
};
account.controller.js
const db = require("../models");
const Account = db.accounts;
const Op = db.Sequelize.Op;
var jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
// Create and Save a new New
exports.create = (req, res) => {
// Validate request
if (!req.body.username) {
res.status(400).send({
message: "Content can not be empty!"
});
return;
}
// Create a Site
const account = {
firstName: req.body.firstName,
username: req.body.username,
password: req.body.password
};
Account.create(account)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Account."
});
});
};
exports.authenticate = (req,res) => {
if (!req.body) {
res.status(400).send({
message: "Content can not be empty!"
});
}
const account = new Account({
username: req.body.username,
password: req.body.password
});
};
account.routes.js
module.exports = app => {
const accounts = require("../controllers/account.controller.js");
var router = require("express").Router();
app.post("/account", accounts.create);
app.post("/account/Login", accounts.authenticate);
};
you need to use jwt token for access token as you said and you are bcrypt password in model file which will be security issue you have to bcrypt password as soon as it comes in request I have implemented it in my answer remove code of password bcrypt from your model file
you have to import in your account.controller.js
const db = require("../models");
const User = db.user;
require('dotenv').config();
const Op = db.Sequelize.Op;
const errors = require('../config/errors');
const error = errors.errors;
var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");
module.exports = {
signup: async (req, res) => {
if (!req.body.first_name|| !req.body.lastt_name || !req.body.password) {
return res.status(200).send(error.MANDATORY_FIELDS);
}
try {
// Save User to Database
User.create({
name: req.body.name,
email: req.body.email,
mo_no: req.body.mo_no,
city: req.body.city,
password: bcrypt.hashSync(req.body.password, 8),
user_type: "admin"
}).then(function (user) {
return res.status(200).send(error.OK)
})
.catch(function (err) {
console.log(err);
return res.status(500).send(error.SERVER_ERROR)
});
} catch (e) {
console.log(e);
return res.status(500).send(error.SERVER_ERROR)
}
},
signin: async (req, res) => {
if (!req.body.email || !req.body.password) {
return res.status(200).send(error.MANDATORY_FIELDS);
}
User.findOne({
where: {
email: req.body.email
}
}).then(function (user) {
if (!user) {
return res.status(404).send(error.USER_NOT_PRESENT);
}
const passwordIsValid = bcrypt.compareSync(
req.body.password,
user.password
);
if (!passwordIsValid) {
return res.status(422).send(error.PASSWORD_MISSMATCH, {
accessToken: null
});
}
const token = jwt.sign({ id: user.id, first_name: user.first_name },
process.env.secret, {
expiresIn: 86400 // 24 hours
});
const authorities = [];
return res.status(200).send({
id: user.id,
name: user.name,
email: user.email,
accessToken: token
});
});
})
.catch(function (err) {
console.log(err)
return res.status(500).send(error.SERVER_ERROR);
});
}
}
than you have to create a separate folder for authorization like authorize.js or authJwt.js where you have to check is token is valid or not put this code in authorize.js
at decoding time secret token or password also needed which you have in .env file
const jwt = require("jsonwebtoken");
verifyToken = (req, res, next) => {
let token = req.headers["x-access-token"];
if (!token) {
return res.status(403).send(error.TOKEN_NOT_PROVIDED);
}
jwt.verify(token, process.env.secret, (err, decoded) => {
if (err) {
return res.status(401).send(error.UNAUTHORIZED);
}
req.first_name= decoded.first_name;
req.id = decoded.user_id
next();
});
};
const authJwt = {
verifyToken: verifyToken
};
module.exports = authJwt;
than you have to import authorize.js file in your routes whenever you want like this
const authorize = require('../authorize.js');
module.exports = app => {
const accounts = require("../controllers/account.controller.js");
var router = require("express").Router();
app.post("/account", accounts.create);
app.post("/account/Login",
authorize.verifyToken,accounts.authenticate);
};
it will be more effective if you genreate access token at signin time

Resources