NextJs & Mongoose putting a jwt in database - node.js

I tryed myself on a little NextJS App with Mongoose and JWT (json web token).
Everything works and I can put user into my database and other stuff. Just when I create a JWT I can't put it into my database. Here is my code, grateful for every help :D
First my schema for mongoose:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Please add a title'],
unique: true,
maxlength: [15, 'Username cannot be more than 15 characters']
},
password: {
type: String,
required: true,
maxlength: [100, 'Password cannot be more than 100 characters']
},
jwt: {
type: String,
required: true,
maxlength: [1000, 'JWT cannot be more than 200 characters']
}
})
module.exports = mongoose.models.User || mongoose.model('User', UserSchema);
I don't know if type: String for jwt is correct, but JSON looks weird too and doesn't work.
Now my backend API Code:
import dbConnect from '../../../utils/dbConnect';
import User from '../../../models/User';
import sjcl from 'sjcl';
var jwt = require('jsonwebtoken');
const hashword = "censored"
dbConnect();
export default async (req, res) => {
var passwordtohash = req.body.password + hashword
const BitArrayHash = sjcl.hash.sha256.hash(passwordtohash);
const passwordhashed = sjcl.codec.hex.fromBits(BitArrayHash)
var bodywithhash = req.body
bodywithhash.password = passwordhashed
const { method } = req;
switch(method) {
case 'POST':
try {
const user = await User.find({"username": bodywithhash.username, "password": bodywithhash.password});
if (user.length > 0) {
createJWT(user);
res.status(200).json({ success: true })
} else (res.status(200).json({ success: false}))
} catch (error) {
res.status(400).json({ success: false });
}
break;
default:
res.status(400).json({ success: false });
}
}
async function createJWT(user) {
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret')
const iduser = { "_id" : user[0]._id.toString()}
const updateuser = await User.findByIdAndUpdate(iduser, jwt);
}
All my users in the database have a default value for JWT called "default"

Related

How can I fix : "Cannot read properties of undefined( reading 'role')" I believe its a mongodb/mongoose issue. How can I fix database request issue?

I am currently getting problems reading role on my postman POST request on backend nodejs express. I was wondering how would i go about fixing this?
below is the route for authentication in my middleware/auth.js
exports.isAuthenticatedUser = catchAsyncErrors(async (req, res, next) => {
const { token } = req.cookies
if (!token) {
return next(new ErrorHandler('Login first to access this resource.', 401))
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = await User.findById(decoded.id);
next()
})
// Handling users roles
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorHandler(`Role (${req.user.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}
I have added in extra information. Hopefully this helps clear up some missing background info. But the ideas is to show you where ```decoded.id`` comes from (getJwtToken). which by the way is used in the registration process of user accounts. which I also share at the bottom.
addition to models/users.js :
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please enter your name'],
maxLength: [30, 'Your name cannot exceed 30 characters']
},
email: {
type: String,
required: [true, 'Please enter your email'],
unique: true,
validate: [validator.isEmail, 'Please enter valid email address']
},
password: {
type: String,
required: [true, 'Please enter your password'],
minlength: [6, 'Your password must be longer than 6 characters'],
select: false
},
avatar: {
public_id: {
type: String,
required: true
},
url: {
type: String,
required: true
}
},
role: {
type: String,
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
},
resetPasswordToken: String,
resetPasswordExpire: Date
})
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_TIME
});
}
controllers/authController.js
exports.registerUser = catchAsyncErrors(async (req, res, next) => {
const result = await cloudinary.v2.uploader.upload(req.body.avatar, {
folder: 'avatars',
width: 150,
crop: "scale"
})
const { name, email, password } = req.body;
const user = await User.create({
name,
email,
password,
avatar: {
public_id: result.public_id,
url: result.secure_url
}
})
sendToken(user, 200, res)
})
and your utils/jwtToken.js
const sendToken = (user, statusCode, res) => {
// Create Jwt token
const token = user.getJwtToken();
// Options for cookie
const options = {
expires: new Date(
Date.now() + process.env.COOKIE_EXPIRES_TIME * 24 * 60 * 60 * 1000
),
httpOnly: true
}
res.status(statusCode).cookie('token', token, options).json({
success: true,
token,
user
})
}
module.exports = sendToken;
The error is due to your user to be undefined. You should handle that case, but if you don't want for any reason you can just check it is not null using req.user?.role:
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user?.role)) {
return next(
new ErrorHandler(`Role (${req.user?.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}
In this case, if the user is not defined the role will be undefined too (returning the same error of Role (${req.user.role}) is not allowed to acccess this resource)
NULL CHECK USER
exports.authorizeRoles = (...roles) => {
if (!req.user) //throw error (user doesn't exist in db)
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorHandler(`Role (${req.user.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}

Circular Dependency Error for deleteMany MongoDB

I am writing the Model for my Web App API, and am getting the following circular dependency error:
Warning: Accessing non-existent property 'deleteMany' of module exports inside circular dependency
(Use node --trace-warnings ... to show where the warning was created)
.
Here is my code:
const validator = require('validator')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const Task = require('./task')
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid.')
}
}
},
password: {
type: String,
required: true,
trim: true,
minLength: 8
},
name: {
type: String,
unique: true,
required: true,
trim: true
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next() // run the save() method
})
userSchema.pre('deleteOne', {document: true, query: false}, async function(next) {
const user = this
await Task.deleteMany({owner: user._id})
next()
})
userSchema.methods.toJSON = function() {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.__v
delete userObject.tokens
return userObject
}
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, process.env.JSON_WEB_TOKEN_SECRET)
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({email})
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
userSchema.virtual('tasks', {
localField: '_id',
foreignField: 'owner',
ref: 'Task'
})
const User = mongoose.model('User', userSchema);
module.exports = User
Any idea what could be going wrong? I have checked my Node.js and MongoDB versions and updated them, but continue to get this same error when I try to delete. I can provide further details of my code if necessary. The problem area in question is the one leading with userScheme.pre('deleteOne'....

Why do I get an error when I try to save mongoose model?

I am trying to create controller for resetting user password in Node.JS.
The idea is to fetch the user from DB based on reset password token, do some validation update the relevant field and save it back to the DB.
However, I get an error when trying to save the updated user ("user.save is not a function").
What might be the reason?
I have a user model defined as follows:
const mongoose = require("mongoose");
const validator = require("validator");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please enter valid name"],
maxLength: [30, "Your name cannot exceed 30 characters]"],
},
email: {
type: String,
required: [true, "Please enter valid email"],
unique: true,
validate: [validator.isEmail, "Please enter valid email address"],
},
password: {
type: String,
requires: [true, "Please enter your password"],
minLength: [6, "Your password must be at least 6 characters"],
select: false,
},
avatar: {
public_id: { type: String, required: true },
url: { type: String, required: true },
},
role: { type: String, default: "user" },
createdAt: { type: Date, default: new Date().now },
resetPasswordToken: { type: String },
resetPasswordExpire: { type: Date },
});
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
this.password = await bcrypt.hash(this.password, 10);
});
// check password matching
userSchema.methods.isPasswordMatched = async function (inputPassword) {
return await bcrypt.compare(inputPassword, this.password);
};
// return JSON Web Token
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this.id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRESIN_TIME,
});
};
// Generate password token
userSchema.methods.getResetPasswordToken = function () {
// Generate Token
const resetToken = crypto.randomBytes(20).toString("hex");
// Hash token
this.resetPasswordToken = crypto
.createHash("sha256")
.update(resetToken)
.digest("hex");
// set expired time
this.resetPasswordExpire = new Date(Date.now() + 30 * 60 * 1000);
return resetToken;
};
module.exports = mongoose.model("User", userSchema);
When I try to reset user password I try the following:
// get the user document from db (make sure token and expiration time are valid)
let user = User.findOne({
resetPasswordToken: resetPasswordToken,
resetPasswordExpire: { $gt: Date.now() },
});
// update password
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
user.save();
sendToken(user, 200, res);
for some reason I get an error:
"errorMsg": "user.save is not a function"
What might be the problem?
Probably, user is null or undefined, so you should handle the user null condition.
Also findOne and save returns promise, so you need to add await keyword before them.
Also you have a typo in user schema password field, requires should be required .
let user = await User.findOne({
resetPasswordToken: resetPasswordToken,
resetPasswordExpire: { $gt: Date.now() },
});
if (user) {
// update password
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
await user.save();
sendToken(user, 200, res);
} else {
res.status(400).send("No user found");
}
If you get the user null, you need to fix your query in findOne.
use await keyword to get mongoose hydrating the response
let user = await User.findOne({
resetPasswordToken: resetPasswordToken,
resetPasswordExpire: { $gt: Date.now() },
});
// update password
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
user.save();
sendToken(user, 200, res);
or you can do it like this
await User.findOneAndUpdate({
resetPasswordToken: resetPasswordToken,
resetPasswordExpire: { $gt: Date.now() },
},{password : req.body.password, resetPasswordToken : undefined,
resetPasswordExpire : undefined,});

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

i am trying to add multiple users on a atlas mongoDB

I have created a rest api and I am trying to add multiple users to atlas mongodb I use this schema
const mongoose = require('mongoose');
const { v1: uuidv1 } = require('uuid');
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
// _id: mongoose.Types.ObjectId,
name: {
type: String,
// trim: true,
unique: true,
required: true,
index: true
},
email: {
type: String,
// trim: true,
required: true,
unique: true,
},
hashed_password: {
type: String,
trim: true,
required: true
},
salt: String,
created: {
type: Date,
default: Date.now
},
updated: Date,
})
// VIRTUAL FIELD
userSchema.virtual('password')
.set(function(password){
//create temporary variable called _password
this._password = password
//generate a timestamp
this.salt = uuidv1();
//encryptPassword
this.hashed_password = this.encryptPassword(password)
})
.get(function(){
return this._password
})
///methods
userSchema.methods = {
authenticate: function(plainText){
return this.encryptPassword(plainText) === this.hashed_password
},
encryptPassword : function(password){
if(!password) return "";
try{
return crypto.createHmac('sha256', this.salt)
.update(password)
.digest('hex');
} catch(err){
return ""
}
}
}
module.exports = mongoose.model('User', userSchema);
I use this function to sign up :
exports.signup = async (req, res) => {
const userExists = await User.findOne({email : req.body.email})
if(userExists) return res.status(403).json({
error: "EMAIL is TAKEN"
})
const user = await new User(req.body)
await user.save()
.then(result => {res.json({result: result})})
.catch(err => res.json({err : err}))
}
I validate :
exports.userSignupValidator = (req, res, next) => {
//name is not null and its between 4 and 10 characters
req.check('name', 'name is required').notEmpty();
//email is not null, valid and NORMALIZED -> we will use method chaining
req.check('email', 'please enter valid email')
.matches(/.+\#.+\..+/)
.withMessage('email must contain #')
.isLength({
min: 4,
max: 2000
})
//check for password
req.check('password', 'Password is required').notEmpty();
req.check('password').isLength({
min: 6,
}).withMessage('password must be minimum 6 char long').matches(/\d/).withMessage('must contain a number')
//check for errors
const error = req.validationErrors()
////////if error apears show the first one as they appear
if(error){
const firstError = error.map((error) => error.msg)[0]
return res.status(400).json({error: firstError})
}
////proceed to next middleware
next()
}
and I use the route :
const express = require('express'); //bring in express
const postController = require('../controlers/postControler') //brings everything that is exported from the postControler FILE and becomes a OBJECT
const router = express.Router();
const validator = require('../validator');
const signup = require('../controlers/authControler');
const userById = require('../controlers/userControler');
router.get('/', postController.getPosts)
router.post('/post', signup.requireSignIn, validator.createPostValidator, postController.createPost)
router.get('/test' , postController.test)
router.post('/signup', validator.userSignupValidator, signup.signup)
router.post('/signin', signup.signin)
router.get('/signout', signup.signout)
router.get('/lahoha', userById.getUsers)
////find the user by id with params
////any routes containing :userId our app will first execute userById()
router.param('userId', userById.userById);
///////////////////////////////////////////////
module.exports = router
the problem is when I try to create a second user with postman with :
{
"name": "petru",
"email": "petru#gmail.com",
"password": "notazece10"
}
I get the error :
{
"err": {
"driver": true,
"name": "MongoError",
"index": 0,
"code": 11000,
"keyPattern": {
"username": 1
},
"keyValue": {
"username": null
}
}
}
Please help !!!!! this error is driving me crazy, I don't know what I'm doing wrong
after running thru my code multiple times line by line i found out the code is fine , the problem was in my atlas mongodb database.
So i am new to nodejs and mongo , and i try to learn , when i created my first mongodb database in atlas i did not pay attention to naming my database so it had the default name of .
I went back to atlas mongodb and i made a new database ( cluster) , named it TEST , copied the link, went into my dotenv file paste the link to my MONGO_URI restarted the server and then all code worked fine now i can add as many users as i want.
I hope other newcomers to mongodb and nodejs learn from my mistake and if someone ever repeats my STUPID mistake i hope they find this and fix it.

Resources