So whilst learning JS and specifically the MEAN 2 stack i'm trying to build out a basic multi tenanted app. Im building out sign up routes in express and the flow i'm trying to achieve would be:
Sign up with company name, email and password. The info would go to save a new tenant, then return the _id of the new tenant and then use this new id, the email and the password to save a new user.
The closest is:
router.post('/', function (req, res, next) {
var tenant = new Tenant({
name: req.body.name
});
var newTenant;
tenant.save(function (err, tenant) {
if (err) {
return res.status(500).json({
title: 'An error has occured',
error: err
});
}
res.status(201).json({
message: 'Tenant created',
obj: tenant
});
return(tenant._id);
newTenant = tenant;
});
Tenant.findById(newTenant._id, function(err, tenant) {
if (err) {
return res.status(500).json({
title:'An error occured',
error: err
});
}
var user = new User({
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 10),
active: req.body.active,
tenant: tenant
});
user.save(function (err, user) {
if (err) {
return res.status(500).json({
title: 'An error has occured',
error: err
});
}
res.status(201).json({
message: 'User created',
obj: user
});
});
});
});
module.exports = router;
I'm getting an error: cant set headers after they've been sent.
I think I know where i'm wrong, with returning the tenant info. I think Async is the answer but cant figure out how to implement it. Sorry if this is a stupid question or i'm missing something obvious, I'm super new to this and callbacks are doing my head in.
This is happening because res.status() sets headers as soon as it has fired. You try to do this multiple times both when checking for errors, and then you try to set the status code again in Tenant.findById().
You end up with a flow like:
if (err) set headers
set headers (again)
findById()
if (err) set headers
set headers (again)
You have to be careful when writing out your response that you only do it at the last point in your logic flow. You can also could set up a global err handler and throw new Error() and stop the flow of logic and handle the output immediately. If you don't, your code will continue to execute even though it encountered an error.
Another tip: callbacks don't work well with returns. And although you can arrange them to work, or implement a promise architecture instead, the simplest fix (and the easiest to learn) is to make your functions all asynchronous.
Try instead something like:
tenant.save(function (err, tenant, callback) {
// add a callback param to your fn ^
if (err) {
throw({
code: 500,
title: 'An error has occured',
error: err
});
} else {
// ^ add an else statement so you don't set the headers twice
// (because res.status() sets headers)
res.status(201).json({
message: 'Tenant created',
obj: tenant
});
}
callback(err, tenant);
// call your async function instead of return,
// and pass both err and tenant as params
// (one or the other may be undefined though, if it fails/succeeds)
});
... Create additional isolated functions (or even modules) for the rest of your tasks, then you can then call your function like this:
tenant.save(function(err, tenant) {
Tenant.findById(tenant._id, function(err, tenant) {
var user = new User({...})
user.save()
});
});
Related
Note: What you see below is the updated description of my problem, because I have been going down a rabbit hole and finding the root cause of a problem.
So, I found what's causing it (read 'OLD DESCRIPTION' below to know the context), but I have zero idea why is it being caused. So, the thing is, apparently Node cannot find the utils.getHash function (I have a separate file called utils.js which exports the getHash function), so it is never called, and execution never moves forward.
utils.js
...
const getHash = (password) => {
return crypto.createHash('sha3-512').update(password).digest('hex')
}
...
module.exports = {
getHash: getHash
}
Someone help please :(
OLD DESCRIPTION
There's a weird problem I am facing. I wrote a backend API server in ExpressJS, and one of the task it performs is user authentication. I am using MongoDB as the database, and Mongoose to connect and perform operations on it.
The problem I am facing is that the checkUserCreds function does not proceed after a point (commented in code), and Express just returns a blank JSON response.
And I say it it's weird, because I tested with the SAME code just 2 days back, it worked correctly like it should.
user.js
userSchema.statics.checkUserCreds = function (email, password) {
return new Promise((resolve, reject) => {
// Execution goes upto '$and' line, then it goes nowhere; no exceptions are raised
User.findOne({
$and: [{ email: email }, { password: utils.getHash(password) }]
}, (err, userDoc) => {
if (err) {
reject({ status: "ERROR", message: err })
} else if (userDoc) { // If valid credential
console.log(`User with email '${email}' logged in`)
resolve({ status: "OK", message: "Login successful!" })
} else { // If invalid credential
reject({ status: "ERROR", message: "Invalid credential!" })
}
})
})
}
api.js
// Route - Login (POST: email, password)
router.post("/login", (req, res) => {
// If user is already logged in, reject further login
if (req.session.email) {
res.json({ status: "ERROR", message: "Already logged in!" }).status(403).end()
} else {
// Get data from body
var form = formidable()
form.parse(req, (err, fields, files) => {
if (err) {
res.json({ status: "ERROR", message: err }).status(500).end()
} else {
// Check if credentials are valid
User.checkUserCreds(fields.email, fields.password).then((result) => {
// This portion of code isn't reached either
req.session.email = fields.email
res.json(result).status(200).end()
}).catch((err) => {
res.json(err).status(401).end()
})
}
})
}
})
Can anyone tell me why this is happening?
I have many endpoints in my express app that have many conditions. I want to find what is the best design pattern for them without repeating myself so much.
This is one of my simpler routes:
router.post('/reset/:token',
asyncMiddleware(async(req, res, next) => { await reset(req, res, next, pino); })
);
Inside reset()I need to check a couple of things, such as:
If all the required body params are there
If the email from the decrypted token matches the one from the database
If the password was saved successfully.
I would like to check those conditions that without having a huge function, but I don't know what is the best way to do so.
Entire Route Code
export async function reset(req, res, next) {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
const user = await userAssociatedWithEmail(req.body.email);
if (!user) {
return res.status(501).json(Error.noActiveUserAssociatedWithEmail);
}
// Generate token
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
const emailSent = await sendForgotEmail(token, user);
if (!emailSent) return res.status(500).json(Error.emailNotSent);
else return res.json({ status: 'success', message: 'Email sent successfully.' });
}
What I would like to do
Final Result I'd like to have
export async function reset(req, res, next) {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
// If error inside userAssociatedWithEmail, I'd like to stop execution and
// return res.status(501).json(Error.noActiveUserAssociatedWithEmail) from inside
// that function, without having to add an if condition below as exists in the
// original code above
const user = await userAssociatedWithEmail(req.body.email);
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
// Again I'd like to return res.status(500).json(Error.emailNotSent)
// from inside sendForgotEmail IF there is an error
const emailSent = await sendForgotEmail(token, user);
// If everything is successful, finally I'd return this
return res.json({ status: 'success', message: 'Email sent successfully.' });
}
Explanation of the result in word:
I'd like to be able to handle the conditions and scenarios without having to handle it in the main reset function if that's possible (aka, without having to store a response in a variable, check the variable and return in the main function in the case of error).
So for example, instead of:
const allParamsAreValid = validParams(token, email, new_password, res);
if (!allParamsAreValid) return;
I'd like to do something like:
validateParams(token, email, new_password, res);
And then inside validateParams() if a param is missing, I'd force exit the program besides also setting the response with res.json({}).
Is that possible?
You can make all your asynchronous functions that return promises reject their promise with the status and value you want sent. Then, you can handle that rejected promise in one place:
export async function reset(req, res, next) {
try {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
// If error inside userAssociatedWithEmail, I'd like to stop execution and
// return res.status(501).json(Error.noActiveUserAssociatedWithEmail) from inside
// that function, without having to add an if condition below as exists in the
// original code above
const user = await userAssociatedWithEmail(req.body.email);
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
// Again I'd like to return res.status(500).json(Error.emailNotSent)
// from inside sendForgotEmail IF there is an error
const emailSent = await sendForgotEmail(token, user);
// If everything is successful, finally I'd return this
res.json({ status: 'success', message: 'Email sent successfully.' });
} catch(e) {
res.status(e.status || 500).json(e.errData)
}
}
And, then all of your asynchronous functions would reject if they have an error condition and set both e.status and e.errData on the rejected reason. That would allow you to have one common error handler and let the async function collect any rejected promise into your try/catch for you. This is meant to be the clean way you handle rejections in a series of await calls where you want the whole function to finish.
Then, you also need to make sure your asyncMiddleware() function is NOT also sending a response (can't really tell what its purpose is). You don't show that code, so I can't see what it's doing.
You don't show any code that uses validateParams(), but if it was synchronous, then it could just throw an exception with the right fields set on it and the try/catch would also catch it just like it would catch the async rejections.
For example:
function validateParams(token, email, new_password) {
let err = new Error();
err.errData = {status: 'error'};
if (!token) {
err.errData.message = 'invalid token';
throw err;
}
if (!email) {
err.errData = Error.paramsMissing('email');
throw err;
}
if (!new_password) {
err.errData.message = 'invalid new password');
throw err;
}
}
If you wanted to, you could also send an error response in validateParams(), but I think it's cleaner not to because they you can collect all errors including all your await asynchronous calls in one try/catch in the route handler and frankly, it's a lot more readable and understandable code not to send a response in some function calls, but not in others. I try to keep all my responses both error and success sent at the same level. Then, it's really easy to keep track of and to avoid accidentally trying to send multiple responses.
Then, in your route handler, you'd just call validateParams(...) just like that. If it throws, your try/catch would catch it and send the appropriate error. If no error, then execution would just continue.
Since you are passing res object to the validateParams method, you can do something like this,
async function validateParams(token, email, new_password, res) {
if (token && emal && new_password && res) {
// all values are available
// perform your desired operation
} else {
// exit from the method and pass info to the client
return res.json({ message: 'Invalid parameter' });
}
}
In this case, all you have to do is invoking the validateParams.
await validateParams(token, email, new_password, res);
If there is a missing parameter, the server passes the control to the client immediately. Otherwise, you can perform your operation there.
So, I have the following code:
function SignUp(req, res, next){
const userCreds = {
email: req.body.email,
password: req.body.password
}
//Username and password must exist
if(!userCreds.email || !userCreds.password){
res.status(422).send({ error: 'Email and Password required'});
throw new Error(('Email and Password Required'));
}
//See if email is already being used
Users.findOne({ email: userCreds.email })
.then(function(user){
//If user does exist, return Error
if(user){
res.status(422).send({ error: 'Email is in use'});
throw new Error(('Email and Password Required'));
}
//Else if email is true, create and save user error
const newUser = new Users(userCreds);
//Save the user
return newUser.save(); //Return promise
})
.then(function(doc){
//Respond saying all OK
res.json({
success: true,
email: doc.email
});
})
.catch(function(err){
if(err)
return next(err);
});
}
The function above is passed to an Express route, like this
app.get('/signup', SignUp);
In this code, there are two different 'errors' that can occur and I need to handle. One kind of error is that the user request cannot be processed (Trying to create an account without supplying both Email and Password, or using an Email that already is being used). The second kind of error is one that I have less control in: rejected promises from the Mongoose package.
Let's say that I have received a bad request, an error of type 1. I want to handle it by setting the header of the response to 422, and sending that response with a message detailing why it could not be processed. At that point the execution would end.
If I get an error of type 2, I want to call next(error) and stop execution at that point.
The problem is, by chaining .then() functions, I cannot return from a block of code without jumping into the following .next().
One way to get around this is by throwing an error via throw new Error() when I get an error of either type 1 or 2, and handle the case in .catch(), but I am unsure how much of a good or bad practice this would be.
How can I make it so that I can handle the error in a .then() block and then stop execution? And would that be the best way to do it?
Is there a better way to handle these kind of situations in Express? Am I missing anything?
Thank you!
A solution would be to create an Error subclass (for instance, using error-subclass) and throw instances of those in case you want to signal a processing error.
Subsequently, in the .catch() handler you'd check if the error is an instance of that custom error class, and if so, return a 422 response. If not, pass it to next instead:
const ErrorSubclass = require('error-subclass').default;
class ProcessingError extends ErrorSubclass {}
Users.findOne({ email: userCreds.email })
.then(function(user){
if (user) {
throw new ProcessingError('Email and Password Required');
}
...
}).catch(function(err) {
if (err instanceof ProcessingError) {
return res.status(422).send({ error: err.message });
}
next(err);
});
Im creating an a API using NodeJS with the express framework and mongodb to store my data.
I have a register function which does 3 main things.
Creates the new.
Creates a token and associates it with a user.
Sends an email.
module.exports.register = function(req, res) {
var input = req.body;
var token = uuid.v4();
// Create a new user
var user = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Create User'});
});
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
});
}
// Send Email To User
var mailData = {
from: 'deleted#hotmail.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
});
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
}
Now even if there are errors whilst creating the user or the token or sending an email. It's always going to return that it successfully registered a user. How can i restructure / handle this better?
I also have the problem where if the email fails to send the user and token will have already been created, how do i solve this issue? Would i just create a resend activation function?
You mention that it's always going to return that it successfully registered a user. It will also send the email even if the token creation failed.
One (not very pretty) way to do it would be to continue with the next step inside the callback function of the previous step:
user.save(function(err) {
if(err) {
return res.json({success: false, errors: 'Failed To Create User'});
} else {
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) {
return res.json({success: false, errors: 'Failed To Save User Token'});
} else {
// Send Email To User
var mailData = {
from: 'deleted#example.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) {
return res.json({ success: false, errors: 'Failed To Send Email' });
} else {
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
}
});
}
});
}
}
});
As you can see, it looks like a callback-piramyd-of-doom very fast, but it only sends the success response when all the previous steps have completed.
You should also add the else case when the newToken is not created.
You should remove final return statement (from the end of your code) and return at the correct place inside each callback if there is no error.
If you send your response in the body of the function your callbacks will never get the chance to run. Therefore you must nest your callbacks and only call res.send if you are
returning early due to an error or
if everything is complete.
e.g.
// Create a new user
var user = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Create User'});
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
// Send Email To User
var mailData = {
from: 'deleted#hotmail.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
});
});
}
});
Asynchronous alternatives
Unfortunately, with node.js you should get used to and understand callbacks; even if you end up using something else most of the time. The way your code was structured was neater and logical but does not work in node.js because you have to wait for the callbacks to complete before you can return from your function.
However, callbacks are the default but one of the worst mechanisms for handling asynchronous logic. If you want to structure the code differently you have quite a few options. Here are just a couple:
Use promises instead of callbacks.
In your case your database library (mongoose? sequelize?) should have something built in that allows you to write your code like this:
user.save()
.then(function () {
// step 1
})
.then(funciton () {
// step 2
})
.done()
This style of programming is well worth learning and will make your code more readable than callbacks. callbacks vs promises
Use Koa instead of express.
Koa, is the next generation of express written by the same people. It uses generators instead of callbacks which means you can write code that looks more like this:
// this is just an example
var result = user.save();
if (result.error) return res.send({success : false, ...});
user.token = getNewToken();
user.update();
if (result.error) return res.send({success : false, ...});
return res.send({success : true, message : "Good news, no errors"});
Generators/(aka async functions) are the direction Javascript is moving in but there is a learning curve to start using. Behind the scenes there is something very complex going on to make asynchronous code appear exactly like synchronous code. Basically, the functions know how to pause execution until they are required again.
Start with callbacks
Like I say, callbacks are not that nice. However, you should get used to using them. They are the basic building blocks of node.js and it take a while to get comfortable with better alternatives. It's also important to get used to them because otherwise you won't appreciate why the alternatives are better.
Good luck and watch out for callbacks inside loops :)
I'm using sails 0.10.4 and stumbled with one pretty annoying bug. When user logs in I write his data into the req.session.user then in policies I can retrieve his data such as his role, password etc. But the req.session.user becomes undefined when I go out of the login action. Do you have any ideas how to handle this? Here's the code:
api/controllers/User.js :
module.exports = {
login: function (req, res) {
Users.findOneByEmail(req.param('email'))
.exec(function (err, user) {
if ((err) || (!user)) {
res.send({
error: 'User not found'
});
return;
}
if (!passwordHash.verify(req.param('password'), user.password)) {
res.send({
error: 'Incorrect passwpord'
});
return;
}
req.session.user = user;//I write user into the session
res.send({
user: user
});
});
}
}
api/policies/isLoggedIn.js
module.exports = function (req, res, next) {
if (req.headers.authentication) {
var credentials = JSON.parse(req.headers.authentication);
if(req.session.user.login === credentials.login)//User doesn't exist in session
return next();
}
}
In a testing environment , this issue can happen when testing with Supertest and not defining an agent
var agent = request.agent(app);
agent.post('/api/login',{email:'foo#bar.com',password:'foobar})
.end(function(err,res){...; done();});
It is the correct way to work with sessions, simply using request.post would not work as it would reinit the session variable as soon as the response is sent, even if we are chaining requests inside the same test.
Learnt it the hard way, so I hope it can help some lost developper.