How to show logged in user info in all routes NodeJS - node.js

So this is my code that shows if user session exists and if it exists then it renders user info with the file so I can display the logged in user info there.
app.get('/', async(req, res) => {
if(req.session.user && req.cookies.user_sid){
let user = await User.findOne({username: req.session.user.username});
res.render('index', {user});
} else {
res.render('index');
}
});
But now I want to display user info and in another routes. So in my opinion it would be stupid to repeat again that if statement. What alternative could be there?

It's best to repeat that statement and make the DB call again. In the time between the first request and any subsequent requests, the user may have been logged out (such as by cookies expiring) or user data might have been changed in the database, so it's best to not cache the data and check every time.

Method 1
Add a user details middleware ,which checks if user details is available in session or not and then updates the session object if not available.This way you will avoid redundant calls to db across routes.
app.use(function (req, res, next) {
if(req.session && req.session.userDetails === undefined){
const userDetails = await User.findOne({username: req.session.user.username});
req.session.userDetails = userDetails || undefined;
//optional
req.user = {};
Object.assign(req.user, req.session.userDetails);
}
next()
})
You can pass the userDetails in all your routes with reference to req.user or req.session.userDetails, something like
app.get('/profile', async(req, res) => {
res.render('profile', {user : req.user});
})
Method 2
You can also save the user details in session when the user successfully logs in and use the session reference in all routes, something like
app.post('/authenticate', async (req, res) => {
const userDetails = await User.findOne({ username: req.body.username, password: req.body.password });
if (userDetails.length > 0) {
//when authentication is successsful
req.session.user = userDetails;
}
});

Related

Check user that can view my files in my server

I want to secure my files and not anyone can view my files in express, I want to check who has logged in into my website and then authenticate if he has access to view this file or not , how can I achieve that this is the code I am using to access my files url :
app.use("/profile", express.static(__dirname + '/profile'));
I want only the user that logged in, he is the only one that can view his profile image and if he is not the user then say for example you are not allowed to view this file.
I am using mongodb as a backend but i don't know if its mongodb authentication or express middleware security ?
I Discovered how to solve this :
const checkImg = async (req, res, next) => {
const token = req.cookies.jwt;
if (token) {
jwt.verify(
token,
process.env.JWTKEY,
async (err, decodedToken) => {
if (err) {
res.json({ status: false });
} else {
const user = await UserModal.findById(decodedToken.id);
const imgUrl = req.originalUrl.split('/')[2];
const imgUser = user.profileImg;
if (imgUrl === imgUser || user.admin === "ADMIN") {
next();
}
else {
return res.send("Your are not allowed to view this file")
}
}
}
);
}else{
return res.send("you are not allowed !")
}
}
and here is when i use my middleware :
app.use("/images", checkImg , express.static(__dirname + '/images'));
this code checks if the user is the same user then he can only see the data and also the admin user can display the data , otherwise send him that he is not allowed to get the data

Cookies persist in supertest/superagent test, but the user doesn't stay logged in

My Goal
I'm trying to use supertest's agent function in a jest beforeEach() to login the user before each test, as I want each test to run under the assumption that the user is signed in. For authentication, I am using passport and passport-local.
This is what I tried (with parts cut out for brevity):
Test file:
import { agent, SuperAgentTest } from 'supertest';
import app from '../../src/app';
// create a `testRequest` variable to use in the tests
// that will be refreshed in between
let testRequest: SuperAgentTest;
const fakeUser = { email: 'john#john', username: 'john', password: 'john' };
beforeEach(async () => {
// create new agent
testRequest = agent(app);
// register and login
await testRequest.post('/register').send(fakeUser).expect(302);
// other irrelevant stuff...
});
// protected route
describe('POST /campgrounds/new', () => {
it('returns 200 OK', () => {
return testRequest.get('/campgrounds/new');
})
});
/register route:
router.post('/register', async (req, res) => {
const { password, ...details } = req.body;
try {
// I am using passport-local-mongoose for this function-
// it just registers the user
const user = await User.register(new User(details), password);
req.login(user, (err) => {
// error handling and redirect
});
} catch (e) {
// error handling
}
})
This is my result
Instead of a 200 status, I get a 302 status, meaning I was redirected to the login page. To debug this, I created a test route called /current which will log the current user and session ID cookie. I then sent a GET request to this route in both the it and beforeEach function respectively.
Interestingly, they both logged the same session ID, but only the request in beforeEach had a user object attached to the request.
#1 Ensure body parser correct order
Make sure you have this before any routes or auth-related things.
app.use(express.json())
#2 Check Passport Middleware Wire-up
Ensure you call app.use(passport.initialize()) & app.use(passport.session()) before any app.use('/', aRouter), router.get, router.post, etc:
// Set up session w/ specific config
app.use(session({
secret: 'bquyqueajhbd',
resave: true,
saveUninitialized: true,
store: new FileStore({path: '/tmp/session'})
}));
// Wire up the
app.use(passport.initialize())
app.use(passport.session())
EDIT: Notes on req.user
Passport is designed to store the user ID in session.
Every request to the server must reload the user from the database.
This is the job of the middleware passport.initialize() and passport.session().
The logic there will call passport.deserializeUser to lookup the user by ID - the same ID that was saved upon login into the session by passport.serializeUser.
passport.serializeUser(function(user, done) {
done(null, user.id); // <-- Here's where the ID is saved to session.
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user); // <-- Here is where the `req.user` get's it's value from.
});
});
To debug this I'd focus on the passport.deserializeUser callback, add logs before and after the DB query.
(Note: it's been a few years since I taught this. Appologies if I'm not using the precise terms, etc.)

GET Request route MEAN

I'm creating a MEAN APP and upon registration I want to check if an email already exists. However the GET request doesn't show anything. Also no error.
server.js
...
// Set user routes
const userRoutes = require('./user/user_controller');
app.use('/user', userRoutes);
...'
user_controller.js
router.get('/:email', (req, res) => {
console.log('Requesting user');
User.findOne({'email': req.params.email})
.exec(function(err, user) {
if(err) {
console.log('Error getting the post');
} else {
res.send(user);
}
});
});
The GET route never enters because I also don't see the console.log at the beginning of the route. I expect the route to work when I call localhost/user?email=email
I have a POST route whih works perfectly for localhost/user - just to compare
router.post('/', (req, res) => {
user = req.body;
// Validation
req.checkBody('firstName', "Enter a valid firstname").exists().isAlpha();
req.checkBody('lastName', 'Enter a valid lastname').exists().isAlpha();
req.checkBody('email', 'Enter a valid email').exists().isEmail();
req.checkBody('password', 'Enter a valid password').exists().isLength({min:8});
const errors = req.validationErrors();
if(errors) {
res.json({errors: errors});
} else {
User.create(user)
.then((user) => res.send(user));
}
});
You are calling your route the wrong way,
You can do one of these things.
1) Change your route path to /
As you said you were calling it localhost/user?email=email because this matches the route path / not /:email and you can access it like req.query.email.
2) Change the way you're calling it
If you want to use with /:email route path call it like localhost/user/someemail.

Express rendering in/after post

Web app routing novice here. I've got a relatively simple app working based on Node/Express.
The main index of the app is a list of user names, and IDs.
localhost:242
Every user ID is a link to a page with a form to enter additional metadata about that particular user.
localhost:242/user/1398
Everything is working correctly. When I enter some metadata about the user, and submit the form, a POST route is executed, and then I'm redirected back to the original page I was on. Instead of using a redirect, I'd like to be able to re-render that same page, so I can pass some confirmation messages indicating what was just changed.
Here's a simplified version of my code.
// Module imports
var express = require('express');
var validator = require('express-validator');
var router = express.Router();
router.get('/', function(req, res, next) {
db.dataTalk(queryUsers, null, config.connection, function(err, result) {
var listUsers = result;
res.render('index', {
// Index with list of users
title: 'Page Title',
listUsers: listUsers
});
});
});
// GET /user/:id
router.get('/user/:id', function(req, res, next) {
db.dataTalk(queryUserDeets, [req.params.id], config.connection, function(err, result) {
// Details for a single user
var userDetails = result;
res.render('user', {
title: req.params.id,
userDetails: userDetails
});
});
});
// POST /user-update
router.post('/user-update', function(req, res) {
// Here goes a lot of logic to validate the form contents, and update the appropriate databases
// Redirect back to the user page, which should display the updated metadata
res.redirect('/user/' + req.body.userInitID);
});
module.exports = router;
Extract a helper function you can call from both places. Here's one that sticks very close to your original code.
function renderUserPage (userId, res) {
db.dataTalk(queryUserDeets, [userId], config.connection, function(err, result) {
// Details for a single user
var userDetails = result;
res.render('user', {
title: userId,
userDetails: userDetails
});
});
});
// GET /user/:id
router.get('/user/:id', function (req, res) {
renderUserPage(req.params.id, res)
});
// POST /user-update
router.post('/user-update', function(req, res) {
// Here goes a lot of logic to validate the form contents, and update the appropriate databases
// Redirect back to the user page, which should display the updated metadata
renderUserPage(req.body.userInitID, res);
});
Aside: You are ignoring errors from database calls. If you don't at the very least log something for each and every error passed to an async callback, you are going to be blind to problems that would otherwise be straightforward to debug.

Verifying headers at top-level

I have a Node.js app built with Express.js framework.
I want to check that the user is authorized to do a certain request, I do this by requiring the clients to supply an access token in a header.
I don't want to add this to each of the individual functions that the clients have access to. Like this, for an info request about a user:
exports.info = function(req, res) {
var userId = req.params.id,
accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user == null) {
...
How can I do this at a higher level? Can I set this header requirement somewhere on a global for express?
I want to do this basically for all functions except for the user login function, so all functions except for one.
You can make a small middleware:
verifyUser = function(req,res,next){
var userId = req.params.id, accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user == null) {
...
}
next()
}
}
Then:
On one request:
app.get("/user/info", verifyUser, exports.info)
On a selection of requests:
app.all(SomeRegex, verifyUser)
On all resquests:
app.use(verifyUser)
You can create a middleware and set it up on each route, you need to authorize. Example:
var myAuthMiddleware = function (req, res, next) {
// Here goes your code to check if the user complies
// with the conditions. You can use req.headers, req.user, etc
if (conditionIsMet) return next(); // If the user complies, you continue the process
// The user doesn't comply
return res.send('Error');
}
Then, you use his middleware in the needed routes:
app.get('/my-route', myAuthMiddleware, myRouteHandler);
app.post('/another-route', myAuthMiddleware, myOtherRouteHandler);
// This one doesn't need auth
app.get('/', indexHandler);
Just add your function as one more of the express middleware that runs before all your request processing.
app.use(function(req, res, next) {
var userId = req.params.id,
accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user != null) {
return next(); // This is ok, keep processing
} else {
// don't call next, redirect to login page, etc...
}
}
app.get('/home', ...);
apg.get('/some_other_page');
You call next to get express to process as usual, or you use redirect, or return an error and don't call next.

Resources