Related
when I use this code to try and update the user it appears as a server error. Im using JWT and mongodb but am unsure if im pulling the token or the id to update the users information. Below my controller code is attached and my schema.
const updateUser = async (req, res) => {
try {
const user = await User.findByIdAndUpdate(req.user.id)
if(!user) return res.status(400).send({ error: 'User not found'})
Object.assign(user, req.body);
user.save()
res.send({ data: user})
} catch (error) {
res.status(500).send({error: 'Server Error'})
}
}
const mongoose = require('mongoose');
let userSchema = new mongoose.Schema({
name: { type: String, required: true},
email: { type: String, required: true, unique: true},
password: { type: String, required: true},
date: { type: Date, default: Date.now}
})
module.exports = mongoose.model('user', userSchema)
Updated update function but i appear to have an error
const updateUser = async (req, res) => {
try {
const updatedUser = await User.findByIdAndUpdate(req.params.id,req.body)
if(!updatedUser) return res.status(400).send('User cannot be updated!')
res.json(updatedUser)
} catch (error) {
res.status(500).send({error: 'Server Error'})
}
}
Try this :
//update user by id
exports.updateUser = async (req, res) => {
try {
const updatedUser = await
<--req.params.id is the user.id and req.body contains the requested fields to update -->
User.findByIdAndUpdate(req.params.id,req.body)
res.json(updatedUser);
}
catch (err) {
console.log(err);
res.status(500).json({ message: 'Internal server error' });
}
}
I am trying to authenticate the user using jwt and found this error. This is a MERN project and I had to verify the user whose data I have stored in MongoDb before showing a secret page.
Other errors which VSCode is highlighting are:
1.When I hover on ._id (authentication.js)
const rootUser=await User.findOne({_id:verifyToken._id,"tokens.token":token})
Property '_id' does not exist on type 'string | JwtPayload'.
2.When I hover on req.rootUser(app.js)
res.send(req.rootUser)
Property 'rootUser' does not exist on type 'Request<{}, any, any, ParsedQs, Record<string, any>>
My code looks as follows:
app.js
const mongoose = require('mongoose')
const dotenv = require('dotenv')
const bcrypt = require('bcryptjs')
const jwt=require('jsonwebtoken')
const app = express()
const router = express.Router();
const port = 5000
const authenticate=require("./middleware/authenticate")
dotenv.config({ path: './config.env' })
const DB = process.env.DATABASE;
app.use(express.json());
const User = require('./model/userSchema')
mongoose
.connect(DB, {
useUnifiedTopology: true,
useNewUrlParser: true,
})
.then(() => console.log('Database connected.'))
.catch(err => console.log(err));
app.get('/', (req, res) => {
res.send('Hello World!')
console.log("hey")
})
app.post('/register', async (req, res) => {
const { name, email, work, phone, password, cpassword } = req.body;
if (!name || !email || !work || !phone || !password || !cpassword) {
return res.status(422).json({ error: "Form Not Properly Filled" });
}
try {
const userExist = await User.findOne({ email: email })
if (userExist) {
return res.status(422).json({ error: "Email ALready Exists" })
}
else if (password != cpassword) {
return res.status(422).json({ error: "Password does not match" })
}
else {
const user = new User({ name, email, work, phone, password, cpassword })
await user.save()
res.status(201).json({ message: "user registered successfully" })
}
} catch (error) {
console.log(error);
}
})
//login route
app.post('/signin', async (req, res) => {
try {
let token;
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: "Please fill the data correctly" })
}
const userLogin = await User.findOne({ email: email })
console.log(userLogin);
if (userLogin) {
const isMatch = await bcrypt.compare(password, userLogin.password)
token = await userLogin.generateAuthToken();
console.log(token)
res.cookie("jwtoken",token,{
expires:new Date(Date.now()+25892000000),
httpOnly:true
})
if (!isMatch) {
res.status(400).json({ error: "Invalid Credentials" })
}
else {
res.status(200).json({ message: "Login Success" })
}
}
else {
res.status(400).json({ error: "Invalid Credentials" })
}
} catch (error) {
console.log(error)
}
})
//about page request
app.get('/about',authenticate, (req, res) => {
console.log("About")
res.send(req.rootUser)
})
app.get('/forget', (req, res) => {
res.cookie("harsh","test")
res.send('Forget World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
authenticate.js
const User=require("../model/userSchema")
const Authenticate=async (req,res,next)=>{
try {
console.log(req.cookies)
const token=req.cookies.jwtoken;
console.log("below")
const verifyToken =jwt.verify(token,process.env.SECRET_KEY)
console.log(verifyToken)
const rootUser=await User.findOne({_id:verifyToken._id,"tokens.token":token})
if(!rootUser){
throw new Error("User Not Found")
}
req.token=token;
req.rootUser=rootUser;
req.userID=rootUser._id;
next();
} catch (error) {
res.status(401).send('Unauthorized : No token provided')
console.log(error)
}
}
module.exports=Authenticate;
userSchema.js
const mongoose=require('mongoose')
const bcrypt=require('bcryptjs')
const jwt=require('jsonwebtoken')
const userSchema=new mongoose.Schema({
name:{
type:String,
required:true,
},
email:{
type:String,
required:true,
},
phone:{
type:String,
required:true,
},
work:{
type:String,
required:true,
},
password:{
type:String,
required:true,
},
cpassword:{
type:String,
required:true,
},
tokens:[
{
token:{
type:String,
required:true,
}
}
]
})
userSchema.pre('save' , async function(next){
console.log("inside hash")
if(this.isModified('password')){
this.password=await bcrypt.hash(this.password,12)
this.cpassword=await bcrypt.hash(this.cpassword,12)
}
next();
})
userSchema.methods.generateAuthToken = async function(){
try {
let token =jwt.sign({_id:this._id},process.env.SECRET_KEY)
this.tokens=this.tokens.concat({token : token})
await this.save();
return token
} catch (error) {
console.log(error)
}
}
const User=mongoose.model('USER',userSchema);
module.exports=User;
1. GENERATE JWT TOKEN WHEN USER LOGIN
Generate Token and Send this token with a response...
router.post("/login", async (req, res) => {
try {
// checking username
const user = await User.findOne({ email: req.body.email });
!user && res.status(401).json("Wrong Username");
// checking password
const bytes = CryptoJS.AES.decrypt(user.password, process.env.SECRET_KEY);
const originalPassword = bytes.toString(CryptoJS.enc.Utf8);
// If password not match return respond
originalPassword !== req.body.password &&
res.status(401).json("Wrong Password");
// Creating Json Web Token
const accessToken = jwt.sign(
{ id: user._id, isAdmin: user.isAdmin },
process.env.SECRET_KEY,
{ expiresIn: "5d" }
);
// stop sending password to respond
const { password, ...info } = user._doc;
// Returning User(info) , also sending accessToken
res.status(200).json({ ...info, accessToken });
} catch (err) {
res.status(500).json(err);
}
});
2. VERIFY TOKEN MIDDLEWARE
Create this verify token function use as a MIDDLEWARE in your routes...
const jwt = require("jsonwebtoken");
function verify(req, res, next) {
const authHeader = req.headers.token;
if (authHeader) {
const token = authHeader.split(" ")[1];
jwt.verify(token, process.env.SECRET_KEY, (err, user) => {
if (err) res.status(403).json("Token is not valid");
req.user = user;
next();
});
} else {
return res.status(402).json("You are not authorized");
}
}
module.exports = verify;
3. VERIFY-IN ROUTES AS AN MIDDLEWARE
const verify = require("../verifyToken");
// CREATE
router.post("/", verify, async (req, res) => {
if (req.user.isAdmin) {
const newList = new List(req.body);
try {
const savedList = await newList.save();
res.status(201).json(savedList);
} catch (error) {
res.status(500).json(err);
}
} else {
res.status(403).json("You are not allowed!");
}
});
// DELETE
router.delete("/:id", verify, async (req, res) => {
if (req.user.isAdmin) {
try {
await List.findByIdAndDelete(req.params.id);
res.status(201).json("The list has been deleted");
} catch (err) {
res.status(500).json(err);
}
} else {
res.status(403).json("You are not allowed!");
}
});
CONCLUSION:-
Create a token with jwt.sign when user login...
Create a verify function in the root directory export this function...
Require this function in routes file use as a middleware...
I'm trying to redirect a user to their profile after logging them in. What I'm trying to do is when they log in, we will find the user by credentials and then generate and auth token from them (note I created a user const for testing purposes). After both are done, I'll set a header Authorization, use the token, and pass it to the /user/me route. Here are my routes:
(login POST route, "/user/login"):
router.post('/user/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
res.header('Authorization', 'Bearer '+token)
res.status(302).redirect('/user/me')
} catch (err) {
res.status(400).send(err)
}
})
(profile route: "/user/me"):
router.get('/user/me', auth, async (req, res) => {
res.send(req.user)
})
(the "auth" middleware that I'm passing in the previous method):
const auth = async (req, res, next) => {
try{
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, SECRET_TOKEN)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token})
console.log(token)
if(!user) {
throw new Error("User not found")
}
req.token = token
req.user = user
next()
} catch(err) {
res.status(503).send({error: 'Please authenticate'})
}
}
But whenever I try this, it gives my 503 error from the auth method:
{
"error": "Please authenticate"
}
The Authorization header passes correctly as I've seen in my dev tools.
For more information, here's what the generateAuthToken & findByCredentials methods look like:
userSchema.methods.generateAuthToken = async function() {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, SECRET_TOKEN)
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({message: "Unable to log in."})
}
const isValid = await bcrypt.compare(password, user.password)
if(!isValid) {
throw new Error({message: "Unable to log in."})
}
return user
}
For more more information, here's what my User model looks like:
const userSchema = mongoose.Schema({
email:{
type: String,
required: true,
unique: true,
trim: true,
validate(value) {
if(!validator.isEmail(value)) {
throw new Error("Not a valid email")
}
}
},
password:{
type: String,
required: true,
validate(value) {
if(value === "password") {
throw new Error("Enter a strong password.")
}
if(value.length < 8) {
throw new Error("Enter minimum 8 letters for a password.")
}
}
},
tokens: [{
token:{
type: String,
required: true
}
}]
})
I've solved it by using cookies. It maybe a temporary work-around but I'll find resources to make it more secure!
In the login route:
res.cookie('Authorization', `Bearer ${token}`, {
maxAge: 60000,
})
In the auth middleware:
const token = req.cookies['Authorization'].replace('Bearer ', '')
I have a User model and a Post model. Now, only logged in users can create a new post. Now, when the post is submitted, I want to get the post info as well as the user info. If you see, I have used
userId: { type: Schema.Types.ObjectId, ref: "User" } to reference the User model.
Say, in the API tester Postman, what should I enter as the body?
{
"title": "abcd",
"description": "efgh",
//should userId be automatically generated or I have to enter here
}
I know slug is generated automatically but what about the user info? I'm getting confused here?
And how do I get user info from userId? (I have a route /users/:id though but don't know how to use it in this case)
User Model
const userSchema = new Schema({
username: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
}, { timestamps: true })
Post Model
const postSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
userId: { type: Schema.Types.ObjectId, ref: "User" },
slug: { type: String, slug: "title" }
}, { timestamps: true }
authmiddleware.js
function generateToken(payload) {
return jwt.sign(payload, "secret")
}
function verifyToken(req, res, next) {
const token = req.headers.Authorization
if (!token) return res.json({ msg: "unauthorized"})
jwt.verify(token,"secret",(err, decoded) => {
if (err) return res.json({ error: "unuthorized" })
req.user = decoded
next()
})
}
module.exports = {generateToken, verifyToken }
routes/posts.js
router.post("/new", authmiddleware.verifyToken, postControllers.newPost)
postControllers/newPost.js
newPost: (req, res) => {
Post.create(req.body, (err, newPost) => {
if (err) console.log(err)
if (!newPost) {
return res.json({ message: "No post found" })
} else if (newPost) {
return res.json({ post: newPost })
}
})
}
When I checked in postman by entering this info as body(and sent token in headers)
{
"title": "My first post",
"description": "My first post description"
}
I got this result:
{
"post": {
"_id": "5e3028ded3e24b55e06baaf2",
"title": "My first post",
"description": "My first post description"
"createdAt": "2020-01-28T12:28:14.331Z",
"updatedAt": "2020-01-28T12:28:14.331Z",
"slug": "my-first-post",
"__v": 0
}
}
But I also want to get the user info in this.
Update: So, I got the userId in the newly created post object.
I was doing this earlier:
newPost: (req, res) => {
Post.create(req.body, req.user.userId, (err, newPost) => {
if (err) console.log(err)
if (!newPost) {
return res.json({ message: "No post found" })
} else if (newPost) {
return res.json({ post: newPost })
}
})
}
And got and error:
{
"error": {
"message": "Parameter \"obj\" to Document() must be an object, got 5e2f365d41ff66569445eec2",
"name": "ObjectParameterError"
}
}
Then I did this I got the result:
newPost: (req, res) => {
const data = {
title: req.body.title,
description: req.body.description,
userId: req.user.userId
}
Post.create(data, (err, newPost) => {
if (err) console.log(err)
if (!newPost) {
return res.json({ message: "No post found" })
} else if (newPost) {
return res.json({ post: newPost })
}
})
}
It shouldn't be happening. No?
You can put the logged in user's id to the request in your auth middleware, and acccess the user id in your route.
Let's say you have a such auth middleware:
const jwt = require("jsonwebtoken");
module.exports = function(req, res, next) {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
token = req.headers.authorization.split(" ")[1];
}
if (!token) return res.status(401).send("Access denied. No token provided.");
try {
const decoded = jwt.verify(token, "JWT_PRIVATE_KEY");
req.user = decoded;
next();
} catch (ex) {
res.status(400).send("Invalid token.");
}
};
I assume you send the authorization token in the Authorization header in the format Bearer TOKEN....... If you send the token in a different header, you need to adjust code to read token accordingly.
Now when you apply this middleware to your route, you can read the user id from request,
and use it.
const Post = require("../models/post");
const auth = require("../middleware/auth");
router.post("/posts", [auth], async (req, res) => {
const result = await Post.create({
title: req.body.title,
description: req.body.description,
user: req.user._id //comes from auth middleware
});
res.send(result);
});
For this to work, you need to include the user id in the token when you sign it.
const token = jwt.sign({ _id: user._id }, "JWT_PRIVATE_KEY");
I am trying to incorporate the express-jwt library and I do not quite understand how it's error handling works.
The documentation says:
Error handling
The default behavior is to throw an error when the token is invalid, so you can >add your custom logic to manage unauthorized access as follows:
app.use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') {
res.status(401).send('invalid token...');
}
});
But I am confused how that works. If I have a simple req res situation, and I want to call next if the token is valid, or call next with an error if it is not, where to I put that app.use function?
For instance, here is my code:
router.post('/', expressJwt({
secret: jwtSecret,
credentialsRequired: false
}), (req, res, next) => {
databaseController.findUser(req.user.email, (err, user) => {
if (err) {
return next(err)
}
res.json(user)
})
})
The err here would come from my DB call, not from the express-jwt validation.
Any help is appreciated.
Another way is you could place the middleware with app.use to scan all the routes for a valid jwt in the header or the query string.
Any public endpoints can be exempted using the unless keyword.
Ex:
app.use(expressjwt({credentialsRequired: true, secret: config.TOKEN_SECRET, requestProperty: 'user'}).unless({path: config.PUBLIC_URLs}));
app.use(function(err, req, res, next) {
if(err.name === 'UnauthorizedError') {
res.status(err.status).send({message:err.message});
logger.error(err);
return;
}
next();
});
this is my solution for individual routes.
function UseJwt(){
return [
jwtExpress({ secret: configuration.jwtSecret, algorithms: ['HS256'] }),
function(err, req, res, next){
res.status(err.status).json(err);
}
]
}
usage...
app.get(`/${prefix}/:user_id`,
...UseJwt(),
async function (req, res) {
// handle your code here.
}
)
You can create an express middleware just before the code you use to start express server.
// Global error handler that takes 4 arguments and ExpressJS knows that
app.use((err, req, res, next) => {
res.status(err.status).json(err);
});
app.listen(3000);
I use that for apps use REST but you can use the same approach and modify what should happen based on your needs. If you use Jade template for instance then you need to populate the template with the data you want to show to end user and log the rest on your log file.
app.use((err, req, res, next) => {
res.locals.status = status;
res.render('error')
});
Express-jwt is just a method that returns a RequestHandler (a function that takes in req, res, and next). This RequestHandler can be wrapped in a closure that replaces its next input with one you design that handles or formats errors. For example:
/**
* Wraps an Express request handler in an error handler
* #param method RequestHandler to wrap
* #returns RequestHandler
*/
export function formatError<T extends RequestHandler>(method: T): RequestHandler {
return async (req, res, next) => {
const wrapError = (err: any) => {
return (err)
? res.status(err.status).send({ message: err.message })
: next();
};
await method(req, res, wrapError);
};
}
const jwtMiddleware = formatError(expressjwt(...));
Then just use this closure instead of using expressjwt() directly:
router.post('/', jwtMiddleware, ...);
import { Schema, model } from "mongoose";
export const ROLES = ["Admin", "Estudiante","Docente","Secretario","Vicerrector","Inpector"];
const roleSchema = new Schema(
{
name: String,
},
{
versionKey: false,
}
);
export default model("Role", roleSchema);
//
import { Schema, model } from "mongoose";
import bcrypt from "bcryptjs";
const productSchema = new Schema(
{
username: {
type: String,
unique: true,
},
email: {
type: String,
unique: true,
},
password: {
type: String,
required: true,
},
//********************************NUEVOS CAMPOS PARA USUARIOS ADMINISTRADORES
nombres: {
type: String,
required: true,
},
apellidos: {
type: String,
required: true,
},
cedula: {
type: String,
unique: true,
},
foto: {
type: String,
required: true,
},
status: {
type: String,
required: true,
},
telefono: {
type: String,
required: true,
},
//---------------TIPO DE DOCUMENTOS
typo:{
type: String,
},
//---------------TIPO MAS DATOS
roles: [
{
type: Schema.Types.ObjectId,
ref: "Role",
},
],
},
{
timestamps: true,
versionKey: false,
}
);
productSchema.statics.encryptPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
};
productSchema.statics.comparePassword = async (password, receivedPassword) => {
return await bcrypt.compare(password, receivedPassword)
}
export default model("User", productSchema);
//
import Role from "../models/Role";
import User from "../models/User";
import bcrypt from "bcryptjs";
export const createRoles = async () => {
try {
// Count Documents
const count = await Role.estimatedDocumentCount();
// check for existing roles
if (count > 0) return;
// Create default Roles
const values = await Promise.all([
new Role({ name: "Estudiante" }).save(),//user
new Role({ name: "Docente" }).save(),//moderator
new Role({ name: "Admin" }).save(),//admin
new Role({ name: "Secretario" }).save(),//-------+++
new Role({ name: "Vicerrector" }).save(),//-------+++
new Role({ name: "Inpector" }).save(),//-------+++
]);
console.log(values);
} catch (error) {
console.error(error);
}
};
export const createAdmin = async () => {
// check for an existing admin user
const user = await User.findOne({ email: "10004095632w#gmailcom" });
// get roles _id
const roles = await Role.find({ name: { $in: ["Admin", "Estudiante","Docente","Secretario","Vicerrector","Inpector"] } });
if (!user) {
// create a new admin user
await User.create({
username: "admin",
email: "10004095632w#gmail.com",
password: await bcrypt.hash("Imperio 789.", 10),
roles: roles.map((role) => role._id),
nombres: "ad",
apellidos: "ad",
cedula: "123456789",
foto: "profile.jpg",
status: "Activo",
telefono: "+570995283857",
});
console.log('Admin User Created!')
}
};
//
import jwt from "jsonwebtoken";
import config from "../config";
import User from "../models/User";
import Role from "../models/Role";
export const verifyToken = async (req, res, next) => {
let token = req.headers["x-access-token"];
if (!token) return res.status(403).json({ message: "No token provided" });
try {
const decoded = jwt.verify(token, config.SECRET);
req.userId = decoded.id;
const user = await User.findById(req.userId, { password: 0 });
if (!user) return res.status(404).json({ message: "No user found" });
next();
} catch (error) {
return res.status(401).json({ message: "Unauthorized!" });
}
};
export const isSecretario = async (req, res, next) => {
try {
const user = await User.findById(req.userId);
const roles = await Role.find({ _id: { $in: user.roles } });
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === "Secretario") {
next();
return;
}
}
return res.status(403).json({ message: "Require Moderator Role!" });
} catch (error) {
console.log(error)
return res.status(500).send({ message: error });
}
};
export const isAdmin = async (req, res, next) => {
try {
const user = await User.findById(req.userId);
const roles = await Role.find({ _id: { $in: user.roles } });
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === "Admin"||roles[i].name === "Secretario") {
next();
return;
}
}
return res.status(403).json({ message: "Require Admin Role!" });
} catch (error) {
console.log(error)
return res.status(500).send({ message: error });
}
};
//
import User from "../models/User";
import { ROLES } from "../models/Role";
const checkDuplicateUsernameOrEmail = async (req, res, next) => {
try {
const user = await User.findOne({ username: req.body.username });
if (user)
return res.status(400).json({ message: "El numero de cédula ya existe" });
const email = await User.findOne({ email: req.body.email });
if (email)
return res.status(400).json({ message: "El correo electrónico ya existe" });
next();
} catch (error) {
res.status(500).json({ message: error });
}
};
const checkRolesExisted = (req, res, next) => {
if (req.body.roles) {
for (let i = 0; i < req.body.roles.length; i++) {
if (!ROLES.includes(req.body.roles[i])) {
return res.status(400).json({
message: `Role ${req.body.roles[i]} does not exist`,
});
}
}
}
next();
};
export { checkDuplicateUsernameOrEmail, checkRolesExisted };
//
import * as authJwt from "./authJwt";
import * as verifySignup from "./verifySignup";
export { authJwt, verifySignup };