I need to check users rights to protect route.
I finding user by Id from token, and check in DB "admin" field.
It finds and check well, but i don't get what to do next. I'm using this middleware in "/admin" route:
User.findById(decodedToken.userId,)
.then(user =>{
isAdmin = user.admin;
if(!isAdmin){
const error = new Error('Unauthorized')
error.statusCode = 401
throw error
}else{
req.userId = decodedToken.userId
next();
}
}).catch(err=>{
console.log(err);
return err
})
but I don't get any response on frontend, only pending GET request
You need to send a response to your frontend:
router.route("/").get((req, res, next) => {
res.setHeader("Content-Type", "application/json");
User.findById(decodedToken.userId, )
.then(user => {
//your logic
res.end('Have Permission');
}).catch(err => {
console.log(err);
return err
})
})
If you want to send an object you can do res.json(yourobject)
Related
I have a simple web service and it has a route for register user ,
I want when email exists in DB throw an error with status of 400 or other
I've done it like this
controllers/user.js
const { User } = require('../models/user')
exports.create = async (req, res) => {
try {
const { email } = req.body
const user = await User.findOne({ email })
if (user) {
return res.json({ err: 'email already exists' })
}
await User.userValidation(req.body)
await User.create(req.body)
return res.json({})
} catch (err) {
res.status(400).send({ err })
}
}
BUT , it always give status of 200,
where is the problem ?
Add the status to your response:
if (user) {
return res.status(400).json({ err: 'email already exists' })
}
You can simply send the status 400 when checking if(user)
if(user){
res.status(400).jsom({ err: "Email already exists" });
}
OR
Threat the errors and add a middleware using next (a little bit more complicated then the first one, but more proffessional)
exports.create = async (req, res, next) => {
try {
const { email } = req.body
const user = await User.findOne({ email })
if (user) {
throw new Error("Email already exists");
}
await User.userValidation(req.body)
await User.create(req.body)
return res.json({})
} catch (err) {
next(err, req, res, next);
}
}
In the next middleware you can threat the error and send whatever response you need. (err, req, res objects are sent like references, so you can use them there)
my backend send a res.staus(200).json({somedata)} to my front, but i can't retrieve the data in the frontend.
My backend :
exports.login = (req, res, next) => {
//===== Check if user exists in DB ======
const { user_email, user_password: clearPassword } = req.body;
let sql = `SELECT user_password, user_id FROM users WHERE user_email=?`;
db.query(sql, [user_email], async (err, results) => {
console.log(results);
console.log(req.body);
if (err) {
return res.status(404).json({ err });
}
// ===== Verify password with hash in DB ======
const { user_password: hashedPassword, user_id } = results[0];
try {
const match = await bcrypt.compare(clearPassword, hashedPassword);
if (match) {
console.log("match ... user_id : ", user_id);
// If match, generate JWT token
res.status(200).json({
test: 'iyu',
user_id: user_id,
token: jwt.sign({ userId: user_id }, "TOOOKEN", {
expiresIn: "24h",
}),
});
} else {
console.log("not match");
}
} catch (err) {
return res.status(400).json({ err: "une erreur" });
}
});
};
The frontend :
const login = async (e) => {
e.preventDefault();
await POST(ENDPOINTS.USER_LOGIN, userLogin);
// await GET(ENDPOINTS.USER_LOGIN)
fetch("http://localhost:4200/api/auth/login")
.then((response) => response.json())
.then((data) => {
console.log(data);
});
};
This login fonction send data to my backend, then the backend checks if an user exist in database with the first POST request. If yes, the backend send in json format some data that i wan't to put in the local storage of the user, so after the POST request, i do another request with GET method to retrieve the json data sent from the back, but i have an 404 error.
How can i get my data sent by the back ?
Seems the problem is with the SQL statement, underneath the if statement you have can you print the error like so:
if(err) {
console.log(err);
}
and tell me the result please
I'm having a hard time connecting the last dots building a role based access control api in Express.
Following this tutorial and implementing onto my existing program, but I think I am missing the last step and after countless tutorials analysis paralysis has set in. I have since scaled back all my necessary code to what I think is the bare minimum.
Currently I am able to create a new user and save them to the mongoose database. I can see the hash by bcrypt is doing its thing and I can see the token being generated in the response after signing up. However as soon as I navigate to a new page after signup or login, for eg the users own id page/user/:userId as per tutorial, I keep getting You need to be logged in. I know I need to check for a token on every request but my question is, why doesn't it seem like the middleware is checking for the token or something is holding it back?
Since the token is shown in the json reponse surely I should be able to check for the tokens existence with the next get request at for eg the /user/:userId page? Isn't that the idea? Or is the browser just showing the response but I still need to actually store it? I don't understand where it goes to so to speak..
Any advice? Or is this a session thing? I know its a bit hard without all the code but if anyone could spot anything relevant so that I could research my next steps I would much appreciate it!
First this middleware in app.js
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use('/', async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
console.log('token verified'); // not printing to console
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use('/', userRoutes);
I have built the roles using the module access-control which is required
const AccessControl = require('accesscontrol');
const ac = new AccessControl();
exports.roles = (function() {
ac.grant('basic')
.readOwn('profile')
.updateOwn('profile');
ac.grant('supervisor')
.extend('basic')
.readAny('profile');
ac.grant('admin')
.extend('basic')
.extend('supervisor')
.updateAny('profile')
.deleteAny('profile');
return ac;
})();
routes examples as per tutorial.
router.get('/signup', (req, res, next) => {
res.render('signup', {
viewTitle: 'User SignUp',
});
});
router.post('/signup', userController.signup);
router.get('/login', (req, res, next) => {
res.render('login', {
viewTitle: 'User Login - WTCT OPS',
});
});
router.post('/login', userController.login );
router.get('/add', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.add);
router.get('/users', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.getUsers);
router.get('/user/:userId', userController.allowIfLoggedin, userController.getUser);
router.put('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('updateAny', 'profile'), userController.updateUser);
router.delete('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('deleteAny', 'profile'), userController.deleteUser);
relevant part of controller
async function hashPassword(password) {
return await bcrypt.hash(password, 10);
}
async function validatePassword(plainPassword, hashedPassword) {
return await bcrypt.compare(plainPassword, hashedPassword);
}
// grant access depending on useraccess role
exports.grantAccess = function(action, resource) {
return async (req, res, next) => {
try {
const permission = roles.can(req.user.role)[action](resource);
if (!permission.granted) {
return res.status(401).json({
error: 'You don\'t have enough permission to perform this action',
});
}
next();
} catch (error) {
next(error);
}
};
};
// allow actions if logged in
exports.allowIfLoggedin = async (req, res, next) => {
try {
const user = res.locals.loggedInUser;
if (!user) {
return res.status(401).json({
error: 'You need to be logged in to access this route',
});
}
req.user = user;
next();
} catch (error) {
next(error);
}
};
// sign up
exports.signup = async (req, res, next) => {
try {
const {role, email, password} = req.body;
const hashedPassword = await hashPassword(password);
const newUser = new User({email, password: hashedPassword, role: role || 'basic'});
const accessToken = jwt.sign({userId: newUser._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
newUser.accessToken = accessToken;
await newUser.save();
res.send({
data: newUser,
message: 'You have signed up successfully',
});
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
try {
const {email, password} = req.body;
const user = await User.findOne({email});
if (!user) return next(new Error('Email does not exist'));
const validPassword = await validatePassword(password, user.password);
if (!validPassword) return next(new Error('Password is not correct'));
const accessToken = jwt.sign({userId: user._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
await User.findByIdAndUpdate(user._id, {accessToken});
res.status(200).json({
data: {email: user.email, role: user.role},
accessToken,
});
} catch (error) {
next(error);
}
};
// get one user
exports.getUser = async (req, res, next) => {
try {
const userId = req.params.userId;
const user = await User.findById(userId);
if (!user) return next(new Error('User does not exist'));
// console.log(req.params);
res.send(200).json({
data: user,
});
} catch (error) {
next(error);
}
};
Why when trying to post to the endpoint /user/:userId is the middleware not checking for the token?
Thank you for any advice!
Update:
So far I have tried to removed the / from app.use. I saw I made that mistake now, but also tried removing it from the app.use(userRoutes); middleware to make it apply to all http requests but no luck.
app.use(async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
// console.log('Time:', Date.now());
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use(userRoutes);
I also thought that maybe because my server makes http requests in the backend maybe that was causing a problem in setting the x-access-token header? So I tried to change the x-access-token mw to use router.use on all routes but still nothing. I don't understand what I am missing. And just to be sure I'm not missing something fundamental, since I am using the JWT I do not need to use local storage or cookies to allow for browsing between pages while logged in since I can use the token set in the header, correct?
Thanks again for any advice!
That's because your middleware is only tied to the / route. Remove it if you want it to be used for every route. Take a look at the ExpressJS Docs regarding middleware.
I have a user profile collection in which I have the following fields:
member_id
userhandle
height
weight
I register a user with passport and generate a unique member_id for each user which is later used for getting the profile page populated and also for referrals. Following is the get profile route where user can change their details:
// Get User Profile Settings route
router.get('/profilesettings/:member_id', (req, res) => {
Profile.findOne({ member_id: req.params.member_id })
.then(profile => {
res.render('users/profilesettings', { profile: profile });
})
.catch(error => {
console.log('could not find profile');
});
});
Once this page is loaded the user can change their details and use the submit button to update their data. Following is the code for the put request:
router.put('/profilesettings/:member_id', (req, res) => {
Profile.findOne({ member_id: req.params.member_id })
.then(profile => {
profile.userhandle = req.body.userhandle;
profile.weight = req.body.weight;
profile.height = req.body.height;
profile.mobile = req.body.mobile;
profile.save()
.then(updatedProfile => {
req.flash('success_msg', 'Profile updated successfully');
res.redirect('/user/userdashboard');
})
.catch(error => {
console.log(error);
});
})
.catch(error => {
console.log('could not find record');
});
});
What I want to do is ensure that the userhandle is always unique, so if the user enters a userhandle which is already taken by someone else in the profile collections there should be an error and the form should not submit. I am totaly stumped on how to put in a logic which does the following:
1- Checks if there is a difference in the userhandle submitted and the one already stored in the collection
2- Checks if the userhandle which came in the request already exists or not
3- if not then sets the userhandle to the new value and save
4- if it does it creates and error and redirects.
Would appreciate any advise. I know it's a small thing for you pros but I am learning Node and express :-)
After you have confirmed if the member exists or not, you can do a 'count' query to check if the 'userHandle' exists or not. If the userHandle already exists you can return a 4xx status code. Otherwise, save it in the db. It would look something like this...
router.put('/profilesettings/:member_id', (req, res) => {
Profile.findOne({ member_id: req.params.member_id })
.then(profile => {
Profile.count({userhandle: req.body.userhandle})
.then(count => {
if(count != 0){
//return the error code
}
else{
//proceed with your normal flow
profile.userhandle = req.body.userhandle;
profile.weight = req.body.weight;
profile.height = req.body.height;
profile.mobile = req.body.mobile;
profile.save()
.then(updatedProfile => {
req.flash('success_msg', 'Profile updated successfully');
res.redirect('/user/userdashboard');
})
.catch(error => {
console.log(error);
});
}
}).catch(err => {
console.log(err);
});
})
.catch(error => {
console.log('could not find record');
});
});
Whenever a user registers i am sending him an email which contains the link which user needs to click to get verified. I am passing a token in that link. When the user clicks the link he should get verified but i am not able to do this. I can just retrieve the token from the link but i am unable to find the user in the database and update the value.
Here is my code:
router.route('/verify')
.get(isNotAuthenticated, function(req, res){
var verifyToken = req.query.id;
var user = User.findOne({ 'secretToken': verifyToken });
if (!user) {
req.flash('error', 'No user found with this email id, please check your email id or incorrect link');
res.redirect('/users/register');
return;
}
user.active = true;
user.secretToken = '';
user.save();
req.flash('success', 'Thank you! Now you may login.');
res.redirect('/users/login');
res.redirect('login');
Try using promise to do this instead of assignment.
User.findOne({ 'secretToken': verifyToken })
.then(user => {
// do something with user
})
.catch(err => {
// do something with error
})
If you are using JWT to validate your routes you can:
1 - Generate the link verification with one "hash value"(token), save this token in the user document (user collection).
send the link e.g. "https://site/user/verify?token=3f0621f7e4c41ece51926a40cee4dae0be65ct7"
2 - Disable the security for this route:
app.use(jwt({secret: process.env.SECRET}).unless({path: ['/users/verify']}));
3 - Receive the request to verify the user:
router.put('/verify', bodyParser.json(), function (req, res, next) {
try {
const user = userController.verify(req.query.token);
res.status(200).json(user);
} catch (err) {
next(err);
}
});
4 - Find and update the user(as verified):
User.findOneAndUpdate(
{
token: token,
},{$set:{verified:true}})
.then((result) => {
return result;
}).catch((err) => {
//throw error
});
If you need to wait the update for execute other sttufs, you can use async/wait: Async/Await Tutorial