not able to work with express auth middleware - node.js

I want to use auth middleware in expressjs with role based auth check or without role based.
after login, i am passing access token as bearer token, but authentication doesn't seems to work.
this is auth middleware
const passport = require('passport');
const httpStatus = require('http-status');
const ApiError = require('../utils/ApiError');
const { roleRights } = require('../config/roles');
const verifyCallback = (req, resolve, reject, requiredRights) => async (err, user, info) => {
if (err || info || !user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
req.user = user;
if (requiredRights.length) {
const userRights = roleRights.get(user.role);
const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight));
if (!hasRequiredRights && req.params.userId !== user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
}
resolve();
};
const auth = (...requiredRights) => async (req, res, next) => {
return new Promise((resolve, reject) => {
passport.authenticate('jwt', { session: false }, verifyCallback(req, resolve, reject, requiredRights))(req, res, next);
})
.then(() => next())
.catch((err) => next(err));
};
module.exports = auth;
here in router, i am using middleware like this.
const express = require('express');
const auth = require('../../middlewares/auth');
const validate = require('../../middlewares/validate');
const userValidation = require('../../validations/user.validation');
const userController = require('../../controllers/user.controller');
const router = express.Router();
router.get('/', auth(), userController.getUsers);
module.exports = router;
here i am passing accesstoken like this in request
GET http://localhost:5000/v1/users HTTP/1.1
Authorization: Bearer *****************(accesstoken-returned-after-login)************************
access token expire time is set to 30 minutes, whenever i pass access token in this request,
i am only getting response "Please Authenticate".
**
passport.js in config
**
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const config = require('./config');
const { tokenTypes } = require('./tokens');
const { User } = require('../models');
const jwtOptions = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
};
const jwtVerify = async (payload, done) => {
try {
if (payload.type !== tokenTypes.ACCESS) {
throw new Error('Invalid token type');
}
const user = await User.findById(payload.sub);
if (!user) {
return done(null, false);
}
done(null, user);
} catch (error) {
done(error, false);
}
};
const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify);
module.exports = {
jwtStrategy,
};
this is user collection schema
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const { toJSON, paginate } = require('./plugins');
const { roles } = require('../config/roles');
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Invalid email');
}
},
},
password: {
type: String,
required: true,
trim: true,
minlength: 8,
validate(value) {
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
throw new Error('Password must contain at least one letter and one number');
}
},
private: true, // used by the toJSON plugin
},
role: {
type: String,
enum: roles,
default: 'employee',
},
isEmailVerified: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
userSchema.plugin(toJSON);
userSchema.plugin(paginate);
userSchema.statics.isEmailTaken = async function (email, excludeUserId) {
const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
return !!user;
};
userSchema.methods.isPasswordMatch = async function (password) {
const user = this;
return bcrypt.compare(password, user.password);
};
userSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8);
}
next();
});
const User = mongoose.model('User', userSchema);
module.exports = {
User
};

I just had issue in importing collection schema in my passport.js file. i solved it by doing in passport.js
const { User } = require('../models/user.model');
and everything works as expected.

Related

Userschema and controller

I am building a user signup and login api and admin signup and login using express and currently I am testing in the postman, but somehow postman keeps return "error": "firstName is not defined" even though I posted firstname etc. here is my code, can anyone help me to explain it what is wrong? I saw so many videos using all different kinds of method, like generateAuthtakoken in the user.model or joi password library, it is just so overwhelming, can you help me to point to a direction as to how to use express to create ?
this is my user.model file:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const validator = require("validator");
const userSchema = new mongoose.Schema(
{
firstName: {
type: String,
required: true,
trim: true,
},
lastName: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
required: true,
trim: true,
unique: true,
},
password: {
type: String,
required: true,
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
contactNumber: { type: String },
profilePicture: { type: String },
},
{ timestamps: true }
);
//static signup method
userSchema.statics.signup = async function (email, password) {
//validation
if (!firstName || !lastName || !email || !password) {
throw Error("All fields must be filled");
}
if (!validator.isEmail(email)) {
throw Error("Email is not valid");
}
if (!validator.isStrongPassword(password)) {
throw Error("Password is not strong enough");
}
const exists = await this.findOne({ email });
if (exists) {
throw Error("Email already in use");
}
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt);
const user = await this.create({ email, password: bcrypt.hash });
return user;
};
//static login method
userSchema.statics.login = async function (email, password) {
if (!firstName || !lastName || !email || !password) {
throw Error("All fields must be filled");
}
const user = await this.findOne({ email });
if (!user) {
throw Error("Incorrect Email");
}
const match = await bcrypt.compare(password, user.password);
if (!match) {
throw Error("Incorrect password");
}
return user;
};
module.exports = mongoose.model("User", userSchema);
this is my controller file:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const createToken = (_id) => {
jwt.sign({ _id }, process.env.JWT_SECRET, { expiresIn: "3d" });
};
//login user
const loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.login(email, password);
// create token
const token = createToken(user._id);
res.status(200).json({ email, token });
} catch (error) {
res.status(400).json({ error: error.message });
}
res.json({ msg: "login user" });
};
//signup user
const signupUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.signup(email, password);
// create token
const token = createToken(user._id);
res.status(200).json({ email, token });
} catch (error) {
res.status(400).json({ error: error.message });
}
res.json({ msg: "login user" });
};
module.exports = { signupUser, loginUser };
and my router file:
const express = require("express");
const router = express.Router();
const { signupUser, loginUser } = require("../controller/auth");
//login route
router.post("/login", loginUser);
//signup route
router.post("/signup", signupUser);
module.exports = router;
where exactly do you get this error. Please provide full details to regenerate this error.
But as i could guess
In your static login method you do not need firstName and LastName.
In your signup user method you should be passing those missing required db fields as in your model.

findOneByIdAndUpdate mongodb and express.js

I am trying to wrote RESTFull api with express and mongodb . sections for registraion and login are work properly but when I trying to updatOneById it dosent work .and send you can update only uor accout i delete if cluse but it still same and doesnt work .
index.js
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const helmet = require("helmet");
const morgan = require("morgan");
const userRoute = require("./routes/users");
const authRoute = require("./routes/auth");
dotenv.config();
mongoose.connect(
process.env.ACCESS_KEY,
{ useNewUrlParser: true, useUnifiedTopology: true },
() => {
console.log("Connected to MongoDB");
}
);
//middleware
app.use(express.json());
app.use(helmet());
app.use(morgan("common"));
app.use("/api/auth", authRoute);
app.use("/api/users", userRoute);
app.listen(8800, () => {
console.log("Backend server is running!");
});
and these are my routers
first is Auth.js
const router = require("express").Router();
const User = require("../models/User");
const bcrypt = require("bcrypt");
//REGISTER
router.post("/register", async (req, res) => {
try {
//generate new password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt);
//create new user
const newUser = new User({
username: req.body.username,
email: req.body.email,
password: hashedPassword,
});
//save user and respond
const user = await newUser.save();
res.status(200).json(user);
} catch (err) {
res.status(500).json(err)
}
});
//LOGIN
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
!user && res.status(404).json("user not found");
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
});
module.exports = router;
seconde is user router
const User = require("../models/User");
const router = require("express").Router();
const bcrypt = require("bcrypt");
//update user
router.put("/:id", async (req, res) => {
if (req.body.userId === req.params.id || req.body.isAdmin) {
if (req.body.password) {
try {
const salt = await bcrypt.genSalt(10);
req.body.password = await bcrypt.hash(req.body.password, salt);
} catch (err) {
return res.status(500).json(err);
}
}
try {
const user = await User.findByIdAndUpdate(req.params.id, {
$set: req.body,
});
res.status(200).json("Account has been updated");
} catch (err) {
return res.status(500).json(err);
}
} else {
return res.status(403).json("You can update only your account!");
}
});
//delete user
router.delete("/:id", async (req, res) => {
if (req.body.userId === req.params.id || req.body.isAdmin) {
try {
await User.findByIdAndDelete(req.params.id);
res.status(200).json("Account has been deleted");
} catch (err) {
return res.status(500).json(err);
}
} else {
return res.status(403).json("You can delete only your account!");
}
});
//get a user
router.get("/:id", async (req, res) => {
try {
const user = await User.findById(req.params.id);
const { password, updatedAt, ...other } = user._doc;
res.status(200).json(other);
} catch (err) {
res.status(500).json(err);
}
});
//follow a user
router.put("/:id/follow", async (req, res) => {
if (req.body.userId !== req.params.id) {
try {
const user = await User.findById(req.params.id);
const currentUser = await User.findById(req.body.userId);
if (!user.followers.includes(req.body.userId)) {
await user.updateOne({ $push: { followers: req.body.userId } });
await currentUser.updateOne({ $push: { followings: req.params.id } });
res.status(200).json("user has been followed");
} else {
res.status(403).json("you allready follow this user");
}
} catch (err) {
res.status(500).json(err);
}
} else {
res.status(403).json("you cant follow yourself");
}
});
//unfollow a user
router.put("/:id/unfollow", async (req, res) => {
if (req.body.userId !== req.params.id) {
try {
const user = await User.findById(req.params.id);
const currentUser = await User.findById(req.body.userId);
if (user.followers.includes(req.body.userId)) {
await user.updateOne({ $pull: { followers: req.body.userId } });
await currentUser.updateOne({ $pull: { followings: req.params.id } });
res.status(200).json("user has been unfollowed");
} else {
res.status(403).json("you dont follow this user");
}
} catch (err) {
res.status(500).json(err);
}
} else {
res.status(403).json("you cant unfollow yourself");
}
});
module.exports = router;
**and this is my schema model **
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema(
{
username: {
type: String,
require: true,
min: 3,
max: 20,
unique: true,
},
email: {
type: String,
required: true,
max: 50,
unique: true,
},
password: {
type: String,
required: true,
min: 6,
},
profilePicture: {
type: String,
default: "",
},
coverPicture: {
type: String,
default: "",
},
followers: {
type: Array,
default: [],
},
followings: {
type: Array,
default: [],
},
isAdmin: {
type: Boolean,
default: false,
},
desc: {
type: String,
max: 50,
},
city: {
type: String,
max: 50,
},
from: {
type: String,
max: 50,
},
relationship: {
type: Number,
enum: [1, 2, 3],
},
},
{ timestamps: true }
);
module.exports = mongoose.model("User", UserSchema);
finally I find that .all of my code is ok but the problem is my cluster on mongodb.
you have to delete your cluster and run it again the problem will be solved .

Mongoose validation error, "email is not defined"

I am new to mongoose and express. I try to create a simple login backend, however when send a post request with
{
"userEmail": "abc#xyz", "password": "pswrd"
}
I get "email is not defined" error whose type is "VALIDATION". My User Schema is as follows:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "Email is required"],
trim: true,
unique: true,
},
password: {
type: String,
trim: true,
required: [true, "Password is required"],
},
username: {
type: String,
required: [true, "Username is required"],
trim: true,
unique: true,
},
});
UserSchema.pre("save", async function (next) {
const user = await User.findOne({ email: this.email });
if (user) {
next(new Error(`${this.email} already taken`));
return;
}
const user1 = await User.findOne({ username: this.username });
if (user1) {
next(new Error(`${this.username} already taken`));
return;
}
const salt = await bcrypt.genSalt(8);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// userSchema.statics is accessible by model
UserSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email });
if (!user) {
throw Error("User does not exist.");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
throw Error("Unable to login");
}
return user;
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
I use findByCredentials to check if the User is in my mongoDB database or not. Finally, my login.js is as follows:
const express = require("express");
const mongoose = require("mongoose");
const User = require("../db/models/User");
const loginRouter = express.Router();
loginRouter.get("/api/login2", (req, res) => res.send("In Login"));
loginRouter.post("/api/login", async (req, res) => {
const { userEmail, password} = req.body;
if (!validateReqBody(userEmail, password)) {
return res
.status(401)
.send({ status: false, type: "INVALID", error: "invalid request body" });
}
try {
const newUser = new User({
email: userEmail,
password: password,
});
await newUser.findByCredentials(email, password);
} catch (error) {
const validationErr = getErrors(error);
console.log(validationErr);
return res
.status(401)
.send({ status: false, type: "VALIDATION", error: validationErr });
}
res.send({ status: true });
});
//user.find --> mongoose documentation
// Validates request body
const validateReqBody = (...req) => {
for (r of req) {
if (!r || r.trim().length == 0) {
return false;
}
}
return true;
};
// Checks errors returning from DB
const getErrors = (error) => {
if (error instanceof mongoose.Error.ValidationError) {
let validationErr = "";
for (field in error.errors) {
validationErr += `${field} `;
}
return validationErr.substring(0, validationErr.length - 1);
}
return error.message;
};
module.exports = { loginRouter };
Thank you.
You need to use body-parser middleware in backend
const bodyParser = require('body-parser');
const express = require('express');
const app = express();
//bodypraser middleware
app.use(bodyParser.json());
You can read more about bodyparser here
Happened to me once, it was really annoying. I don't know If it would help you, but try sending the post request with headers: { 'Content-Type': 'application/json' }, using fetch.
Definition of findByCredentials() is in User model. I was trying to reach that function by the object instance newUser that i created in login.js. However, i should have called the function as User.findByCredentials(email, password).

TypeError: Cannot read property 'authenticate' of null

I am working in user signin authentication in backend. Whenever I hit send request from postman it shows error as
TypeError: Cannot read property 'authenticate' of null
at /home/saru/mernbootcamp/projbackend/controllers/auth.js
I had check this error in stackoverflow but the solution doesn't match my case
controllers/auth.js
`const User = require("../models/user");
//express-validator
const { check, validationResult } = require('express-validator');
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
const dotenv = require("dotenv")
const config = dotenv.config({ path: './routes/.env' });
//user object creation for class/model User
const user = new User(req.body);
exports.signin = (req, res) => {
const errors = validationResult(req);
const { email, password } = req.body;
if (!errors.isEmpty()) {
return res.status(422).json({
error: errors.array()[0].msg
});
}
User.findOne({ email }, (err, user) => {
if (err) {
return res.status(400).json({
error: "USER email does not exists"
});
}
console.log(password);
if (!user.authenticate(password)) {
return res.status(401).json({
error: "Email and password do not match"
});
}
//create token
const token = jwt.sign({ _id: user._id }, process.env.SECRET);
//put token in cookie
res.cookie("token", token, { expire: new Date() + 9999 });
//send response to front end
const { _id, name, email, role } = user;
return res.json({ token, user: { _id, name, email, role } });
});
};
`
models/user.js
`var mongoose = require("mongoose");
const crypto = require("crypto");
const uuidv1 = require("uuid/v1");
var userSchema = new mongoose.Schema(
{
email: {
type: String,
trim: true,
required: true,
unique: true
},
encry_password: {
type: String,
required: true
},
salt: String,
},
{ timestamps: true }
);
userSchema
.virtual("password")
.set(function (password) {
this._password = password;
this.salt = uuidv1();
this.encry_password = this.securePassword(password);
})
.get(function () {
return this._password;
});
userSchema.method = {
authenticate: function (plainpassword) {
return this.securePassword(plainpassword) === this.encry_password;
},
securePassword: function (plainpassword) {
if (!password) return "";
try {
return crypto
.createHmac("sha256", this.salt)
.update(plainpassword)
.digest("hex");
} catch (err) {
return "";
}
}
};
module.exports = mongoose.model("User", userSchema);
`
In controllers/auth.js
Replace the if(err) with if(err || !user) in the findOne() method
You can use this code:
User.findOne({email}, (err, user) => {
if (err || !user) {
return res.status(400).json({
error: "USER email does not exists"
})
}
});
Replace if (err) with if (err || !user) and use return before res.status

Result not getting stored in Google datastore DB

Not able to save the data in Google Datastore DB not getting any error, can somebody help me to find the fix
Console.log result as below
entityKey: Key { namespace: undefined, kind: 'User', path: [Getter] },
entityData:
{ firstname: 'Abcd',
lastname: 'Abcd',
email: 'abcd#gmail.com',
password: '123454',
createdOn: 'Abcd',
[Symbol(KEY)]: Key { namespace: undefined, kind: 'User', path: [Getter] } },
Ref - https://www.npmjs.com/package/gstore-node
const express = require('express');
const router = express.Router();
const { check, validationResult } = require('express-validator');
var User =require('../models/user');
//get register page
router.get('/register',function(req,res){
res.render('register')
});
//get login page
router.get('/login',function(req,res){
res.render('login')
});
router.post('/register', [
check('Name').isEmpty().withMessage('The Name is required'),
check('Email').isEmail().withMessage('Email is requried'),
//check('Password').isEmpty().withMessage('pass is requried'),
//check('Password','Password is Requried').isEmpty(),
// check('Password2','Password Not Match').equals('password2'),
], (req, res,next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.render('register',{
error:errors.mapped()
})
}else{
console.log()
const newUser = new User ({
firstname:req.body.name,
lastname:req.body.name,
email :req.body.Email,
password :req.body.Password,
createdOn:req.body.name
});
console.log("Data1",newUser)
const createUser = (req, res) => {
const entityData = User.sanitize(req.body);
const user = new User(entityData);
console.log("Data2",createUser)
user.save()
.then((entity) => {
res.json(entity.plain());
})
.catch((err) => {
// If there are any validation error on the schema
// they will be in this error object
res.status(400).json(err);
})
};
req.flash('success_msg','you are registered and can login now');
res.redirect('/users/login');
}
});
module.exports=router;
const { Gstore, instances } = require('gstore-node');
const { Datastore } = require('#google-cloud/datastore');
const gstore = new Gstore();
const datastore = new Datastore({
projectId: 'sinuous250616',
});
gstore.connect(datastore);
// Save the gstore instance
instances.set('unique-id', gstore);
const bcrypt = require('bcrypt');
// Retrieve the gstore instance
const ggstore = instances.get('unique-id');
const { Schema } = ggstore;
/**
* A custom validation function for an embedded entity
*/
const validateAccessList = (value, validator) => {
if (!Array.isArray(value)) {
return false;
}
return value.some((item) => {
const isValidIp = !validator.isEmpty(item.ip) && validator.isIP(item.ip, 4);
const isValidHostname = !validator.isEmpty(item.hostname);
return isValidHostname && isValidIp;
});
}
//Create the schema for the User Model
const userSchema = new Schema({
firstname: { type: String, required: true },
lastname: { type: String, optional: true },
email: { type: String, validate: 'isEmail', required: true },
password: { type: String, read: false, required: true },
createdOn: { type: String, default: gstore.defaultValues.NOW, write: false, read: false }
});
/**
* List entities query shortcut
*/
const listSettings = {
limit: 15,
order: { property: 'lastname' }
};
userSchema.queries('list', listSettings);
/**
* Pre "save" middleware
* Each time the entity is saved or updated, if there is a password passed, it will be hashed
*/
function hashPassword() {
// scope *this* is the entity instance
const _this = this;
const password = this.password;
if (!password) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
bcrypt.genSalt(5, function onSalt(err, salt) {
if (err) {
return reject(err);
};
bcrypt.hash(password, salt, null, function onHash(err, hash) {
if (err) {
// reject will *not* save the entity
return reject(err);
};
_this.password = hash;
// resolve to go to next middleware or save method
return resolve();
});
});
});
// add the "pre" middleware to the save method
userSchema.pre('save', hashPassword);
/**
* Export the User Model
* It will generate "User" entity kind in the Datastore
*/
module.exports = gstore.model('User', userSchema);
*I think there is a problem with User model **
You should have a User model like this in /models/user.js (put models at the root of your application) to define User:
const { instances } = require('gstore-node');
const bscrypt = require('bcrypt-nodejs');
// Retrieve the gstore instance
const gstore = instances.get('unique-id');
const { Schema } = gstore;
var usersSchema = new Schema({
firstname:{type:String},
lastname:{type:String},
email:{type:String},
password :{type:String},
createdOn: Date
})
var User = gstore.model('User', usersSchema);
module.exports = User;
And you forgot to use to save with save()
var newUser = new User ({
firstname:req.body.name,
lastname:req.body.name,
email :req.body.Email,
password :req.body.Password,
createdOn: new Date() // there is a problem here.... use new Date()
});
newUser.save(); //<======= it is abscent so it won't save

Resources