I am creating REST api and want to authenticate user by token. I've found tutorial and wrote function based on it. But it's on callbacks and I want to return Promise from mongoose model and in route use then rather than do callbacks. Here is my function:
UserSchema.statics.authenticate = function(login, password, fn) {
var token;
this.findOne({login: login}, function(err, user) {
var _token;
if (err)
token = ( false);
if (!user){
token = ( false);
}
else if (user)
{
if (user.password != password)
token = ( false);
else
{
token = jwt.sign(user, secret);
user.update(
{ $set: {
token: token ,
lastActive: new Date()
}}
);
}
}
fn(token);
});
};
module.exports = mongoose.model('User', UserSchema);
I know that to return promise from find function i have to usee exec() but what I want to achive is to return token do I have to var q = new Promise in function and return this q object?
This is my route
router.post('/authenticate', function(req, res, next) {
User.authenticate( req.body.login,req.body.password, function(response){
if(response)
res.status(200)
.send({'success': true, token: response, msg: "Successfuly authenticated"});
else
res.status(200)
.send({'success': false, token: null, msg: "Wrong username or password"});
})
});
Bluebird is a great library to handle this.
You can define a promise before a query, resolve it in the response, and yield it after.
For example:
var Promise = require('bluebird');
var defer = Promise.defer();
collection.find({name: 'name here'}).exec(function(err, result) {
if(err){
defer.reject(err);
} else {
defer.resolve(result);
}
});
return defer.promise
The best way to use mongoose with promises is to used the bluebird npm package :
npm install --save bluebird
and to make that on your models declaration :
const Promise = require('bluebird');
//...
let Model = mongoose.model('User', UserSchema);
module.exports = Promise.promisifyAll(Model);
Now you can use all mongoose methods with promises :
const User = require('./User');
User.find().then((users) => {
if (users) {
reply(null, users);
return;
}
reply(null, []);
}).catch((err) => {
reply.boom(500, err);
});
Related
I am making a REST API in NodeJS using the Mongoose Driver. I want to hash the passwords before saving them. For the same, I am using Mongoose Schema, where I made a userSchema for my user model. And for hashing I used the following function.
userSchema.pre('save', async (next) => {
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
But on creating a user or modifying it I am getting Error 500, and {} is getting returned. My routers are as follows.
router.post('/users', async (req, res) => {
const user = User(req.body);
try {
await user.save();
res.status(201).send(user);
} catch (e) {
res.status(400).send(e);
}
});
router.patch('/users/:id', async (req, res) => {
const updateProperties = Object.keys(req.body);
const allowedUpdateProperties = [
'name', 'age', 'email', 'password'
];
const isValid = updateProperties.every((property) => allowedUpdateProperties.includes(property));
if (!isValid) {
return res.status(400).send({error: "Invalid properties to update."})
}
const _id = req.params.id;
try {
const user = await User.findById(req.params.id);
updateProperties.forEach((property) => user[property] = req.body[property]);
await user.save();
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(400).send(e);
}
});
And the following is my console output.
Server running on port 3000
{}
undefined
On commenting out the userSchema.pre('save', ...) everything is working as expected. Please can you help me figure out where am I going wrong.
Using function definition instead of arrow function for mongoose pre save middleware:
userSchema.pre('save', async function(next) { // this line
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
Update:
The difference is this context, if you use arrow function in this line const user = this;, this now is your current file (schema file, I guess).
When you use function keyword, this context will belong to the caller object (user instance).
I have an API / express router:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(500).json("DB Error");
}
});
Currently, on error, it returns 500 DB error. This is my controller:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(e);
if (doc) {
reject("Username already exists.");
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(e);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
What's the right way to return a 400 if username already exists, and a 500 if it was a database error?
Mongoose already uses promises, the use of new Promise is promise construction antipattern.
Express doesn't have the concept of controllers, there are only route handlers and middlewares. Since register should be very aware of the way it will be used in a response, there may be no need for another level of abstraction above route handler. There will be no problem when a function has access to handler parameters and can form a response in-place.
It can be:
router.post("/signup", async function (req, res) {
try {
const { body, password } = req.body;
const user = await User.findOne({ username: username }).lean();
if (user) {
res.status(400).json("Username already exists");
} else {
...
res.json(user);
}
} catch (e) {
res.status(500).json("DB Error");
}
});
In case route handler needs to be reused in multiple places with some variations, it could be refactored to higher-order function or some other helper that is aware of original req and res parameters.
You can change the way you are rejecting the Promise. I'd suggest something like:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(500);
if (doc) {
reject(400);
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(500);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
And in the route:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(e).json(e == 400 ? "Username already exists." : "DB Error");
}
});
For some reason i get this Error :
Error: req#login requires a callback function
at IncomingMessage.req.login.req.logIn (/home/project/node_modules/passport/lib/http/request.js:47:44)
at exports.update (/home/project/controllers/authController.js:92:13)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
with this code:
exports.update = async (req, res) => {
const user = await User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: { $gt: Date.now() }
});
if (!user) {
req.flash('error', 'Password reset is invalid or has expired');
return res.redirect('/login');
}
const setPassword = promisify(user.setPassword, user);
await setPassword(req.body.password);
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
const updatedUser = await user.save();
await req.login(updatedUser);
req.flash('success', 'Your password has been reset! You are now
logged in!');
res.redirect('/');
};
What am i doing wrong ? I can't figure it out ..
As per the error message, req.login(updatedUser); expects a callback as the second argument. Basically, you'd do something like this:
req.login(updatedUser, function(err, data) {
// handle err and data
});
However, since you wish to await on this async operation, you need something that returns a promise. Basically, you can await on any value that is a promise (and login does not return one.
You can build a promise around this call like this:
await new Promise(function(res, rej) {
req.login(updatedUser, function(err, data) {
if (err) rej(err);
else res(data);
});
})
Node.JS ships with a helper function named promisify to help constructing this behavior if you do not wish to do this manually everytime.
Exactly as Ekin Konc answered you can only async/await only functions that returns a Promise.
You can write some middleware to alias your req.login to promisified function.
E.g
loginPromisifier.js
const { promisify } = require('util');
const loginPromisifier = (req,res,next)=>{
req.login = promisify(req.login);
next();
}
module.exports = loginPromisifier;
and then app.js
const loginPromisifier = require(./loginPromisifier) // your path to it
app.use(loginPromisifier);
I hope that helped.
I am working on a model here:
// user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');
// Define collection and schema for Users
let User = new Schema(
{
firstName: String,
lastName: String,
emailaddress: String,
password: String,
},
{
collection: 'users'
}
);
// authenticate input against database documents
User.statics.authenticate = ((emailaddress, password, callback) => {
User.findOne({ emailaddress: emailaddress })
.exec(function(error, user){
if(error){
return callback(error)
} else if (!user){
console.log('User not found!');
}
bycrypt.compare(password, user.password, function(err, result){
if(result === true){
return callback(null, user);
} else {
return callback();
}
})
})
});
module.exports = mongoose.model('User', User);
As you can see on my model I put the User.statics.authenticate on my codes to do some authentication. And then on my login.js route file:
const path = require('path');
const express = require('express');
const router = express.Router();
const db = require('../../database/index');
const axios = require('axios');
const User = require('../../database/models/user');
router.get('/', (req, res) => {
console.log('hi there this is working login get');
});
router.post('/', (req, res) => {
var emailaddress = req.body.emailaddress;
var password = req.body.password;
if( emailaddress && password ){
User.authenticate(emailaddress, password, function(err, user){
if(err || !user){
console.log('Wrong email or password!');
} else {
req.session.userId = user._id;
return res.redirect('/');
}
});
} else {
console.log('both fields are required...');
}
});
module.exports = router;
I called the function and then User.authenticate function and also I created the route for root w/c is the sample that I want to protect and redirect the user after login:
router.get('/', (req, res) => {
if(! req.session.userId ){
console.log('You are not authorized to view this page!');
}
User.findById(req.session.userId)
.exect((err, user) => {
if(err){
console.log(err)
} else {
res.redirect('/');
}
})
});
Upon clicking submit on my react form it returns this error:
TypeError: User.findOne is not a function
at Function.User.statics.authenticate (/Users/mac/Documents/monkeys/database/models/user.js:35:8)
I checked the Mongoose documentation and it seems I am using the right syntax.Any idea what am I doing wrong here? Please help! Sorry super beginner here!
PS. I've already installed and set up the basic express session too.
UPDATES:
I remove the arrow function from my call and use this.model.findOne but still get the typerror findOne is not a function
// authenticate input against database documents
User.statics.authenticate = function(emailaddress, password, callback){
this.model.findOne({ emailaddress: emailaddress })
.exec(function(error, user){
if(error){
return callback(error)
} else if (!user){
console.log('User not found!');
}
bycrypt.compare(password, user.password, function(err, result){
if(result === true){
return callback(null, user);
} else {
return callback();
}
})
})
};
findOne is a method on your User model, not your user model instance. It provides its async results to the caller via callback:
User.findOne({field:'value'}, function(err, doc) { ... });
I am using Promise with Express.
router.post('/Registration', function(req, res) {
var Promise = require('promise');
var errorsArr = [];
function username() {
console.log("1");
return new Promise(function(resolve, reject) {
User.findOne({ username: req.body.username }, function(err, user) {
if(err) {
reject(err)
} else {
console.log("2");
errorsArr.push({ msg: "Username already been taken." });
resolve(errorsArr);
}
});
});
}
var username = username();
console.log(errorsArr);
});
When I log errorsArray, it is empty and I don't know why. I am new in node.js. Thanks in advance.
Try the following, and after please read the following document https://www.promisejs.org/ to understand how the promises work.
var Promise = require('promise');
router.post('/Registration',function(req,res,next) {
function username() {
console.log("agyaaa");
return new Promise(function(resolve,reject) {
User.findOne({"username":req.body.username}, function(err,user) {
if (err) {
reject(err)
} else {
console.log("yaha b agyaaa");
var errorsArr = [];
errorsArr.push({"msg":"Username already been taken."});
resolve(errorsArr);
}
});
});
}
username().then(function(data) {
console.log(data);
next();
});
});
You can have other errors also (or things that shouldn't be done that way). I'm only showing you the basic use of a Promise.
router.post('/Registration', function(req, res) {
return User
.findOne({ username: req.body.username })
.then((user) => {
if (user) {
return console.log({ msg:"Username already been taken" });
}
return console.log({ msg: "Username available." });
})
.catch((err)=>{
return console.error(err);
});
});
you can write a clean code like this.
Promise is a global variable available you don't need to require it.