Unknown error when using bcrypt.hash - node.js

I have this pre mongoose middleware for saving passwords,I previously used synchronous implementation,now I am doing an asynchronous implemntation as mongoose middleware:
schema.pre('save', function(next) {
var user = this;
var SALT_WORK_FACTOR = 5;
if (!user.isModified('local.password')) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
bcrypt.hash(user.local.password, salt, function(err, hash) {
if (err) return next(err);
user.local.password = hash;
next();
});
});
});
The code just throws an unknown error when it gets to bcrypt.hash although the error is previously null. If I use something like stackup,the error looks like this:
E:\Do\login\node_modules\stackup\index.js:32
error.stack = activeTrace.toString(error.stack);
^
TypeError: Cannot assign to read only property 'stack' of No callback function w
as given.
at AsyncListener.error (E:\Do\login\node_modules\stackup\index.js:32:19)
at asyncCatcher (E:\Do\login\node_modules\stackup\node_modules\async-listene
r\glue.js:123:26)
at process._asyncFatalException [as _fatalException] (E:\Do\login\node_modul
es\stackup\node_modules\async-listener\glue.js:211:14)

You're using bcrypt-nodejs, which expects two callbacks:
hash(data, salt, progress, cb)
docs
You've only provided it once, and so cb isn't defined when bcrypt-nodejs hits it.

Related

Return value in PDF-HTML npm package

I am trying to upload the pdf created with pdf-html npm package and return a value. But pdf.create() always returns undefined. How can I force pdf.create() to return a value?
My method is as below:
return await pdf.create(stocksReport({})).toFile(`./dist/${storestocks.store.storename}.pdf`, async (err, stream)=>{
if(err) console.log(err)
const serviceS3Result = await this.s3Service.uploadFileFromSystem(`${storestocks.store.storename}`, fs.readFileSync(`../${storestocks.store.storename}.pdf`), `${storestocks.store.storename}.pdf`);
fs.unlinkSync(`../${storestocks.store.storename}.pdf`);
return serviceS3Result;
});
but this always returns undefined.
Just to make things clear:
var x = pdf.create(html).toFile(`./dist/test.pdf`, function (err, res){
if(err) console.log(err)
console.log(res);
return 'something'
)};
return x;
this always returns undefined.
Is there a way around this?

async.waterfall() breaks on passing callback to bcrypt.hash()

I am new to async/await and have set up a basic node.js server that handles form data for user registration. Below is my code
async.waterfall([async function(callback){ //Method 1
const hash = await bcrypt.hash(password, 10/*, () => { //breakpoint
console.log("Hash Generated Successfully");
}*/);
return hash;
}, function(hash, callback){ //Method 2
console.log(`The value of passed arg is: ${hash}`);
callback(null, 'success');
}], function(err, result){
if(err){
throw err
}
else {
console.log(result);
}
});
In Method 1, if i don't provide the callback to bcrypt.hash(), the code works correctly and the value of hash is printed. However, if i do provide the callback, i get this output:
The value of passed arg is: undefined.
So, i have two questions here.
1) Why does async.waterfall() break on providing callback to bcrypt.hash()?
2) What is the other way to do error handling, other than callbacks?
Passing the requisite parameters to the bcrypt callback function is a necessity if you plan on including the anonymous function as a parameter. For ex:
const hash = await bcrypt.hash(password, 10, (err, hash) => { // Added err, hash params.
console.log("Hash Generated Successfully");
});
return hash;

Express Mongoose Model.find() returns undefined

Hej, have a problem. Trying to send Express response with Mongo data in it.
This is code from my Express server
var Task = require('./modules/Task');
app.get('/get-all-tasks',function(req,res){
res.setHeader('Content-Type', 'application/json');
console.log(Task.getAllTasks()); // returns undefined
res.json({msg:"Hej, this is a test"}); // returns object
});
This is mongoose model in separate file
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/todo-app');
var TaskSchema = mongoose.Schema({
name: String,
assignee: String
},{ collection : 'task' });
var Task = module.exports = mongoose.model('Task', TaskSchema);
module.exports.createTask = function (newTask, callback) {
newTask.save(callback);
}
module.exports.getAllTasks = function(){
Task.find().lean().exec(function (err, docs) {
console.log(docs); // returns json
});
}
How can I properly send data from getAllTasks function?
That's looks correct, but your are forgetting about the Javascript's asynchronous behavior :). When you code this:
module.exports.getAllTasks = function(){
Task.find().lean().exec(function (err, docs) {
console.log(docs); // returns json
});
}
You can see the json response because you are using a console.log instruction INSIDE the callback (the anonymous function that you pass to .exec())
However, when you type:
app.get('/get-all-tasks',function(req,res){
res.setHeader('Content-Type', 'application/json');
console.log(Task.getAllTasks()); //<-- You won't see any data returned
res.json({msg:"Hej, this is a test"}); // returns object
});
Console.log will execute getAllTasks() function that doesn't return anything (undefined) because the thing that really returns the data that you want is INSIDE the callback...
So, to get it work, you will need something like this:
module.exports.getAllTasks = function(callback){ // we will pass a function :)
Task.find().lean().exec(function (err, docs) {
console.log(docs); // returns json
callback(docs); // <-- call the function passed as parameter
});
}
And the we can write:
app.get('/get-all-tasks',function(req,res){
res.setHeader('Content-Type', 'application/json');
Task.getAllTasks(function(docs) {console.log(docs)}); // now this will execute, and when the Task.find().lean().exec(function (err, docs){...} ends it will call the console.log instruction
res.json({msg:"Hej, this is a test"}); // this will be executed BEFORE getAllTasks() ends ;P (because getAllTasks() is asynchronous and will take time to complete)
});
I believe what you would need to do is return the docs in your getAllTasks function, but perhaps a better way to do it asynchronously using callbacks like so:
module.exports.getAllTasks = function(callback){
Task.find().lean().exec(function (err, docs) {
// If there is an error, return the error and no results
if(err) return callback(err, null)
// No error, return the docs
callback(null, docs)
});
}
And then inside your route you would do:
app.get('/get-all-tasks',function(req,res){
Task.getAllTasks(err, docs){
if(err) return res.json(error: err)
res.json(msg: docs);
}
});
I'm not sure if getAllTasks should be a mongoose static, in which case your model would look something like this:
TaskSchema.statics.getAllTasks = function (callback) {
return this.find().lean().exec(callback);
}

Mongoose findById possibly sending Express headers?

I am trying to send an error if a condition is true using the Mongoose function findById. The problem is that Mongoose appears to be setting the res Express object and is then throwing an error when I try to set the headers myself. Here is the code:
console.log(res.headersSent); // false
Trade.findById(req.body.trade, function (err, trade) {
if (err) throw err;
// Ensure user is not making an offer on their own item
Item.findById(trade.listing, function (err, item) {
if (err) throw err;
if (req.decodedId == item.user) {
console.log(res.headersSent); // true (?)
return res.status(403).send({
success: false,
message: 'You cannot make an offer on your own item'
})
} else {
return;
}
})
And here is the stack trace for the error:
false // res.headersSent() before calling Trade.findById()
POST /api/v2/offer 200 148.799 ms - 162
true // res.headersSent() after calling Item.findById() and checking error condition
_http_outgoing.js:335
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:335:11)
at ServerResponse.header (/Users/Matt/Dropbox/work/TradeRate/prototype/node_modules/express/lib/response.js:700:10)
at ServerResponse.send (/Users/Matt/Dropbox/work/TradeRate/prototype/node_modules/express/lib/response.js:154:12)
at ServerResponse.json (/Users/Matt/Dropbox/work/TradeRate/prototype/node_modules/express/lib/response.js:240:15)
at ServerResponse.send (/Users/Matt/Dropbox/work/TradeRate/prototype/node_modules/express/lib/response.js:142:21)
at /Users/Matt/Dropbox/work/TradeRate/prototype/server/controllers/offers.js:48:40 // LINE THAT CONTAINS return res.status(403).send ...
at /Users/Matt/Dropbox/work/TradeRate/prototype/node_modules/mongoose/lib/query.js:1169:16
at /Users/Matt/Dropbox/work/TradeRate/prototype/node_modules/mongoose/node_modules/kareem/index.js:103:16
at process._tickCallback (node.js:355:11)
18 Jul 15:26:39 - [nodemon] app crashed - waiting for file changes before starting...
What could be causing this error? Is there aspect of the Mongoose API that sets the response headers that I'm missing?
EDIT: I added my full (updated) exported route handler in case that has some context that would make the problem more clear.
// POST /api/offer
exports.createOffer = function (req, res, next) {
console.log(res.headersSent);
Trade.findById(req.body.trade, function (err, trade) {
if (err) {
next(err);
return;
} // not good to throw from async events, let express' error handling middleware take care of it
// Ensure user is not making an offer on their own item
Item.findById(trade.listing, function (err, item) {
if (err) {
next(err);
return;
}
if (req.decodedId == item.user) {
console.log(res.headersSent); // true (?)
res.status(403).send({
success: false,
message: 'You cannot make an offer on your own item'
});
}
// all done with async stuff, pass the request long
next();
});
// If trade is expired, reject the offer
if (trade.expiresOn < Date.now()) {
res.status(403).send({
success: false,
message: 'This trade has expired and cannot accept new offers'
});
}
// Create new offer and add data
var newOffer = new Offer();
newOffer.items = req.body.items;
newOffer.trade = req.body.trade;
newOffer.save(function (err, offer) {
if (err) throw err;
});
// Add offer to items in offer
for (var i = 0; i < req.body.items.length; i++) {
Item.findById(req.body.items[i], function (err, item) {
if (err) throw err;
item.offers.push(newOffer._id);
item.save(function (err, item) {
if (err) throw err;
});
});
}
// Add offer to trade
trade.offers.push(newOffer._id);
trade.save(function (err, trade) {
if (err) throw err;
});
return res.send(newOffer);
});
};
Completely new answer, disregard my old one it was all wrong (it's been a while since I've used express).
Anyway the problem is you're calling async functions which return immediately so at the bottom there when you're calling return res.send(newOffer);, you're doing it before any of those callbacks return. So you returned before you
Check if the user is trying to create an offer on their own item
Add the new offer id to the items
Save any of those changes
Another problem is your loop there will likely fail horribly. There's no guarantee that you'll be pushing those items in order because findById and save as async, they return instantly and may be executed in any order. Plus there's no reason at all to save after every push. You need to either wait for each findById to return before continuing the loop (so you can't use a basic for loop, most likely a callback) or more correctly, just use a mongoose update query to do this all at once (you don't need to load an item to push an offer to it, just use $push)
The best way to handle all of this in express is with middleware. So change your code to this (I've added a dependency on http-errors to make error handling easier.
I'm assuming you're using the most recent version of express:
The Offer Route
var httpError = require('http-error') // needed for ezpz http errors
var express = require('express'); // needed for express.Router()
// middleware that loads the trade
function loadTrade(req, res, next) {
Trade.findById(req.body.trade, function (err, trade) {
req.trade = trade;
next(err, trade);
})
}
// middlware that checks expiration
function checkExpired(req, res, next) {
if(req.trade.expiresOn < Date.now())
next(httpError(403, 'This trade has expired and cannot accept new offers'));
else next();
}
// middleware makes sure the user isn't making an offer on their own item
function checkIsOwner(req, res, next) {
Trade.findById(req.trade.listing)
.select('user')
.exec(function(err, listing) {
if (err) next(err)
else if (listing.user == req.decodedId) next(httpError(403, 'You can not make an offer on your own item'))
else next();
})
}
// now we can create an offer
function createOffer(req, res, next) {
// req.trade was loaded and validated by our middleware
// if next(err) was called at any point this function wouldn't be called
var trade = req.trade;
Offer.create({trade: trade._id, items: req.body.items}, function (err, offer) {
if (err) {
next(err); // we only call next to trigger the error handler
return;
}
// now push the new offer id to all the items
Item.update({$in: req.body.items}, {$push: offer._id}, function (err, offer) {
if (err) next(err)
else res.json(newOffer);
})
});
}
exports.createOffer = express.Router()
.post(loadTrade)
.post(checkExpired)
.post(checkIsOwner)
.post(createOffer);
For handling errors I'd add this after you've setup all the routes (where you have your app.post('/api/v2/offer', ....) stuff:
app.use('/api/v2/*', function(err, req, res, next) {
res.status(err.status || 500).json({ success: false, message: err.message });
});
Now whenever you call next(err), this error handler will be called and send a status code and error message.

how to send a message from inside a callback function with mongodb in nodejs

I am coding a basic project manager, nothing fancy. I am writing the page where the project is created (with AngularJS) and am sending all the $scope to /create (the backend is Express.js). The router gets the JSON perfectly, and save it to a local MongoDB without problems.
My problem is that I want to set a message telling that the project was created successfully and send it back to AngularJS. This is my code.
router.js
module.exports = function(app, db) {
app.post('/create', function (req, res) {
var create = require("./../scripts/create")(req, res, db);
console.log(create); //just for testing whether I can receive the message.
});
}
create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
return 'Project created.'; //I want to return this string.
});
};
I don't know how to return something from inside the db.collection.insert's callback function.
So you have to remember that anonymous function calls in JavaScript are not assigned to anywhere. They are passed, and then lost. This is usually why we don't really have return statements in them.
var x = function () { return y }; gives x the value of y but since there is never an assignment of the value of a callback, a return statement is meaningless. Callbacks, no matter if they have a return value, will not give you a value. They may feed that return value up to the function that they were given to, but they are entirely lost to you.
The way to get around this is to do some trickery with the scope. Basically what you want to do is 'bump' the value you want to return up a scope you can assign and then return it there. For example, you can do this:
module.exports = function(req, res, db) {
var stringToReturn;
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
stringToReturn = 'Project created.'; //I want to return this string.
});
return stringToReturn;
};
This will work because the return value gets bound to module.exports, which is in turn bound to the result of
var create = require('./create');
console.log(create('something')) //should log 'Project created.'
Solved!!
Router.js
module.exports = function(app, db) {
app.post('/create', function(req, res) {
var create = require("./../scripts/create")(req, res, db);
});
});
Create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, records) {
if (err) throw err;
res.send("Project created.");
});
};
Now Angular is receiving the response from the server.

Resources