Express rendering in/after post - node.js

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.

Related

passport.authorize() clearing req.user with multiple (same) strategies

I need two instances of a passport local strategy ("localA" and "localB"), one instance of this authenticates against a collection "colA" in "DbA" and is used in one route sequence (Route A), the other instance authenticates against another collection (ColB) in "DbB" and is used in a second route sequence (Route B).
In both cases, access to "req.user" is needed. In the first route, "req.user" has its expected defined value, however, in the second route, "req.user" is undefined. Here is an extract of what I believe to be the relevant code:
const userA = DbA.model(`colA`, userASchema);
passport.use(`localA`, new passportLocalStrategy({usernameField: `email`, passwordField: `password`}, userA.authenticate()));
passport.serializeUser(userA.serializeUser());
passport.deserializeUser(userA.deserializeUser());
const userB = DbB.model(`colB`, userBSchema);
passport.use(`localB`, new passportLocalStrategy({usernameField: `email`, passwordField: `password`}, userB.authenticate()));
passport.serializeUser(userB.serializeUser());
passport.deserializeUser(userB.deserializeUser());
//Route A
app.post('/routeA', passport.authenticate(`localA`), (req, res) => {
res.redirect(`/routeAA`);
});
app.get('/routeAA', function (req, res) {
res.render('routeA.ejs');
});
//Route B
app.post('/routeB', passport.authenticate(`localB`), (req, res) => {
res.redirect(`/routeBB`);
});
app.get('/routeBB', function (req, res) {
res.render('routeB.ejs');
});
It appears that this is not a new issue. Here are some related posts:
https://github.com/jaredhanson/passport/issues/803
Passport.js multiple local strategies and req.user
In post 803 user #nathan6am, states ....
I ran into the same problem, it's a bit of a hacky solution but I got
around it by using req.session?.passport?.user and deserializing the
user manually instead of using req.user in the callback.
I'm still struggling to understand how to manually de-serialize so as to force req.user to re-acquire correct values, but I did confirm that the contents of "req.session.passport.user" (for my schema) is the user's email address, so I saved that in a session variable, "req.session.email". My plan was then to write some middleware (in the next route) that would search my DB, using the contents of req.session.email, then use that DB record to extract the data that I would subsequently pass onto my rendered ejs file. It would have looked something like this:
//Route B
app.post('/routeB', passport.authenticate(`localB`), (req, res) => {
req.session.email = req.session.passport.user;
res.redirect(`/routeBB`);
});
app.get('/routeBB', hack, function (req, res) {
res.render('routeB.ejs' {key1: value1, key2: value2 });
});
function hack(req, res, next) {
// find user in DB using req.session.email
// extract need data from DB
// build object comprised of needed data
// key1: value1
// key2: value2
return next();
}
But then I realized that I have other middleware, for other routes, that rely on req.user for authorization (req.user.role=some role) ... so having req.user as undefined isn't something that can work. Is there anyone who can add some color to #nathan6am's post?
Thank you.
Tim.

How to add data from instagram api to existing user record?

How do I associate data returned from instagram API with an existing user account--so that the instagram profile info can be stored in the same document as the user that made the request?
I'm using node.js, Mongoose with MongoDB, and node-instagram from npm to pull in basic user info from instagram. Users on this site will create an account and then later click a button to connect their account to instagram. I'm able to pull in instagram data and store it on my database but I'm not sure how to associate this data with the local (already existing) account that made the request?
app.get('/auth/instagram/feed', async (req, res) => {
try {
const data = await instagram.authorizeUser(req.query.code,
redirectUri);
const newUserInfo = new User({
username: data.user.username,
full_name: data.user.full_name,
access_token: data.access_token,
});
newUserInfo.save();
res.redirect("/");
} catch (err) {
res.json(err);
}
});
This successfully stores some basic instagram info on the database. Instead of saving a new record, how do I add this data to an existing user's record with email and password info?
I was able to get this to work by just creating a variable before the await line that gets the user id.
I've added my full code below as well as I neglected to include everything in my original post. Please note that the "/auth/instagram/feed" route is called by instagram (if you are confused about where that is coming from). You have to enter this into their URI redirect when signing up for a new API app.
const instagram = new instagramConnect({
clientId: process.env.INSTAGRAM_CLIENT_ID,
clientSecret: process.env.INSTAGRAM_CLIENT_SECRET
});
const redirectUri = 'http://localhost:3000/auth/instagram/feed';
app.get("/instagram-connect", function(req, res){
res.render("instagramConnect");
});
app.get('/auth/instagram', (req, res) => {
res.redirect(instagram.getAuthorizationUrl(redirectUri, { scope: ['basic'] }));
});
app.get('/auth/instagram/feed', async (req, res) => {
try {
const userID = req.user._id;
const data = await instagram.authorizeUser(req.query.code, redirectUri);
User.findOneAndUpdate({_id: userID}, {access_token: data.access_token}, function(err){
if(!err){
res.redirect("/ig-success");
}
});
} catch (err) {
res.json(err);
}
});

How to show logged in user info in all routes NodeJS

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;
}
});

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.

Can't access data from a POST call by express saved by Mongoose

I am just learning Node, Mongoose and Express. I've looked all through stackoverflow and else where, and I still cannot find an answer to my question.
I'm fairly certain this a just a very beginner mistake. When I save posted data inside of app.post, I cannot access it anywhere else in my node/express code. I get the error "ReferenceError: getUsersInfo is not defined."
var mongoose = require('mongoose');
mongoose = mongoose.createConnection('localhost', '27017');
mongoose.on('error', console.error.bind(console, 'connection error:'));
var Schema = mongoose.Schema
, ObjectId = Schema.ObjectID;
var usersSchema = new Schema({
username: String,
password: String,
email: String,
date_created:{ type: Date, default: Date.now }
});
var users = mongoose.model('users', usersSchema);
app.get('/', function(req,res){
res.render('layout.jade', {
title : "Hello!"
,analytics: 'XXXXXX'
});
});
app.post('/', function(req, res){
var getUsersInfo = new users({
username: req.body.user,
password: req.body.password,
email: req.body.email,
});
getUsersInfo.save(function (err, getUsersInfo) {
if (err){throw err;}
console.log(getUsersInfo.username);
res.redirect("/success");
});
});
app.get('/success', function(req, res){
res.render('loggedin.jade', {
title : "Admin Panel",
analytics: 'XXXXXX'
});
var username = getUsersInfo("username");
res.write("<h1> hi, " + username + "</h1>");
});
Any help you can give me such as places to look or a better way to write my code would be much appreciated. I tried learning from out-dated tutorials and haven't gotten anywhere and the mongoose api/docs don't seem to cover this thing.
Thanks!
EDIT
To clarify what I'm trying to achieve here is I want to take form inputs (rendered in the layout.jade file) and save them to the database (Which I'm doing in app.post). I want to then be able to access those database objects elsewhere in the code, not specifically in just app.get('/success'). But I keep running into a scope issue, it seems.
In a nodejs/expressjs application, your routes (app.get, app.post etc) are set up with callback functions that will be invoked when a user requests the corresponding URL. All operations that should be done in a request needs to be invoked from within these callbacks. In your example, you're attempting to access getUsersInfo outside the callback you have set up in app.post. If you move the code inside the callback instead, it should get you further:
app.post('/', function(req, res) {
var getUsersInfo = new users({
username: req.body.user,
password: req.body.password,
email: req.body-email
});
getUsersInfo.save(function(err, userinfo) {
if(!err) {
res.redirect('/success');
}
else {
// Send error
}
});
});
Edit: Answer above was confused by the indentation of the code, which should be corrected now. It looks like you're making a regular web app, so I would recommend that you check out the session middleware. An easy way to get started with it is to generate your express app with the command express --sessions myapplication. This will set your application so you have a req.session object where you can put session scoped objects:
app.post('/', function(req, res) {
var getUsersInfo = new users({
username: req.body.user,
password: req.body.password,
email: req.body-email
});
getUsersInfo.save(function(err, userinfo) {
if(!err) {
req.session.user = userInfo;
res.redirect('/success');
}
else {
// Send error
}
});
});
Then in your app.get
app.get('/success', function(req, res) {
var user = req.session.user;
// Do what you want with the user
});
If you were using a REST approach, a typical pattern when saving and then retrieving a resource is to redirect with GET to the URL fetching the created resource, and include an identifier for the resource. In that case, your redirect would look like this
res.redirect('/success/' + userInfo.id)
And you could make a route like this:
app.get('/success/:userId', function(req, res) {
var user_id = req.param('userId');
users.findById(user_id, function(err, user) {
// user is available here. Add it to the template context and render it.
});
});
The syntax :userId in the uri indicates a path variable named userId which will be available in req.params.
But if you're writing a regular web application, I would recommend the first approach using the session middleware.

Resources