I am attempting to check if a user owns a document before updating it or deleting it, and would like to keep this as DRY as possible. Ideally, I would not have to make two calls to the database where I would first findById().then(doc => {check if user owns document and then -> doc.findByIdAndUpdate() }) but rather keep this as one call to the DB.
I am constantly having to execute this check on express routes and have thought about implementing this layer of logic on the mongoose .pre('update') middleware. but am unaware how to pass the incoming userid from the req object to my middleware validation function?
Are there any better layers to implement this checking functionality? or am I going to have to make the two requests to the database every time I want to check if a user owns a document and write this out in every express route?
My current implementation is:
const addDocToDoc = (req, res, next) => {
let doc1id = req.params.id;
let doc2id = req.params.doc2id;
Doc1.findById(doc1id)
.then(doc1 => {
if(userCanAlter(doc1, req.user, res)) {
doc1.doc2s.push(doc2id)
return doc1.save().then(updatedDoc1 => res.send(updatedDoc1))
}
}).catch(next)
}
Where userCanAlter() looks like this:
function userCanAlter(instance, user, res) {
if (!instance) { res.status(404).send("Document does not exist."); return false}
if (instance.user != user) { res.status(401).send("User unauthorized"); return false}
else return true;
}
Obviously, this is a very simple update but the more complex updates would require more configuration before saving.
Current implementation in question found to be the best & DRY’est implementation.
You can simply wrap your user in find query and use findOne(), Something like:
const addDocToDoc = (req, res, next) => {
const {
user = ''
} = req;
const {
id = '', doc2id = ''
} = req.params;
Doc1.findOne({
_id: id,
user
})
.then(doc => {
if (!doc) {
return res.status(400).json({
message: 'User Not Found!!'
});
}
doc.doc2s.push(doc2id);
doc.save()
.then(updatedDoc1 => res.status(200).json(updatedDoc1))
.catch(err => res.status(500).json({
message: 'Error While Updating!!',
error: err
}));
})
.catch(err => res.status(500).json({
message: 'Error While Fetching!!',
error: err
}));
}
Also I'd suggest if you work a bit on naming things, as this may mess up things a few times.
In case if you wanna throw specific error for unauthorized user, you can stick to your way of implementation, just don't need a separate method to check ownership. I've simplified it with async/await and the code is:
const addDocToDoc = async (req, res, next) => {
try {
const {
user = ''
} = req;
const {
id = '', doc2id = ''
} = req.params;
const doc = await Doc1.findById(id);
if (!doc || !doc.user || doc.user !== user) {
return res.status(401).json({
message: 'Unauthorized User!!'
});
}
doc.doc2s.push(doc2id);
const updatedDoc1 = await doc.save();
return res.status(200).json(updatedDoc1);
} catch (err) {
res.status(500).json({
message: 'Error While Updating Record!!',
error: err
});
}
}
Ps: You may need some modification as i couldn't get a chance to run it.
Hope this helps :)
Related
So, I'm not 100% why this isn't working as intended. I have an Edit Profile React component (I'm learning how to build a SSR-based application currently, using the MERN stack) - but when I submit the edit, I get an error that "user.save is not a function - Code:
From the routes:
router.route('/api/users/:userId')
.get(authCtrl.requireSignin, userCtrl.read)
.put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update)
.delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove)
The API Helper:
const update = async (params, credentials, user) => {
try {
let response = await fetch('/api/users/' + params.userId, {
method: 'PUT',
headers: {
"Accept": 'application/json',
Authorization: 'Bearer ' + credentials.t
},
body: user
})
return await response.json()
} catch (err) {
console.log(err)
}
}
And lastly, the actual controller, that handles all the logic behind the update: (This function sanitizes the password information before passing it back to the client, hence the undefineds)
const update = (req, res) => {
let form = new formidable.IncomingForm()
form.keepExtensions = true
form.parse(req, async (err, fields, files) => {
if (err) {
return res.status(400).json({
error: "Photo could not be uploaded"
})
}
let user = req.profile
user = extend(user, fields)
user.updated = Date.now()
if(files.photo){
user.photo.data = fs.readFileSync(files.photo.path)
user.photo.contentType = files.photo.type
}
try {
await user.save()
user.hashed_password = undefined
user.salt = undefined
res.json(user)
} catch (err) {
console.log(err)
return res.status(400).json({
error: errorHandler.getErrorMessage(err)
})
}
})
}
This isn't a production level application, just for me learning how to do this from scratch (without CRA, and all contained in one project using SSR)
EDIT: After some digging, console.logs and console.dirs, I discovered that the updates passed from the component aren't even being passed to the controller. The stale data (from the database) are logging, but req.profile is completely empty. I may re-visit this code completely and make some major changes to it.. All part of learning, right?
Here are the auth methods that were requested (I'm using Session Storage for now, but that may change to localStorage):
import User from '../models/user.model'
import jwt from 'jsonwebtoken'
import expressJwt from 'express-jwt'
import config from './../../config/config'
const signin = async (req, res) => {
try {
let user = await User.findOne({email: req.body.email})
if (!user) {
return res.status(401).json({error: "User not found"})
}
if (!user.authenticate(req.body.password)) {
return res.status(401).send({error: "Email and Password do not match"})
}
const token = jwt.sign({_id: user._id}, config.jwtSecret)
res.cookie('t', token, {expire: new Date() + 9999})
return res.json({
token,
user: {
_id: user._id,
name: user.name,
email: user.email
}
})
} catch (err) {
return res.status(401).json({error: "Could not sign in"})
}
}
const signout = (req, res) => {
res.clearCookie('t')
return res.status(200).json({message: "Signed out"})
}
const requireSignin = expressJwt({
secret: config.jwtSecret,
algorithms: ['sha1', 'RS256', 'HS256'],
userProperty: 'auth'
})
const hasAuthorization = (req, res, next) => {
const authorized = req.profile && req.auth
&& req.profile._id == req.auth._id
if (!(authorized)) {
return res.status(403).json ({error: "User is not authorized"})
}
next()
}
export default {
signin,
signout,
requireSignin,
hasAuthorization
}
Possible places where you could have a mistake: (code is not shown)
If your req.profile isn't a mongoose object, this won't work
let user = req.profile
From your other posts, I think you're probably getting req.profile from your jwt. That means this is not a mongoose object. What you'll need to do is either:
As you mentioned, use findByIdAndUpdate passing the id and the object to be updated. Note that if you have a mongoose middleware for save it won't run here
Do a user = await User.findById(id), update the user as you see fit, then use user.save. This gives you a bit more control over it, but runs 2 operations.
This has been solved.. My issue was apparently with the form not passing the request body properly to the API, which was caused by a faulty install of a dependency. Once I got that solved, the rest fell into place, and I can now do what I need to do with ease..
Thank you all who attempted to troubleshoot this with me.
I am part of a project which uses nodeJS + ExpressJS for the backend application, and We have a middleware function to log accesses on routes in the database.
When an User tries to access the /user route with a post method, a middleware receives the Request, get information like the URL, ip address, origin, a description of the event and record it in the database.
Everything works just fine, but some of my teammates were discussing about how to log the erros also in the database.
I will put bellow a code example
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
response.status(500).json({ error });
}
};
When we were discussing about how to implement it, we realized we'd have to call a log error function in a lot of places since we have many flows which leads to an error response.
So the code would be just like:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
function() // here we would log the error
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // here we would log the error
response.status(500).json({ error });
}
};
is it a properly way of dealing with error logging or is there any better way of doing it? Thank you for reading!
You can use the built-in error handler provided by Express.JS for this kind of logic, of course it requires a bit of setup. Like most things in Express.JS, the error handler it's just a middleware function with four parameters err, req, res and next, which MUST be placed after all your other middlewares. It comes to play when, inside a router handle (for example), your call next(err) (where err it's an Error) or by simply throwing err. Check out the documentation for more.
app.use(...)
app.use(...)
app.use((req, res, next) => {
if (req.params.id === undefined) {
let error = new Error("ID required.")
error.statusCode = 400
error.statusMessage = "Request not valid, ID not found."
throw error;
} else {
// Do some stuff...
}
})
// NOTE: After ALL your other middlewares
app.use((err, req, res, next) => {
console.error(err)
res
.status(err.statusCode)
.json(err.statusMessage)
})
Ideally you should log the errors only inside the catch block. Whenever you encounter an error just throw a new error by calling throw new Error("Type your error message here"). Then your function inside catch block will log and handle the error appropriately.
I would change your code to this:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
throw new Error("E-mail already in use")
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // log your error
response.status(500).json({ error.message });
}
};
Read more about Errors here.
I try the response model after create data but it doesn't work. It shows data on "console.log" and it didn't respond when I use "resolve({})".
In the routers.js:
const register = require('./functions/register');
module.exports = router => {
router.get('/', (req, res) => res.end('Welcome to Idol Fan With your Idol !'));
//======REGISTER & LOGIN WITH SOCIAL =====
router.post('/socialuser', (req, res) => {
const social_id = req.body.social_id;
const token = req.body.token;
const name = req.body.name;
const email = req.body.email;
const photoprofile = req.body.photoprofile;
const tokenfirebase = req.body.tokenfirebase;
if (!social_id) {
res.status(400).json({message: 'Invalid Request !'});
} else {
register.registerUser(social_id, name, email, photoprofile, token, tokenfirebase)
.then(result => {
res.status(result.status).json({status: result.status, message: result.message, user: result.user})
})
.catch(err => res.status(err.status).json({message: err.message}));
}
});
}
Function Register.js:
const userfan = require('../models/user');
exports.registerUser = (social_id, name, email, photoprofile, token,
tokenfirebase) =>
new Promise((resolve, reject) => {
const d = new Date();
const timeStamp = d.getTime();
userfan.find({social_id: social_id})
.then(users => {
if (users.length == 0) {
let newUser = new userfan({
social_id: social_id,
name: name,
email: email,
photoprofile: photoprofile,
token: token,
tokenfirebase: tokenfirebase,
created_at: timeStamp
});
newUser.save()
.then(doc => {
console.log("run... " + doc);
resolve({
status: 200,
message: 'User Register Sucessfully !',
user: doc
});
})
.catch(err => {
console.error(err)
if (err.code == 11000) {
reject({status: 409, message: 'User Already Registered !'});
} else {
reject({status: 500, message: 'Internal Server Error !'});
}
});
} else {
return users[0];
}
})
.then(usertemp => resolve({status: 200, message: "Login Successfully !", user: usertemp}))
.catch(err => {
console.log(err.message);
reject({status: 500, message: err.message});
});
});
This is my result after run on server:
As a result and code above. I have a question Why "user: doc" no response?. Thank you so much!
userfan.find.then.then (synchronous since nothing requires waiting) is called before newUser.save.then (asynchronous since under the hood it waits for the DB to answer).
So both resolve are called, but only the first call is considered, and the first one to be called is the one using usertemp. And this one receives undefined as argument, because of the implicit return undefined at the end of userfan.find.then.
Your flow should be:
userfan.find().then(users => {
if (!users.length) {
let newUser = ...
// explicitly return the promise, which will give the created user
return newUser.save().then(doc => {
// format and return the value you want
return {user: doc, message: 'registered'};
});
} else {
// another explicitly returned value, but you already have this
return {user: users[0], message: 'login'};
}
// it's not possible here because we returned before it, but your code reached this point
// and, implicitly, any function ending with no return does:
return undefined;
// this .then receives *either* the promise from newUser.save *or* the object from the else block
}).then(structure => {
structure.status = 200;
return structure; // here structure is {status: 200, message: 'registered'|'login', user: document}
});
Also, note that using the syntax shortcut (without curly braces around the function body) for arrow functions implies the one-line body is returned:
singleArgument => doSomething();
// is actually *strictly* equivalent to
(singleArgument) => { return doSomething(); }
With these ways of writing, it's easy to lose the reflex of writing return when it's needed.
I've been reading some CRUD / Mongoose guides, but haven't a good explainer for conditionally updating fields.
So for example, an action called updateItem is used in one place to update item.price but in another place it updates item.color. Does anyone know a good explanation or tutorial for Mongoose CRUD APIs that shows this?
I'm getting the blow code to work fine, but I have a feeling it could be cleaner :)
Thanks!!!
router.put('/tasks/:id', (req, res) => {
Task.findByIdAndUpdate(req.params.id,
req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours }, { new: true })
.then(task => {
res.status(201).json(task)
})
.catch(err => {
console.log('Our error', err)
})
});
Another approach you could take is to first retrieve the object, and then only update the value if it is passed into the put request. An example of that could be something like this:
router.put('/tasks/:id', (req, res) => {
let price = req.body.price;
let color = req.body.color;
Task.findById(req.params.id, function (err, task) {
if (err) return handleError(err);
task.color = color || task.color;
task.price = price || task.price;
task.save(function(err, updatedTask) {
if err return handleError(err);
return res.send(updatedTask);
});
});
});
Here's another cleaner approach using async-await functions:
// Import promisify from utils
const promisify = require('utils').promisify;
// Wrap findByIdAndUpdate into a promise
const updateOwnerPromise = promisify(Task.findByIdAndUpdate);
// Write an async handler now
updateOwnerPromiseAsync = async (req, res) => {
const replacementObject = req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours };
try {
await updateOwnerPromise(replacementObject, { new:true} );
return res.status(200).send({ message: 'Owner updated successfully!' });
} catch(err) {
// TODO: handle error here
console.log('Our error', err)
return res.status(500).send({ message: 'Failed to update owner, because of some issue at the server!' });
}
}
// Modify the express route with the handler
router.put('/tasks/:id', updateOwnerPromiseAsync);
I have 2 tables: user and material which have a m:m relationship. The intersection entity is journalMaterials. I am trying to send a POST request to insert into journalMaterials. Also, this table has 2 attributes: recycledQuantity and recycleDate. I tried something, but if i insert with a materialId which doesn't exist it doesn't give me "not found".
app.post('/users/:uid/materials/:mid', (req, res, next) => {
User.findById(req.params.uid)
.then((user) => {
if (user){
let journalMaterial = req.body
journalMaterial.userId = user.id
Material.findById(req.params.mid)
.then((material) => {
if (material){
journalMaterial.materialId = material.id
return JournalMaterial.create(journalMaterial)
}
else{
res.status(404).send('not found')
}
})}
else{
res.status(404).send('not found')
}
})
.then(() => {
if (!res.headers){
res.status(201).json('created')
}
})
.catch((err) => next(err))
})
I've solved it. Here is the correct code.
app.post('/users/:uid/materials/:mid', (req, res, next) => {
const { uid, mid } = req.params;
Promise.all([
User.findById(uid),
Material.findById(mid)
])
.then(([user, material]) => {
if (user && material) {
let journalMaterial = req.body
journalMaterial.userId = user.id
journalMaterial.materialId = material.id
res.status(201).json('created')
return JournalMaterial.create(journalMaterial)
}
res.status(404).send('not found')
})
.catch(err => next(err));
})
Slightly re-wrote this to make it a bit more readable. Removed your nested promise calls... (let's not dive into promise hell when they try to get rid of callback hell..)
app.post('/users/:uid/materials/:mid', (req, res, next) => {
const { journalMaterial } = req.body;
const { uid, mid } = req.params;
Promise.all([
User.findById(uid),
Material.findById(mid)
])
.then(([user, material]) => {
if (user && material) {
journalMaterial.userId = user.id;
journalMaterial.materialId = material.id;
return JournalMaterial.create(journalMaterial);
}
res.status(404).send('not found');
})
.then(() => {
if (!res.headers) {
res.status(201).json('created');
}
})
.catch(err => next(err));
});
Your check against if(user) currently passes. It seems that if that's what is happening, you're always getting an object back. Lots of databases generally don't simply return a null or false value, but rather an object with a bunch of meta data. In that object is generally the data you requested (ie, user.data.id, but it may be that user.data is NULL). Can you verify what the exact contents of Users is? It's evaluating to truthy, thus it must have something in it.