Admin role to add category in Express API design - node.js

Hey I am testing on postman as an admin to add category on my project, I have successfully created admin user and login, but when I tried to add category, postman say: TypeError: Cannot read properties of undefined (reading 'role') can anyone help?
Here is my user model:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
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",
},
},
{ timestamps: true }
);
module.exports = mongoose.model("User", userSchema);
here is my auth middleware:
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const { signupUser, loginUser } = require("../controller/adminauth");
exports.auth = (req, res, next) => {
try {
if (req.header.authorization) {
const token = req.header.authorization.split("")[1];
const isCustomAuth = token.length < 500;
let decodeData;
if (token && isCustomAuth) {
decodeData = jwt.verify(token, process.env.JWT_SECRET);
req.UserId = decodeData?.id;
} else {
decodeData = jwt.decode(token);
req.UserId = decodeData?.sub;
}
}
} catch (error) {
console.log(error);
// res.status(400).json({ message: "Authorization required" });
} next ()
};
exports.adminMiddleware = (req, res, next) => {
if (!req.userId.role === "admin") {
return res.status(400).json({ message: "Access denied" });
}
next();
};
Here is my admin auth controller:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const existingUser = await User.findOne({ email });
if (!existingUser) {
return res.status(400).json({ message: "User does not exists." });
}
if (!existingUser.role === "admin") {
return res.status(400).json({ message: "User is not admin." });
}
const isPasswordCorrect = await bcrypt.compare(
password,
existingUser.password
);
if (!isPasswordCorrect)
return res.status(400).json({ message: "Invalid credentials." });
const token = jwt.sign(
{
email: existingUser.email,
id: existingUser._id,
role: existingUser.role,
},
process.env.JWT_SECRET,
{ expiresIn: "3d" }
);
res.status(200).json({ result: existingUser, token });
} catch (error) {
console.log(error);
}
};
exports.signupUser = async (req, res) => {
const { firstName, lastName, email, password, confirmPassword } = req.body;
try {
const existingUser = await User.findOne({ email });
if (existingUser)
return res.status(400).json({ message: "Admin already exists." });
if (!password == confirmPassword)
return res.status(400).json({ message: "Password don't match" });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await User.create({
email,
password: hashedPassword,
firstName,
lastName,
role: "admin",
});
const token = jwt.sign(
{ email: result.email, id: result._id, role: result.role },
process.env.JWT_SECRET,
{ expiresIn: "3d" }
);
res.status(200).json({ result, token });
} catch (error) {
console.log(error);
}
};
Here is my category route:
const express = require("express");
const { addCategory, getCategories } = require("../controller/category");
const { auth, adminMiddleware } = require("../middleware/auth");
const router = express.Router();
router.post("/category/create", auth, adminMiddleware, addCategory);
router.get("/category/getcategory", getCategories);
module.exports = router;

In your auth middleware,
change your exports.auth with the following code:
exports.auth = (req, res, next) => {
try {
if (req.header.authorization) {
const token = req.header.authorization.split("")[1];
const isCustomAuth = token.length < 500;
let decodeData;
if (token && isCustomAuth) {
decodeData = jwt.verify(token, process.env.JWT_SECRET);
req.UserId = decodeData||{}; //change this line
} else {
decodeData = jwt.decode(token);
req.UserId = decodeData?.sub;
}
}
} catch (error) {
console.log(error);
res.status(400).json({ message: "Authorization required" });
} next ()
};

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.

JWT token signup and signin express API

I am trying postman for signup a user, {"firstName": "John", "lastName":"zoe", "email":"aaa#gmail.com", "password":"123465"} but the postman gives me this 500 error: {
"message": "Something went wrong"
},
I could not figure out is my logic wrong, or something is missing, I did not use the validator package, as I am not sure how to use it, is that the problem? can anyone pls help?
here is my code, in the server.js file:
const express = require("express");
const env = require("dotenv");
const { response } = require("express");
const app = express();
const mongoose = require("mongoose");
//routes
const authRoutes = require("./routes/auth");
const adminRoutes = require("./routes/adminauth");
const categoryRoutes = require("./routes/category");
//enviorment variables
env.config();
app.use(express.json());
mongoose
.connect(
`mongodb+srv://${process.env.MONGO_DB_USER}:${process.env.MONGO_DB_PASSWORD}#cluster0.h28xczp.mongodb.net/${process.env.MONGODB_DATABASE}?retryWrites=true&w=majority`
)
.then(() => {
console.log("Database connection established");
});
app.use("/api", authRoutes);
app.use("/api", adminRoutes);
app.use("/api", categoryRoutes);
app.listen(process.env.PORT, () => {
console.log(`server is running at ${process.env.PORT}`);
});
In my routes file:
const express = require("express");
const router = express.Router();
const { signupUser, loginUser } = require("../controller/auth");
const { auth, userMiddleware, adminMiddleware } = require("../middleware/auth");
//login route
router.post("/login", loginUser);
//signup route
router.post("/signup", signupUser);
module.exports = router;
Middleware file:
const jwt = require("jsonwebtoken");
const User = require("../models/user");
exports.auth = (req, res, next) => {
try {
const token = req.header.authorization.split("")[1];
const isCustomAuth = token.length < 500;
let decodeData;
if (token && isCustomAuth) {
decodeData = jwt.verify(token, env.Process.JWT_SECRET);
req.UserId = decodeData?.id;
} else {
decodeData = jwt.decode(token);
req.UserId = decodeData?.sub;
}
next();
} catch (error) {}
};
exports.userMiddleware = (req, res, next) => {
if (req.user.role !== "user") {
return res.status(400).json({ message: "User access denied" });
}
next();
};
exports.adminMiddleware = (req, res, next) => {
if (req.user.role !== "admin") {
return res.status(400).json({ message: "Access denied" });
}
next();
};
In my controller file:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const existingUser = await User.findOne({ email });
if (!existingUser)
return res.status(400).json({ message: "User does not exists." });
const isPasswordCorrect = await bcrypt.compare(
password,
existingUser.password
);
if (!isPasswordCorrect)
return res.status(400).json({ message: "Invalid credentials." });
const token = jwt.sign(
{ email: existingUser.email, id: existingUser._id },
process.env.JWT_SECRET,
{ expiresIn: "3d" }
);
res.status(200).json({ result: existingUser, token });
} catch (error) {
res.status(500).json({ message: "Something went wrong" });
}
};
exports.signupUser = async (req, res) => {
const { firstName, lastName, email, password, confirmPassword } = req.body;
try {
const existingUser = await User.findOne({ email });
if (existingUser)
return res.status(400).json({ message: "User already exists." });
if (!password == confirmPassword)
return res.status(400).json({ message: "Password don't match" });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await User.create({
email,
password: hashedPassword,
firstName,
lastName,
});
const token = jwt.sign(
{ email: result.email, id: result._id },
process.env.JWT_SECRET,
{ expiresIn: "3d" }
);
res.status(200).json({ result, token });
} catch (error) {
res.status(500).json({ message: "Something went wrong" });
}
};
My user model file:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
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,
},
id: {
type: String,
},
},
{ timestamps: true }
);
module.exports = mongoose.model("User", userSchema);
In the middleware, this line contains a wrong reference to JWT_SECRET.
decodeData = jwt.verify(token, env.Process.JWT_SECRET);
Should be
decodeData = jwt.verify(token, process.env.JWT_SECRET);
The application throws an unhandled promise rejection error when trying to connect DB, which means it can operate without a DB connection and then throw that error.
So, to handle that, you can rewrite your code to this.
mongoose.connect('mongodb://localhost:27017/usersdb', // change with your db url
{
useNewUrlParser: true,
useUnifiedTopology: true
}
)
.then(() => {
app.use("/api", authRoutes);
app.listen(process.env.PORT, () => {
console.log("Server has started on port!", process.env.PORT)
})
})
.catch(() => { throw new Error(("Connection error")) });
Also, I successfully ran and tested your application on my local machine. Here is a GitHub link; you can compare.
https://github.com/nairi-abgaryan/express-auth

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).

When i am logout then i cannot login with same email & password, showing me error jwt

*I can create new account and also i can login but when i am going to logout that time i will logout but if i am try to login with same email and password but i cannot able to login it is showing me jsonwebtoken error -> {"name":"JsonWebTokenError","message":"jwt must be provided"} *
register.js
This is my register.js file code
app.get("/register", (req, res) => {
// res.send('Hello Arunesh')
res.render("register");
});
app.post("/register", async (req, res) => {
try {
const password = req.body.password;
const cPassword = req.body.cPassword;
if (password === cPassword) {
const registerData = new Register({
firstName: req.body.firstName,
lastName: req.body.lastName,
phone: req.body.phone,
gender: req.body.gender,
email: req.body.email,
age: req.body.age,
password: req.body.password,
confirmPassword: req.body.cPassword,
});
const token = await registerData.generateAuthToken();
console.log('Register Token : ',token);
res.cookie('jwt', token,{
httpOnly:true
})
const register = await registerData.save();
console.log(register);
res.status(201).render("index");
} else {
res.send("Password are not match");
}
} catch (e) {
res.status(400).send(e);
}
});
login.js
app.get("/login", (req, res) => {
res.render("login");
});
app.post("/login", auth,async (req, res) => {
try {
const email = req.body.email;
const password = req.body.password;
const userEmail = await Register.findOne({ email: email });
const isMatch = await bcrypt.compare(password, userEmail.password);
const token = await userEmail.generateAuthToken();
res.cookie('jwt', token,{
expires:new Date(Date.now()+30000000),
httpOnly:true
})
console.log('Login Token : ',token);
console.log(isMatch);
if (isMatch) {
res.status(201).render("index");
} else {
res.send("Invalid password or email");
}
} catch (e) {
res.status(400).send(e);
}
});
Logout.js
app.get('/logout', auth,async (req, res)=>{
try{
// console.log(req.user);
// Logout for single user
req.user.tokens = req.user.tokens.filter((authToken)=>{
return authToken.token != req.token;
})
// logout from all device
// req.user.tokens = [];
res.clearCookie("jwt");
await req.user.save();
res.render('login');
}catch(e){
console.log(e);
}
})
auth.js
const jwt = require('jsonwebtoken');
const Register = require('../models/registers');
const auth = async (req, res, next)=>{
try{
const token = req.cookies.jwt;
const verifyUser = jwt.verify(token, process.env.SECRET_KEY);
const user = await Register.findOne({_id:verifyUser._id, 'tokens.token':token})
req.token = token;
req.user = user;
next()
}catch(e){
res.send(e)
}
}
module.exports = auth;
**
generateAuthToken
**
registerSchema.methods.generateAuthToken = async function () {
try {
const token = jwt.sign({ _id: this._id.toString() },process.env.SECRET_KEY);
this.tokens = this.tokens.concat({token:token})
await this.save();
return token;
} catch (e) {
console.log(e);
}
};
**
Schema
**
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const registerSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
phone: {
type: Number,
required: true,
unique: true,
},
gender: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
age: {
type: Number,
required: true,
},
password: {
type: String,
required: true,
},
confirmPassword: {
type: String,
required: true,
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
});
/********************************************
* Generate Token
********************************************/
registerSchema.methods.generateAuthToken = async function () {
try {
const token = jwt.sign({ _id: this._id.toString() },process.env.SECRET_KEY);
this.tokens = this.tokens.concat({token:token})
await this.save();
return token;
} catch (e) {
console.log(e);
}
};
/********************************************
* Password Hash
********************************************/
registerSchema.pre("save", async function (next) {
if (this.isModified("password")) {
this.password = await bcrypt.hash(this.password, 10);
this.confirmPassword = await bcrypt.hash(this.password, 10);
}
next();
});
const Register = new mongoose.model("Register", registerSchema);
module.exports = Register;
you use auth middleware in /login route, this error happens when the coming token is null or empty ,remove auth middleware from the route like this, I hope the problem is solved with my answer
app.post("/login" , async (req, res) => {...

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

Resources