How to set run multiple controller functions in nodejs - javascript

I am currently working on boiler plate code of mean.io and implementing passwordresetemail to it. When ever user asks for password reset with email as parameter, I create a salt(resetid) and send him an email having that salt as reset link.
I have user's email in the req but want to append other information of the user(user._id) before it enters into actual createemail controller function. I want following function(userbyemail) to be run before it goes into createResetPasswordEmailLink
/**
* Find user by email
*/
exports.userByEmail = function(req, res, next, email) {
User
.findOne({
email: email
})
.exec(function(err, user) {
if (err) return next(err);
if (!user) return next(new Error('Failed to load User with email ' + email));
req.user = user;
next();
});
};
exports.createResetPasswordEmailLink = function(req, res) {
var resetPassword = new ResetPassword(req.body);
resetPassword.resetId = new User().makeSalt();
**resetPassword.user = req.user._id; // currently req.user is null**
console.log(resetPassword.userId);
resetPassword.save(function(err) {
if (err) {
// handle error
}
res.status(200);
return res.redirect('/');
});
};
Following is my resetPassword schema
var ResetPasswordSchema = new Schema({
created: {
type: Date,
default: Date.now
},
resetId: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
My routes is defined as follows
// Setting up the users resetpasswordlink
app.route('/createresetpasswordemaillink')
.post(users.createResetPasswordEmailLink);

I could solve this issue with app.params. App.params is exactly defined for this kind of usage.
I added
// Setting up the userId param
app.param('email', users.userByEmail);
to my routes and it automatically executed this function before the req is passed on to regular controllers.
From the documentation, App.param is defined as
app.param([name], callback)
Map logic to route parameters. For example when :user is present in a route path you may map user loading logic to automatically provide req.user to the route, or perform validations on the parameter input.

Related

Setup an authentication middleware to reduce duplicate code in Express.js

As the title suggests, I want to reduce duplicate authorization code for each new route I call.
My problem is exactly the same as the user in this post, because apparently we downloaded the same project from GitHub repository.
I tried both of the solutions suggested in the answers, however it restricts me from accessing those routes even if I'm logged in.
Here's the code:
router.js
// GET route for reading data
router.get("/", function (req, res, next) {
return res.sendFile(path.join(__dirname + "/login"));
});
//Export authorization module
var auth = require("../auth");
//Verify if user is authorized to access this route
router.get("/complete-profile", auth.isAuthorized, function (req, res, next) {
return res.sendFile(path.join(__dirname, "../public", "image.html"));
});
//READ THE IMAGE UPLOAD FOLDER
router.use(express.static("public"));
// GET route after login, verify if user logged in
router.get("/complete-profile", function (req, res, next) {
User.findById(req.session.userId).exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error("Not authorized! Go back!");
err.status = 400;
return next(err);
} else {
//SEND NEW USERS TO IMAGE UPLOAD PAGE
return res.sendFile(path.join(__dirname, "../public", "image.html"));
}
}
});
});
As suggested, I tried declaring all of this as a middleware, so here is the middleware:
auth.js
module.exports.isAuthorized = function(req, res, next) {
User.findById(req.session.userId).exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error('Not authorized! Go back!');
err.status = 400;
return next(err);
} else {
return next();
}
}
});
}
Any help is gladly appreciated!
Source: How to setup an authentication middleware in Express.js
In the answer you referenced, it appears that user installed and is using Sequelize to store an individual's user data. If you would like to utilize that approach, I would look into Sequelize. As you mentioned on the other thread, User is not defined. For the other question, the asker most likely set up a model called User.
In Sequelize, each model (like User) defines a table that has its own rows and columns. Each column represents a field that applies to an individual row of data. For example, for a User model, one user may have a username, an email, and a password. You would specify what data types these columns should be and any other necessary information for each column of the Sequelize model definition. Each row represents one data-entry, or in this case, one user. I had previously built a sample web app that maps students to specific classes; below I have copied the Sequelize model definition I wrote for that project. It's quite simple and I would recommend watching some YouTube tutorials or checking out the Sequelize documentation at sequelize.org if this library is foreign to you.
Student.js
'use strict';
const Sequelize = require('sequelize');
const db = require('./_db');
const Student = db.define('student', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notEmpty: true
}
},
phase: {
type: Sequelize.STRING,
allowNull: true,
validate: {
isIn: [['junior', 'senior', null]]
}
}
});
Student.findByPhase = async function(phase) {
const students = await Student.findAll({
where: {
phase: phase
}
})
return students
}
module.exports = Student;
It may also help to check out PostgreSQL or SQL in general as well to understand the basic framework that Sequelize lies on top of.

CastError: Cast to ObjectId failed for value ...` at path "questions"

I'm currently building a Node backend with MongoDB / Mongoose and I seem to be having some problem with tying my data together. Specifically, I wish for all users to be able to submit a form (question form) which will then be added to the "questions" collection. In addition to being added to the questions collection, I also need to store a reference to all of the questions a user has answer directly inside of the user object.
Below you can check out my code. Whenever I make a POST requestion to /questions, it spits out this error. I should note that it successfully adds documents into the questions collection, and each question contains the ID of the user who created it, but the main problem is the user's questions array is not getting updated to include an ID value of submitted questions.
Models/User.js
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt-nodejs');
const UserSchema = new Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
password: {
type: String,
required: true
},
profile: {
firstName: { type: String },
lastName: { type: String }
},
questions: [
{
type: Schema.Types.ObjectId,
ref: 'Question'
}
],
role: {
type: String,
enum: ['Member', 'Client', 'Owner', 'Admin'],
default: 'Member'
},
resetPasswordToken: { type: String },
resetPasswordExpires: { type: Date }
},
{
timestamps: true
});
/** Pre-save of user to database,
hash password if password is modified or new
*/
module.exports = mongoose.model('User', UserSchema);
Models/Question.js
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
// Schema defines how questions will be stored in MongoDB
const QuestionSchema = new Schema({
questionString: String,
answer: Boolean,
_createdBy : [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
],
},{
//user timestamps to save date created as .createdAt
timestamps: true
});
module.exports = mongoose.model('Question', QuestionSchema);
Controller/QuestionController.js
const jwt = require('jsonwebtoken'),
crypto = require('crypto'),
Question = require('../models/question'),
User = require('../models/user'),
config = require('../config/main');
function setQuestionInfo(request) {
return {
_id: request._id,
questionString: request.questionString,
answer: request.answer,
user: request.user
}
}
exports.addQuestion = function(req, res, next) {
User.findById(req.user.id, (err, user) => {
if (err) throw new Error(err);
// We create an object containing the data from our post request
const newQuestion = {
questionString: req.body.questionString,
answer: req.body.answer,
// in the author field we add our current user id as a reference
_createdBy: req.user._id
};
// we create our new post in our database
Question.create(newQuestion, (err, question) => {
if (err) {
res.redirect('/');
throw new Error(err);
}
// we insert our newQuestion in our posts field corresponding to the user we found in our database call
user.questions.push(newQuestion);
// we save our user with our new data (our new post).
user.save((err) => {
return res.send('sucess!');
});
})
});
}
Router.js
module.exports = function(app) {
// Initializing route groups
const apiRoutes = express.Router(),
userRoutes = express.Router(),
authRoutes = express.Router(),
questionRoutes = express.Router();
//=========================
// Auth Routes
//=========================
/** ROUTES BELOW WORK FINE -- ONLY DEALS WITH POST TO /questions
*
app.use middle ware sets /auth as auth route (everything goes through /api/auth)
apiRoutes.use('/auth', authRoutes);
apiRoutes.get('/dashboard', requireAuth, function(req, res) {
res.send('It worked! User id is: ' + req.user._id + '.');
});
// Set user routes as a subgroup/middleware to apiRoutes
apiRoutes.use('/user', userRoutes);
// View user profile route
userRoutes.get('/:userId', requireAuth, UserController.viewProfile);
// Test protected route
apiRoutes.get('/protected', requireAuth, (req, res) => {
res.send({ content: 'The protected test route is functional!' });
});
// Registration route
authRoutes.post('/register', AuthenticationController.register);
// Login route
authRoutes.post('/login', requireLogin, AuthenticationController.login);
*/
// Problem Area --> Making POST req to /questions
apiRoutes.post('/questions', requireAuth, QuestionController.addQuestion);
// Set url for API group routes
app.use('/api', apiRoutes);
};
You've your schema defined to accept question ids for a user.
questions: [
{
type: Schema.Types.ObjectId,
ref: 'Question'
}
After you save with Question.create(newQuestion, (err, question)... the callback attribute question has the updated data, one with the ObjectId.
Now you add this ObjectId value to your existing questions array that you got from findById on User model.
user.questions.push(question._id);
Mongoose will use the questionId to fill your question object when you use populate on questions array, but thats part for retrieving information.

How to find out last_login_date in passport/node js

I have created a program using node js, passport, express and mongo, where first you have to register a user, and then you will be able to login. This is what my users schema looks like:
var UserSchema = mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
index: true
},
password: {
required: true,
type: String
},
creation_date: {
type: Date,
default: Date.now
},
last_login_date: {
type: Date,
default: Date.now
}
})
How do I find out when the user was last logged in? Ive been trying to figure this out for a while as I assume this will need to take place in the routes? This is my user route for login:
router.post('/login',
passport.authenticate('local'),
function(req, res) {
res.json('Welcome ' + req.user.username);
});
Has anyone managed to add a last_login_date for each user?
Any clues would be helpful!
If your already using Mongoose why not define a new static in your schema just for the purpose of logging in.
This way you keep your code together.
Also.. I know Mongoose at top level treats the update query in a findOneAndUpdate method as a $set operation, so no need to do something like this
query = { $set : { 'last_login_date' : Date.now() } }
..but... I strongly suggest you still use the $set operation to keep things clear and if you switch to another driver or plugin in time, you can still keep your all your queries. But that is just a personal habbit. If your using the Mongo Shell you'll get undesired results if you forget to use the $set on your updates.
So I would recommend that you define a static in the User schema for setting last login date and returning the updated doc so that you can use it in passport everytime a User logs in.
Your schema would look like this :
var UserSchema = mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
index: true
},
password: {
required: true,
type: String
},
creation_date: {
type: Date,
default: Date.now
},
last_login_date: {
type: Date,
default: Date.now
}
});
UserSchema.statics.login = function login(id, callback) {
return this.findByIdAndUpdate(id, { $set : { 'last_login_date' : Date.now() }, { new : true }, callback);
};
With this method you can get the user in session and update the last_login_date right away.
I also suggest you put that method in the deserializeUser method like Ankit Rana suggested. This way your req.user object contains the updated last_login_date.
In the code you posted you just update the last_login_date but you are not returning the updated document. So in req.user you still have the last_login_date from the previous session. You can check this by printing it to the console.
console.log(req.user.last_login_date); //last_login_date will not be updated
res.json('Welcome ' + req.user.username);
So remove all the update methods from the passport.authenticate method like this
router.post('/login',
passport.authenticate('local'),
function(req, res) {
// if login is successfull, the following message will be displayed
res.json('Welcome ' + req.user.username);
And change your passport.deserializeUser method to this
passport.deserializeUser(function(id, done) {
User.login(id, function(err, user) {
if(err) return done(err, null);
done(err, user);
});
});
Try it out and let me know if something does not work or is not clear
Ok, so I have managed to achieve this by doing the following:
When the user logs in, you look for the user and then you update the last_login_time. Like this:
router.post('/login',
passport.authenticate('local'),
function(req, res) {
var query = {
'username': req.user.username
};
var update = {
last_login_date: Date.now()
};
var options = {
new: true
};
User.findOneAndUpdate(query, update, options, function(err, user) {
if (err) {
console.log(err);
}
});
// if login is successfull, the following message will be displayed
res.json('Welcome ' + req.user.username);
This works perfect, and every time the user logs in the last_login_date is updated.
This is the place where you can get user and get last login date, manually add in the user object. and property you can easily access by req.user.last_login_date
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
if(err) return done(err, user);
user.last_login_date = Date.now(); // Typo was here.
done(err, user);
});
});

Multiple connection error when using node js and mongodb

I'm using the following schema located in my /routes/schema.js file...
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
username: String,
password: String,
name: String,
last: String,
year: Number,
degree: String
});
var User = mongoose.model('User', userSchema);
module.exports = {
User: User
}
In my /routes/register.js file I am storing some information using POST data...
var User = require('../routes/schema').User;
exports.postRegister = function (req, res) {
var u = new User({
username: req.body.reg_username,
password: req.body.reg_password,
name: req.body.reg_name,
last: req.body.reg_lastname,
year: req.body.reg_year,
degree: req.body.reg_degree
});
u.save(function (err) {
if (err) {
throw err;
}
else {
console.log("saved");
res.render('index', { title: 'Express' });
}
});
}
Everything gets saved fine in my Database. But now, this register.js file redirects the user back to /routes/index.js where the user must then sign in using some credentials stored in the database.
So in my index.js file I need to check if username and password exist together in a collection in my database, I tried the following...
var User = require('../routes/schema').User;
exports.signin = function (req, res) {
User.findOne({
username: req.body.log_username,
password: req.body.log_password
}, function (err, docs) {
if (docs.length) {
console.log("name exists");
}
else {
console.log("no exist");
}
});
};
I used the findOne function with the same Schema to check if username and password exist in a collection in the database, but it doesn't work properly. I seem to get a multiple connection error and I do not know how to avoid it.
When I try to login using some credentials already in the database, the console prints out no exist meaning the else statement in exports.signin is reached.
The value of the docs will be null if the object is not found in the collection.
If the user enters wrong credentials, you will be calling docs.length on null object, which will cause an error. Use docs != null instead, to avoid calling length on null object.
try it docs.length !== 0 this is check docs field is existed or empty.

How to Post and Get Data Unique to Each Logged In User with Express and Mongoose?

I am currently working on a small single page app that lets users login with PassportJs and Mongoose.
One of the things I am trying to do is allow users to login and each user has a unique todo/task list which are items associated to that user.
I have been able to do the first part...users can login and express/passport session is accessed using jade #{user.username}, so when logged in the user see "Welcome, [user.username]".
Now I add a form (accessible when user logged in) and the form says undefined. I'm not sure if its my Mongoose schema design or Routes that are causing the problem. Thanks for reading this and here is my code:
Mongoose Schema
mongoose.connect('mongodb://localhost/poplivecore')
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var user = new Schema({
username: String,
password: String,
email: String,
todos: [Todo]
});
var Todo = new Schema({
name: {type: String, default : ''},
user: {type: Schema.ObjectId, ref: 'user'},
createdAt : {type : Date, default : Date.now}
})
var Todo = mongoose.model('Todo', Todo);
var user = mongoose.model('user', user);
Here are my Express routes:
//WORKING....This route is the one that a logged in user sees, form posts with
app.get('/home', ensureAuthenticated ,function(req, res){
res.render('home', { user: req.user});
});
//WORKING...This route allows user to post/submit the login
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function(req, res) {
res.redirect('/home');
});
//WORKING....This route allows user to create a user/account
app.post('/create', function(req, res, next){
var user = new user({
"username": req.body.username,
"password" : req.body.password,
"email" : req.body.email});
user.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/');
}
});
});
**//NOT WORKING..Post used in the form inside the logged in Area, that adds a 'todo'**
app.post('/todo', function(req, res){
var todo = new todo(req.body.name);
todo.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/fail');
}
});
});
Jade Form, for Adding a todo
enter code here
form(method='post', action='/todo')
//input(type='hidden', value= user._id)#userId
fieldset
label Todo
div.input
input(name='todo.name', type='todo.name', class='xlarge')
div.actions
input(type='submit', value='Save', class='btn primary')
button(type='reset', class='btn') Cancel
I can post on github if you need to see more code...thanks.
Update as per 'numbers1311407' suggesion
*New post route for todo, also changed todo to 'Todo' in both schema and routes*
app.post('/todo', function(req, res){
var todo = new Todo({name : req.body["Todo.name"]});
todo.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/fail');
}
});
});
There are at least two problems here that would cause this to not work:
The name of the input passed by your form is todo.name, and you're referencing it as req.body.name in the route.
mongoose models are instantiated with an attributes object, but you're just giving it a string (which, actually, is null currently because of the first issue).
So for your route to work it would look more like this:
app.post("/todo", function (req, res) {
var todo = new Todo({name: req.body["todo.name"]});
todo.user = req.user._id;
// ...
});
If you wanted to pass todo attributes as a parameter object, you'd want to name them with brackets todo[name], rather than dots. This would result in the todo attributes being on object on the req.body, e.g.:
app.post("/todo", function (req, res) {
console.log(req.body.todo); //=> { name: "whatever" }
// ... which means you could do
var todo = new Todo(req.body.todo);
todo.user = req.user._id;
// ...
});
Some other things you might want to change:
As #NilsH points out, you don't want to pass the user id in the form, as that would allow anyone to make a todo for anyone else just by knowing their ID. Rather since you're using passport, make use of the user in the session. You should have access to the user ID through the passport determined user, like req.user._id. I added this to both examples above.
The type of your form input is todo.name. It should be text (that's what the browser is treating it as anyway).
Not necessarily an error, but model names are conventionally capitalized. This also solves an issue your code has above in that you're redefining todo when you say var todo = new todo(...).

Categories