Express REST API - Delete Method - api

I am getting stuck on the delete method for my API. My application requires a user to log in and then he can add courses. The courses are stored in a nested array inside the User model. I want the user to be able to cancel (delete) a course from the view and then have the course deleted from the user's profile on the server. I am getting a 404 response event though the variables I am comparing are identical.
This is my ajax call to delete a specific course:
jQuery.ajax({
url: "/test/signups/5387c1a0fb06e48f4658170c",
type: "DELETE",
success: function (data, textStatus, jqXHR) {
console.log("Post resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
}
});
This is my delete method:
app.delete('/test/signups/:id', isLoggedIn, function(req, res) {
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
var found = false;
var singlesignup = user.signup.filter(function(e){ return e._id == req.params.id })[0]
user.signup.forEach(function (singlesignup, index) {
if (singlesignup._id === req.params.id) {
found = index;
}
});
if(found) {
user.signup.splice(found, 1);
res.json(200, {status: 'deleted'});
} else {
res.json(404, {status: 'invalid survey question deletion'});
}
}
});
});

The _id values in mongodb are not strings, they are instances of the ObjectId class and they don't work correctly with the == or === operators. It's completely a nuisance. But anyway try converting them to strings before comparing: singlesignup._id.toString() === req.params.id. To get it truly correct in the long run, make sure you handle all the cases of null, string or ObjectId. Consider a helper library such as objectid.

Related

Why does the 1st middleware of the array needs to call next() but the 2nd does not?

I am new in web development. While following the Node Express Mozilla Tutorial, I came accross this array of controller middlewares that made me confused regarding the use of next().
This array has 3 middlewares:
One for making sure a certain form parameter is in array format
One for doing validation and sintizaiton of the for parameters
One for processing and giving a response to the request
My doubt is: why does the 1st one calls next() but the 2nd does not?
Below the block of code with the array of controller middlewares:
// Handle book create on POST.
exports.book_create_post = [
// Convert the genre to an array.
(req, res, next) => {
if(!(req.body.genre instanceof Array)){
if(typeof req.body.genre ==='undefined')
req.body.genre = [];
else
req.body.genre = new Array(req.body.genre);
}
next();
},
// Validate and sanitise fields.
body('title', 'Title must not be empty.').trim().isLength({ min: 1 }).escape(),
body('author', 'Author must not be empty.').trim().isLength({ min: 1 }).escape(),
body('summary', 'Summary must not be empty.').trim().isLength({ min: 1 }).escape(),
body('isbn', 'ISBN must not be empty').trim().isLength({ min: 1 }).escape(),
body('genre.*').escape(),
// Process request after validation and sanitization.
(req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
// Create a Book object with escaped and trimmed data.
var book = new Book(
{ title: req.body.title,
author: req.body.author,
summary: req.body.summary,
isbn: req.body.isbn,
genre: req.body.genre
});
if (!errors.isEmpty()) {
// There are errors. Render form again with sanitized values/error messages.
// Get all authors and genres for form.
async.parallel({
authors: function(callback) {
Author.find(callback);
},
genres: function(callback) {
Genre.find(callback);
},
}, function(err, results) {
if (err) { return next(err); }
// Mark our selected genres as checked.
for (let i = 0; i < results.genres.length; i++) {
if (book.genre.indexOf(results.genres[i]._id) > -1) {
results.genres[i].checked='true';
}
}
res.render('book_form', { title: 'Create Book',authors:results.authors, genres:results.genres, book: book, errors: errors.array() });
});
return;
}
else {
// Data from form is valid. Save book.
book.save(function (err) {
if (err) { return next(err); }
//successful - redirect to new book record.
res.redirect(book.url);
});
}
}
];

Transform answer of REST API

I tried to create a database using sqlite3 and ES6. To receive the data of the database I created REST methods.
When I use GET on the API I receive this answer for example:
{
"user": [
{
value1: tmp,
value2: test,
}
]
}
But I need the answer to be simple JSON, it should be like this
{
"user": {
value1: tmp,
value2: test,
}
}
The code I used for the GET method is:
// GET the user
app.get("/api/userTemplate", (req, res, next) => {
var sql = "select * from user"
var params = []
db.all(sql, params, (err, rows) => {
if (err) {
res.status(400).json({"error":err.message});
return;
}
res.json({
"user":rows
})
});
});
I am aware this will return the full table of users, this is intended.
EDIT: "user":rows[0] works for me, but this returns only one row of the table. Is it possible to modify this in order to return all rows of the table?

How to embed bcrypt into mongose API call?

Setup
I am doing web site authorization, and want to embed best practices into it, while keeping code clean and readible. For now I have classic code like this:
let foundUser = await userModel.findOne({ email: recievedEmail });
if(!foundUser)
error("not authorized!");
const isPasswordMatch = await bcrypt.compare(recievedPassword, foundUser.password);
if(!isPasswordMatch)
error("not authorized!");
foundUser.update({ $set: { lastLogin: new Date() }, $push: { myEvents: authEvent } });
foundUser.save();
success("authorized OK!");
Meanwhile, I've asked a question on the best mongoose command to perform auth, and we've forged up the following "auth-check-and-update" command, in an "atomic" manner:
const foundUser = await userModel.findOneAndUpdate(
{ email: recievedEmail, password: recievedPassword },
{ $set: { lastLogin: new Date() }, $push: { myEvents: authEvent } }
);
if(foundUser)
success("authorized OK!");
else
error("not authorized!");
Idea here is obvious - if a user with matching email and password is found then user is considered as authorized, and its last login timestamp is updated (simultaneously).
Problem
To combine best practices from the two above, I need somehow to embed bcrypt.compare() call inside findOneAndUpdate() call. That is tricky to do, because I cannot just "compare hashed passwords"; bcrypt just works differently from simple hashes (like sha or md5): For security reasons it returns different hashes every time. (Answers in the link explains "why and how").
Solution Attempt
I've looked into mongoose-bcrypt package: it is utilizing Schema.pre() functionality:
schema.pre('update', preUpdate);
schema.pre('findOneAndUpdate', preUpdate);
To get the idea, please, take a look at mongoose-bcrypt\index.js.
You will see, that preUpdate affects only creating new user (..andUpdate part), but not actual checking (findOne.. part). So this plugin could fit for implementing "user registration" / "change password". But it can't work for authorization in the proposed way.
Question
How would you "combine" bcrypt.compare() and userModel.findOneAndUpdate() calls under such circumstances?
What about compare password in UserModel like this
// method to compare password input to password saved in database
UserModel.methods.isValidPassword = async function(password){
const user = this;
const compare = await bcrypt.compare(password, user.password);
return compare;
}
And inside your auth or passport (i am using passport) do something like this
passport.use(new LocalStrategy(
(username, password, done) => {
// change your query here with findOneAndUpdate
User.findOne({ username: username }, (err, user) => {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.isValidPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));

Redis mocha Test case issue

I have one file call cache.js
var redisCache = redis.createClient(port, name);
redisCache.on("error", function(err) {
logger.error("Error connecting to redis", err);
});
exports.setExp = function(key, timeLeft, data){
redisCache.set(key, JSON.stringify(data), function (err, reply) {
console.log("error "+err);
console.log("reply "+reply);
if(err) {
console.log("error "+err.command + err.code);
logger.info("This errror on set key related to node_redis");
}
if(reply == 'OK') {
redisCache.expire(key, timeLeft, function (err, reply) {
if(err) {
logger.info("This errror on expire key related to node_redis");
}
if(reply === 1) {
logger.info(key+" key expire time set as "+timeLeft+" successfully!");
}
});
}
});
}
Now I want to write the test case for the above setExp function but some how the node_redis aways return me the err as null and reply as OK
below is my test case.
var cache = require(path.join(__dirname,'..','/cache'));
describe('cache', function () {
it('Cache #setExp() ', function (done) {
var result = cache.setExp(undefined, 0, []);
assert.equal('OK', results);
done()
})
})
IF I change the it should follow the below error I mention as per the node_redis test case
var result = cache.setExp('foo', 10, []);
it should return me the error called ERR wrong number of arguments for 'set' command
var result = cache.setExp(undefined, 0, []);
It should accept the below error log as
assert.equal(err.command, 'SET');
Please suggest me right way to achieve this.
Your thinking seems to be almost completely wrong here.
First of all, you're writing and using setExp as if it's a synchronous operation, but it isn't. It will return before the request is made to redis. It also never returns anything, so even if it was synchronous, result in your tests will always be undefined.
You need to redesign setExp as an asynchronous operation, either by using the async keyword, returning a promise, or having it accept a callback function.
Second of all, if you want to set an expiration on a Redis key, you should set it when you set the key itself, instead of setting the key with no expiration and then trying to add the expiration later. Otherwise you run the risk of the expiration setting failing, and then winding up with an orphaned key that never expires.
Here's an example, using node's util.promisify to as described in the node_redis docs:
var redis = require('redis');
var {promisify} = require('util');
var redisCache = redis.createClient(port, name);
redisCache.on("error", function(err) {
logger.error("Error connecting to redis", err);
});
var set = promisify(redisCache.set).bind(redisCache);
exports.setExp = function(key, timeLeft, data){
return set(key, JSON.stringify(data), 'EX', timeLeft.toString(10))
.then((reply) => {
if (reply !== 'OK') throw new Error(reply);
return reply;
});
};
In your tests you'd do something like this:
var cache = require('../cache');
describe('cache', function () {
it('Cache #setExp() ', function () {
let key = 'some key';
let timeLeft = 12345;
let data = { foo: 'bar' };
return cache.setExp(key, timeLeft, data)
.then((result) => {
assert.equal('OK', result);
});
});
});
Also, results and result are not the same thing. In your test case, there is no variable called results.
Oh, and don't do this:
var cache = require(path.join(__dirname,'..','/cache'));
require already supports paths relative to __dirname. Just do this:
var cache = require('../cache');

Post authorization doesn't work

I'm trying to restrict ability for one user to edit someone else's posts.
When I click 'Edit post' it change the author of the post, is it because of 'save' method or ?
Here is my attempt:
router.put('/posts/:id', passport.authenticate('jwt'), (req, res) => {
Post.findOne({_id: req.params.id}, (err, post) => {
if (err) throw err;
if (post.author = req.user._id) {
post.title = req.body.title,
post.content = req.body.content,
post.postImageUrl = req.body.postImageUrl
post.save((err, updatedPost) => {
if (err) throw err;
else {
res.json({message:'You have successfully updated your post', success: true});
}
});
} else {
res.json({success: false, message: 'You are not allowed to do this.'});
}
});
});
I checked post.author and req.user._id, they match.
Please check your condition for checking if the current user is the post's author.Instead of checking for equality you are trying to assign and change the author value.
if (post.author = req.user._id)
Solution:
Mongoose offers a .equals method for checking equality for object ids.Here is the link for the docs for mongodb-native driver .equals method, which is used by Mongoose.
if (post.author.equals(req.user._id))