I'm trying to learn node.js
I've got a working function and trying to handle an exeption like this:
Client.Session.create(device, storage, username, password)
.then(function(session) {
session.getAccount()
.then(function(account) {
console.log(account.params)
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(account.params));
return session
})
}).catch(Exceptions.AuthenticationError, function(err) {
console.log(err)
})
but it isn't working I'm still getting this in case of invalid login:
Unhandled rejection AuthenticationError: The username you entered doesn't appear to belong to an account. Please check your username and try again.
Try
Client.Session.create(device, storage, username, password)
.then(function(session) {
return session.getAccount() <-- NOTICE THE RETURN STATEMENT!!
.then(function(account) {
console.log(account.params)
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(account.params));
return session
})
}).catch(Exceptions.AuthenticationError, function(err) {
console.log(err)
})
Without the return, the .then handler of the promise returned by Client.Session.Create(...) will return a resolved promise (this is its default behaviour).
Promise rejections aren't any kind of exceiptions, so they aren't automatically rethrown as it would be if you were added, for example, something like this:
session.getAccount(...).then(...).catch(function(){throw "FooBar"});
Related
I have a working apollo graphql express server. The only problem is express is complaining that I don't have a catch block on a promise I'm using to verify a jwt token:
(node:96074) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
I can add a catch block to the promise but it then returns pending instead of rejected when the token is invalidated. Which causes the authentication flow to break as my graphql resolvers rely on that rejection to block access to the db.
Fwiw this is how auth0, who I'm using for auth, recommends setting it up. They just don't mention the UnhandledPromiseRejectionWarning.
The code looks like this:
//server def
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
if (req.headers.authorization) {
const token = req.headers.authorization.split(' ')[1];
//THE PROMISE IN QUESTION
const authUserObj = new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
reject(err);
}
if (decoded) {
resolve(decoded);
}
});
});
return {
authUserObj
};
}
},
introspection: true,
playground: true
});
//a graphql resolver that gets the rejection via authUserObj and catches the error
addUser: async (parent, args, {authUserObj}) => {
try {
const AuthUser = await authUserObj;
const response = await User.create(args);
return response;
} catch(err) {
throw new AuthenticationError('You must be logged in to do this');
}
}
That all works... except for that nagging node error I wish to vanquish! So I add a catch block to the promise:
const authUserObj = new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
console.log("-------rejected-------", err.message)
reject(err);
}
if (decoded) {
console.log("-------decoded-------")
resolve(decoded);
}
});
}).catch( err => { return err.message});
And now instead of authUserObj returning rejected it's pending and anyone will be able to add a user, which kind of defeats the purpose of auth.
If anyone knows how to catch that error while still rejecting it I'm all ears. Thanks.
The problem is less about the unhandled promise rejection and more about the unhandled promise in general. You try to put a promise inside the context object and then await the promise in the addUser resolver only. In other resolvers, the promise might not be used at all, and when the jwt verification fails for those the rejection will go unhandled. (Also if the resolvers are executed asynchronously, the promise might be rejected before they can handle it).
Instead, the whole context initialisation should be done asynchronously, returning a promise for the context object with the user details. This means that the request will fail before even starting to execute a query:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
if (req.headers.authorization) {
const token = req.headers.authorization.split(' ')[1];
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
}).then(authUser => {
if (authUser) return { authUser };
// else return {};
});
// .catch(err => { … }) - you may chose to ignore verification failure,
// and still return a context object (without an `authUser`)
}
// else return {}; - when not sending the header, no token will be checked at all
},
introspection: true,
playground: true
});
// a graphql resolver that checks for the authUserObj
addUser: async (parent, args, {authUserObj}) => {
if (!authUserObj) { // you might also want to check specific claims of the jwt
throw new AuthenticationError('You must be logged in to do this');
}
const response = await User.create(args);
return response;
}
Just like with try/catch, a .catch() will change the promise chain from rejected to resolved if you just return a normal value from the .catch() handler (or return nothing). When you return a "normal" value, the rejection is considered "handled" and the promise chain becomes resolved with that new value. That's how you handle errors and continue normal processing.
To keep the promise chain rejected, you have to either throw or return a rejected promise. That will keep the promise chain as rejected.
So, if you want authUserObj to keep the promise rejected, then change this:
}).catch( err => { return err.message});
to this:
}).catch( err => { return Promise.reject(err.message)});
or something similar that either throws an error or returns a rejected promise.
I'm trying to write a test in jest but keep getting UnhandledPromiseRejectionWarning when I try to use mockRejectedValue
The code looks like this:
it('Should set error message when call fails', async () => {
const context = mockActionsContext();
const user = {
username: 'alice',
password: 'password'
};
const getError = new Error('network error');
(AuthService.login as jest.Mock) = jest.fn().mockRejectedValue(getError);
await actions[ActionTypes.USER_LOGIN](context, user);
// Check is the commits are called
expect((context.commit as any).mock.calls).toEqual([
[MutationTypes.USER_LOGIN],
[MutationTypes.USER_LOGIN_ERROR, 'Oops, something went wrong. Try again later!']
]);
// Login service is called with user login
expect(AuthService.login as jest.Mock).toHaveBeenCalledWith(user);
});
The AuthService.login returns an axios.post which I try to overwrite with a mock.
actions[ActionTypes.USER_LOGIN](context, user) calls the Authservice.login
The test is passing but I don't want any unhandled promise rejection. Anybody an idea how to fix it?
Edit
#goodmorningasif thanks for your reply.
I've been looking at it too long I thing :)
The action looks as following:
[ActionTypes.USER_LOGIN]: ({ commit }: Context, payload: User) => {
return new Promise((resolve, reject) => {
commit(MutationTypes.USER_LOGIN);
AuthService.login(payload)
.then((token) => {
commit(MutationTypes.USER_LOGIN_SUCCESS, token);
localStorage.setItem('user-token', token);
client.defaults.headers.common.Authorization = `Bearer ${token}`;
resolve(token);
})
.catch((error) => {
let errorMessage = 'Oops, something went wrong. Try again later!';
if (error?.response?.status === 401) {
errorMessage = 'Unknown username and password combination!';
}
localStorage.removeItem('user-token');
commit(MutationTypes.USER_LOGIN_ERROR, errorMessage);
reject(error);
});
});
},
SOLUTION
In my case the action is returning a promise witch would get rejected. In the test, I'm calling the action directly and not catching the rejection.
await actions[ActionTypes.USER_LOGIN](context, user).catch(() => null);
This fixed it.
Can we see the actions and reducer code? It's possible that there's an error in your error :)
You're testing that the login function is called and the action returns the error message you set but you're making an assumption about what causes the error. Maybe it's not because of the mockRejectedValue/'network error'.
I'd suggest including the actual error message in the action payload as well as your error message: one is for developers and debugging and one is for the user to know what to do next.
I also found this helpful on understanding UnhandledPromiseRejectionWarning: https://thecodebarbarian.com/unhandled-promise-rejections-in-node.js.html
Good instinct to figure out the issue and not be content with the test passing, by the way!
So when running a query using Objection.js, the query will return data based on success or failure of said query and this data is passed to the then() block as a 0 or 1. Meaning to error handle, I'm having to check falsey values rather than send a response in the catch block. Am I doing something wrong?
const editIndustry = async (req, res, next) => {
const industry = await Industry.query().findById(req.params.industryId);
if (!industry) {
return res.status(404).json({
error: 'NotFoundError',
message: `industry not found`,
});
}
await industry
.$query()
.patch({ ...req.body })
.then(result => console.log(result, 'then() block'))
// never runs
.catch(err => {
console.log(err);
next(err);
});
};
App is listening on port 3000.
1 then() block ran
Your code is working as expected. The reason it's not going into the catch block is because there isn't an error. patch does not return the row. It returns the number of rows changed (see docs).
The function I think you're really looking for is patchAndFetchById (see docs). If you're concerned about generating a 404 error, you can append throwIfNotFound. Obviously, this will throw if it's not found in the database, which will let you catch. You can catch an instance of this error so you can send a proper 404 response. Otherwise, you want to return a 500. You'd need to require NotFoundError from objection.
const { NotFoundError } = require('objection');
const Industry = require('<myIndustryModelLocation>');
const editIndustry = (req, res) => {
try {
return Industry
.query()
.patchAndFetchById(req.params.industryId, { ...req.body })
.throwIfNotFound();
} catch (err) {
if(err instanceof NotFoundError) {
return res.status(404).json({
error: 'NotFoundError',
message: `industry not found`,
});
}
return res.status(500);
}
};
I am trying to add user authentication to my site. The register route on my page works perfectly but I receive an Unhandled promise rejection warning when I try to send a request to the login route.
I've tried adding .catch(err => console.log(err)); and
.catch(console.log("Something's gone wrong.")); to the end of both .findOne().then() and .compare().then(), but that didn't help.
router.post("/login", (req, res) => {
const email = req.body.email;
const password = req.body.passowrd;
User.findOne({ email }).then(user => {
if (!user) {
return res.status(404).json({ email: "User not found" });
}
bcrypt.compare(password, user.passowrd).then(isMatch => {
if (isMatch) {
res.json({ msg: "Success" });
} else {
return res.status(400).json({ password: "Password incorrect" });
}
});
});
});
The code is supposed to simply send back a message that the passwords match, so I can later generate a token. I get this error:
(node:18152) UnhandledPromiseRejectionWarning: Error: Illegal arguments: undefined, undefined
at _async (/home/jok/code/node_modules/bcryptjs/dist/bcrypt.js:286:46)
at /home/jok/code/node_modules/bcryptjs/dist/bcrypt.js:307:17
at new Promise (<anonymous>)
at Object.bcrypt.compare (/home/jok/code/node_modules/bcryptjs/dist/bcrypt.js:306:20)
at User.findOne.then.user (/home/jok/code/routes/api/users.js:64:12)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
(node:18152) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:18152) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
The UnhandledPromiseRejectionWarning occurs because you're not handling the Promise rejection, meaning that you're missing a .catch handler.
bcrypt.compare(password, user.passowrd).then(isMatch => {
if (isMatch) {
res.json({ msg: "Success" });
} else {
return res.status(400).json({ password: "Password incorrect" });
}
})
.catch(err => {
res.status(500).send('Internal server error');
});
In this particular case, it seems that password & user.passowrd are undefined. The latter probable because of a typo: passowrd => password.
So it's recommended to check that the arguments sent to your route are valid.
router.post("/login", (req, res) => {
const email = req.body.email;
const password = req.body.passowrd;
if(!email || !password)
return res.status(400).send('email & password are required');
/* ... */
});
Since you're missing the .catch handler also on the .findOne Promise, it's always better to chain Promises instead of nesting them as you're doing. So here's the complete code:
router.post("/login", (req, res) => {
const email = req.body.email;
const password = req.body.passowrd;
if (!email || !password)
return res.status(400).send('email & password are required');
User.findOne({ email })
.then(user => {
if (!user) {
return res.status(404)
.json({ message: "User not found" });
}
return bcrypt.compare(password, user.passowrd);
})
.then(isMatch => {
if (typeof isMatch !== 'boolean')
return; // result from `res.status(404)...`
if (isMatch)
return res.json({ message: "Success" });
return res.status(400)
.json({ message: "Password incorrect" });
})
.catch(err => {
res.status(500).json({ message: 'Internal server error' });
});
});
I've tried adding .catch(err => console.log(err)); and
.catch(console.log("Something's gone wrong.")); to the end of both
.findOne().then() and .compare().then(), but that didn't help.
Either you didn't attach the handler correctly, or the warning was triggered in another code. But since the provided code does not have the .catch you mention, I can't confirm. Anyways, the above snippet won't trigger an UnhandledPromiseRejectionWarning
bcrypt.compare(myPlaintextPassword, hash, function(err, res) {
if (err) {
console.log(err);
}
// Use your response
});
That is not necessary to use promises in simple logic.
I´m building an express router that uses mongoose to access the database. My current problem relies on this piece of code:
app.use("/authreset", (req, res) => {
authenticator
.resetPassword(
req.body.username,
req.body.password,
req.body.token,
req.body.type
)
.then((response, error) => {
if (error) throw new Error(error);
console.log('*****************');
console.log(response);
if (!response) {
res.sendStatus(401);
return;
}
})
.catch(error => {
console.log('*****************');
console.log(error);
if (error) throw new Error(error);
});
});
resetPassword uses the following mongoose call:
return UserModel
.findByIdAndUpdate(user.id, data, { new: true })
.exec();
For some reason, my route is being called and the response is fine (checked on console.log(response) inside promise).
My problem is that the response is never sent back to the client, that times out the fetch call.
Why is my promise not returning data?
Uh, you log the response, but you never send it (or at least respond with a status code)?
Your code should look more like
app.use("/authreset", (req, res) => {
authenticator.resetPassword(
req.body.username,
req.body.password,
req.body.token,
req.body.type
).then(response => {
console.log(response);
if (!response) {
return res.sendStatus(401);
} else {
return res.sendStatus(200); // <<<
}
}, error => {
console.log(error);
return res.sendStatus(500);
});
});
Notice that the then callback never gets called with more than one argument, so that error you were checking for cannot happen. In the catch handler, you should never rethrow an error if it doesn't get handled further down. Also I changed .then(…).catch(…) to the more fitting .then(…, …).