In a backend API I have a login route which should perform the following sequence of actions:
Given an username and password, try to authenticate the user against an Active Directory. If authentication has failed reply with status 401. If success, continue.
Look for an user with the given username in the database. If not found reply with status 403, otherwise continue.
Find if the user document has some details like email, display name, etc (in case this is not the first time logging in). If yes reply with the user object, otherwise continue.
Get user details from the Active Directory and update the user object in the database. Reply with the updated object.
Code:
router.post('/login', (req, res, next) => {
// capture credentials
const username = req.body.username;
const password = req.body.password;
let user = null;
// authenticate
ad.authenticate(username, password)
.then((success) => {
if (!success) {
res.status(401).send(); // authentication failed
next();
}
return User.findOne({ username }).exec();
})
.then((found) => {
if (!found) {
res.status(403).send(); // unauthorized, no account in DB
next();
}
user = found;
if (user.displayName) {
res.status(201).json(user); // all good, return user details
next();
}
// fetch user details from the AD
return ad.getUserDetails(username, password);
})
.then((details) => {
// update user object with the response details and save
// ...
return user.save();
})
.then((update) => {
res.status(201).json(update); // all good, return user object
next();
})
.catch(err => next(err));
});
Now I had this running with callbacks but it was really nested. So I wanted to give Bluebird promises a try, but I have two problems:
Looks chaotic, any better way to chain the calls and handle responses?
Whenever I call next() to stop the request after replying, the execution continues to the other .then(). Although the client receives the correct response, in the server log I find that the execution have continued. For example, if there is no account in DB for a given user, the client receives the 403 response but in the server log I see an exception failed to read property displayName of null, because there was no user and it should have stopped in the next() after res.status(403).send();.
Best use if/else to make clear what branches will execute and which won't:
ad.authenticate(username, password).then((success) => {
if (!success) {
res.status(401).send(); // authentication failed
} else {
return User.findOne({ username }).exec().then(user => {
if (!user) {
res.status(403).send(); // unauthorized, no account in DB
} else if (user.displayName) {
res.status(201).json(user); // all good, return user details
} else {
// fetch user details from the AD
return ad.getUserDetails(username, password).then(details => {
// update user object with the response details and save
// ...
return user.save();
}).then(update => {
res.status(201).json(update); // all good, return user object
});
}
});
}
}).then(() => next(), err => next(err));
The nesting of then calls is quite necessary for conditional evaluation, you cannot chain them linearly and "break out" in the middle (other than by throwing exceptions, which is really ugly).
If you don't like all those then callbacks, you can use async/await syntax (possibly with a transpiler - or use Bluebird's Promise.coroutine to emulate it with generator syntax). Your whole code then becomes
router.post('/login', async (req, res, next) => {
try {
// authenticate
const success = await ad.authenticate(req.body.username, req.body.password);
if (!success) {
res.status(401).send(); // authentication failed
} else {
const user = await User.findOne({ username }).exec();
if (!user) {
res.status(403).send(); // unauthorized, no account in DB
} else if (user.displayName) {
res.status(201).json(user); // all good, return user details
} else {
// fetch user details from the AD
const details = await ad.getUserDetails(username, password);
// update user object with the response details and save
// ...
const update = await user.save();
res.status(201).json(update); // all good, return user object
}
}
next(); // let's hope this doesn't throw
} catch(err) {
next(err);
}
});
To answer your second point, you have to reject your promise after calling next() (or at least return something, otherwise the line after will be executed). Something like
next();
return Promise.reject()
and change your catch so it works if you do not have an error
.catch(err => {
if (err)
next(err)
});
To your second question first: there is no way to break/stop a promise chain, unless your callback throw err like
doAsync()
.then(()=>{
throw 'sth wrong'
})
.then(()=>{
// code here never runs
})
You can simply try below demos to verify the second callback still runs.
doAsync()
.then(()=>{
res.end('end')
})
.then(()=>{
// code here always runs
})
doAsync()
.then(()=>{
return;
})
.then(()=>{
// code here always runs
})
To your first question: to use the second parameter in then(), which means reject. And each time split the logic to two parts.
var p = new Promise(function(resolve, reject) {
return
ad.auth(username, password).then(()={
// check if 401 needed. If needed, return reject
if (dont needed 401 in your logic)
resolve(username)
else
reject({ msg: 'authentication has failed', status: 401 })
})
});
p
.then( (username)=>{
// this only runs when the previous resolves
return User.findOne({ username }).exec()
}, (data)=>{
// in fact in your case you dont even have to have the reject callback
return data
} )
.then( (found)=>{
return
new Promise(function(resolve, reject) {
if (found && /*your logic to determine it's not 403*/)
resolve(user)
else
reject({ msg: 'unauthorized, no account in DB', status: 403 })
})
} )
.then( (found)=>{
return
new Promise(function(resolve, reject) {
if (found && /*your logic to determine it's not 403*/)
resolve(user)
else
reject({ msg: 'unauthorized, no account in DB', status: 403 })
})
} )
.then( (user)=>{
return
new Promise(function(resolve, reject) {
if (/*your logic to determine it has the full info*/)
resolve(user)
else
return ad.getUserDetails(username, password)
})
} )
.then( (user)=>{
// all is good, do the good logic
}, (data)=>{
// something wrong, so here you can handle all the reject in one place
res.send(data)
} )
Related
I'm trying to write a POST endpoint using Node and Express that creates a new account for a user. In my function, I'm doing 2 database calls. The first one checks to make sure the email the user sent in the request does not exist in the database and the second one inserts the user if the first function comes back as false:
UserService.checkDupEmail(db, user.email)
.then((result) => {
if (result === true) {
const message = 'Email already exists.';
return res
.status(400)
.json('Error: ' + message)
};
});
const table = 'users';
BaseService.post(db, table, user)
.then((user) => {
Response.created(res, user);
})
.catch(next)
If the first call comes back as true meaning, the email already exists, I want to immediately return the response with an error code and a JSON message stating that the email exists. What's happening is that the response is being sent but the function continues to try to do the second database call which of course fails because the email already exists.
If I'm not mistaken, I believe this is how Express and middleware are supposed to work right? It's supposed to be a chain of validations and middleware that as long as they pass, it continues going down the chain until the last CRUD operation?
Middleware chains don't continue automatically in Express.js unless you call the next variable provided as shown below:
router.post('/createUser', function (req, res, next) { next(); }
As you create a promise the return only belongs to the promise not ending the execution of your Express function. As you are checking for the result === true, I would create an else for the execution of not finding the result in the database and add the new user there. I'd change your code to:
UserService.checkDupEmail(db, user.email)
.then((result) => {
if (result === true) {
const message = 'Email already exists.';
return res
.status(400)
.json('Error: ' + message)
} else {
const table = 'users';
BaseService.post(db, table, user)
.then((user) => {
Response.created(res, user);
})
.catch(next)
}
}).catch(next){
};
UserService.checkDupEmail(db, user.email) returns a promise. Callback added with then() will be invoked after the completion of the UserService.checkDupEmail.
(result) => {
if (result === true) {
const message = 'Email already exists.';
return res
.status(400)
.json('Error: ' + message)
};
}
above is your callback function.
the return you are talking about is the return of the callback function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
I'm in a situation where I have to use a catch block to execute some code but I don't want to consider it an error.
Basically, I want to update/create a user based on whether the user is already registered or not respectively. The admin sdk let me create a user, and if the user already exists it throws an error. So if I'm in the catch block I know that the user already exists and I want to update it.
function addClient(client) {
return new Promise((resolve, reject) => {
admin.auth().createUser({
uid: client.id,
email: client.email,
emailVerified: true,
password: client.password,
}).then(record => {
resolve(record);
return null;
}).catch(
// the user already exist, I update it
admin.auth().updateUser(client.id, {
email: client.email
}).then(record => {
resolve(record);
return null;
}).catch(
err => {
reject(err);
}
)
);
});
}
The problem is that when I call the function with an existing user, it is updated correctly but the HTTP response is an internal server error (I guess because it enters the catch block and it considers this as an error). The same is if I send a new user: it is created correctly but the HTTP response code is a 500.
There is a way to avoid this behaviour?
This is the main function that calls the previous one for each user received and it's responsible for sending the HTTP response:
exports.addClients = functions.https.onRequest((req, res) => {
// fetch recevied list from payload
var receivedClients = req.body.clients;
var promises = [];
receivedClients.forEach(client => {
promises.push(addClient(client));
})
Promise.all(promises)
.then(() => {
res.sendStatus(200);
return null;
})
.catch(err => {
res.status(500).send(err);
});
});
I guess that what I want to achieve is to have all the promises resolving.
You need to pass a callback to .catch, not a promise. Also avoid the Promise constructor antipattern!
function addClient(client) {
return admin.auth().createUser({
uid: client.id,
email: client.email,
emailVerified: true,
password: client.password,
}).catch(err => {
// ^^^^^^^^
// if (err.code != "UserExists") throw err;
return admin.auth().updateUser(client.id, {
email: client.email
})
});
}
I have an express route for handling password resets, and with that i have a route, where i first find the user, and have some error handling with that, but now i want aditional error handling inside a nested function, and I'm not sure what pattern to use
function triggerPasswordResetEmailSend(req, res, next) {
var email = req.body.email;
if (!email) return res.status(422).json({error: "Please provide an email."});
UserRepositoryClass.findUserByEmail(email).then(user =>{
if(!user) return res.status(422).json({message: "User not found"})
sendPasswordReset(user);
return res.status(200).json({user: user});
}).catch(err =>{
return res.status(500).json({error: err})
});
}
Inside this function i do some initial error handling. The issue now is that the sendPasswordReset function can also throw errors, but there are not caught by the .catch() function, so I'm looking for something to handle this function.
I have tried passing the req and res objects into the function, but that does not seem like a good solution. I could do some try catch or maybe return a promise. But i want to ensure, that i follow the same pattern and best practises as i have already tried to do.
Here is the code snippet from my mail function:
module.exports = (user) => {
const userResetToken = generatePasswordToken();
UserRepositoryClass.setPasswordResetToken(user.id, userResetToken);
const passwordResetUrl = PASSWORD_RESET_URL(user._id, userResetToken);
return sendMail(options(user.email, passwordResetUrl));
}
You can use promise instead of function.
module.exports.sendPasswordReset = user = new Promise((resolve, reject) => {
const userResetToken = generatePasswordToken();
UserRepositoryClass.setPasswordResetToken(user.id, userResetToken);
const passwordResetUrl = PASSWORD_RESET_URL(user._id, userResetToken);
sendMail(options(user.email, passwordResetUrl))
.then(response => {
resolve(response, null); // we can get result as (data,error) here error is null
})
.catch(err => {
reject(null, err); // here response is null
});
});
You can use sendPasswordReset Promise like this:
sendPasswordReset(user).then((res, err) => {
// here you can get your res as well as err
if (err) throw new Error("Error while sending an email");
console.log("response", res);
});
So i'm having a problem where i'm attempting to have an async call to my database to check if the username and password of an account exist before then checking to see if they are valid.
I'm running into the problem that the server skips my database call and then proceeds to the check before the information from the database is grabbed.
The code:
class HandlerGenerator {
login (req, res) {
let username = req.body.username;
let password = req.body.password;
let checkUsername = "";
let checkPassword = "";
var lData = {
username: req.body.username,
password: req.body.password
};
// For the given username fetch user from DB
var db = req.db;
var getUser = async () => {
var result = await (
User.findOne(lData , function(err, userLogin){
if(err){
console.log(err);
return
}
console.log(userLogin);
console.log("------====----");
console.log(userLogin.username);
checkUsername = userLogin.username;
checkPassword = userLogin.password;
console.log("------====----");
console.log(checkUsername);
console.log(checkPassword);
})
);
console.log("--00--");
console.log('result' + result)
return result;
};
console.log("does this work?");
if (username && password) {
console.log("------==2==----");
console.log(checkUsername)
console.log(checkPassword)
if (username === checkUsername && password === checkPassword) {
let token = jwt.sign({username: username},
config.secret,
{ expiresIn: '24h' // expires in 24 hours
}
);
// return the JWT token for the future API calls
res.json({
success: true,
message: 'Authentication successful!',
token: token
});
} else {
res.send(403).json({
success: false,
message: 'Incorrect username or password'
});
}
} else {
res.send(400).json({
success: false,
message: 'Authentication failed! Please check the request'
});
}
}
index (req, res) {
res.json({
success: true,
message: 'Index page'
});
}
}
When I run this the "Does this work?" Comment always runs first and i'm confused on what I am messing up
You have two main issues here.
First, async returns an AsyncFunction, which returns a Promise when called. This means that at this point, nothing in getUser has been executed. Not only you need to actually call getUser to start running what is inside it, but you also need to await the result, otherwise you have absolutely no guarantee that the execution will be over.
Instead of going through this trouble, making your login function async seems a more reasonable choice, as you are trying to do asynchronous operations inside it. You could then remove the getUser function and only keep the var result = await User.findOne(....) part.
The other issue, as multiple persons said in the comments, is that you need to await on a Promise.
Looking at your function call, it looks like your findOne functions uses a callback rather than a promise. Do check the documentation, some libraries support both and might indeed return a promise if you do not pass in any callback.
If it does, you should not pass any callback. The result of the "awaited" call should be userLogin.
Otherwise, wrapping the function so that it returns a promise is straightforward.
The basic pattern is as follows
// this is the function that we want to wrap
function funcToWrap(someParam, callback) {
// does stuff
callback(null, "some result");
}
// this is the wrapper
function wrappedFunction(someParam) {
return new Promise((resolve, reject) => {
funcToWrap(someParam, (err, res) => {
if (err === null) {
reject(err);
} else {
resolve(res);
}
});
});
This transforms your callback-based function into a promise-based one. You can then of course await on wrappedFunc and use it as any other promise.
This is such a common pattern that a lot of libraries already implement this functionality. For example, the Promise library Bluebird provides a promisify function which does exactly this.
http://bluebirdjs.com/docs/api/promise.promisify.html
So instead of writing all of this yourself, you could simply write
var wrappedFunction = Promise.promisify(funcToWrap);
In my node js app, I'm trying to create a registration route that takes a few asynchronous steps. In an early step, there's a condition where I want to stop and just return a message, but I'm getting confused about mixing conditions into the promises.
My specific question is in comments in the following psuedo-code:
router.post('/register', function(req, res, next) {
var identity = req.body.identity;
var password = req.body.password;
// can't register without a reservation
hasReservation(identity).then(function(hasReservation) {
if (hasReservation) { return register(identity, password); }
else {
// stuck here: I want to say:
res.json({ registered: false, reason: "missing reservation" });
// and then I want to stop further promises
// but this isn't an error, I don't want to throw an error here
}
}).then(function(user) {
// in my app, registering an existing user is okay
// I just want to reset a few things about the user and save
// but I don't want to be here if there's no reservation
if (user) {
user.foo = 'bar';
return user.save().then(function() { return user; });
} else {
return register(identity, password);
}
}).then(function(user) {
// I want to do more asynch stuff here
}).then(function(result) {
res.json(result);
}).catch(function(error) {
res.status(500).json(error);
});
});
How can I conditionally "bail out" after that first promise completes, without throwing an error?
If register(identity, password) returns a promise, you can reorganise your code as follows:
router.post('/register', function(req, res, next) {
var identity = req.body.identity;
var password = req.body.password;
// can't register without a reservation
hasReservation(identity)
.then(function(hasReservation) {
if (hasReservation) {
return register(identity, password)
.then(function(user) {
// in my app, registering an existing user is okay
// I just want to reset a few things about the user and save
// but I don't want to be here if there's no reservation
if (user) {
user.foo = 'bar';
return user.save().then(function() { return user; });
} else {
return register(identity, password);
}
})
.then(function(user) {
// I want to do more asynch stuff here
})
.then(function(result) {
res.json(result);
})
} else {
// stuck here: I want to say:
res.json({ registered: false, reason: "missing reservation" });
// and then I want to stop further promises
// but this isn't an error, I don't want to throw an error here
}
})
.catch(function(error) {
res.status(500).json(error);
});
});
Otherwise, if register(..) just returns a value, wrap the FIRST instance of the register(...) in a Promise.resolve
return Promise.resolve(register(identity, password))