I'm building RESTful api with adonisjs. I face this problem in jwt login module. Look at the code below:
async login({request, auth, response}) {
let {email, password} = request.all()
try {
if (await auth.attempt(email, password)) {
let user = await User.findBy('email', email)
let token = await auth.generate(user)
user.password = undefined
Object.assign(user, token)
//------------------------------------------
const assignedToken = await Token.create({
user_id: user.id,
token,
})
// -------- i'd like to catch exception here...
return response.json({
success: true,
user
})
}
} catch(e) {
return response.json({
success: false,
message: 'login_failed'
})
}
}
I'd like to catch possible exception while persisting jwt token to database. I am rather new to adonis. I checked their doc but cannot find exact return type. Do they throw an exception? Or just return null/false? I have no idea. Do you have any?
Do they throw an exception?
Yes
An exception will appear if there is a problem during creation. You can create a new try/catch inside you try/catch. Like:
async login({request, auth, response}) {
...
try {
...
try { // New try/catch
const assignedToken = await Token.create({
user_id: user.id,
token,
})
} catch (error) {
// Your exception
return ...
}
return response.json({
success: true,
user
})
}catch (e) {
...
}
}
It's the simplest solution. I would do it this way because there can be different types of errors.
Related
Node.js CODE
exports.user = async (req, res) => {
try {
const { wallet } = req.body;
if (!wallet) {
res.status(400).json({ error: "Not logged in" });
return;
} else {
user = User.findone(wallet);
// if user is not found then create a new user and mark as loggged In
if (!user) {
User.create({
user: wallet,
});
}
// if user found then create a session token and mark as logged
in
res.send({
user: wallet,
});
}
} catch (error) {
console.log(`ERROR::`, error);
}
};
REACTJs CODE
// post call/update
const axiosCall = async () => {
// core login will give a unique username by fulling a transcation
// core.login i dont have any control
const userAccount = await core.login();
try {
const res = await Axios.post(`${API}/user`, userAccount, dataToken);
setData({
...data,
error: "",
success: res.data.message,
});
} catch (error) {
setData({
...data,
error: error.response.data.error,
});
}
};
Now here the problem occurs when some one could modify userAccount in the front-end or someone could send a body with wallet: anything to my route localhost:3000/api/user
There is no option for me to check if some actually used core.login(); to get the wallet address.
So is there any solution?
I was thinking to allow only my server IP or localhost to hit the route localhost:3000/api/user and is that even possible?
Also there is another issue anyone could modify userAccount in front-end.
having a real problem with getting this code to work. I have everything set up working great with Appwrite. I'm getting a response back from the server, but in my promise.then it finishes the other code and returns undefined from the login function. so then the post async function is sending a blank array in the try block. I've tried setting this code up every way I can think of but it never works. Sorry, i'm still trying to wrap my head around the promises and async js.
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
const { email, password } = await request.json();
function login() {
// add logic to authenticate user with external service here
const sdk = new Appwrite();
sdk
.setEndpoint('https://') // API Endpoint
.setProject('') // project ID
;
let promise = sdk.account.createSession(email, password);
let userLogin;
promise.then(function (response) {
console.log(response); // Success
userLogin = response.providerUid;
console.log(userLogin);
}, function (error) {
console.log(error); // Failure
});
console.log('login.json.js', { email, password: !!password });
console.log(userLogin);
return userLogin;
}
try {
const user = login();
locals.user = user;
return {
status: 200
};
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return {
status: 500,
body: message
};
}
}
You're returning userLogin in login before it's even populated in the asynchronous promise.then chain
Also, since you're currently handling the rejection in your promise.then(onFulfilled, onRejected) that would mean any rejection is handled inside login and your try/catch (once written correctly) would never have an error to catch since login handled it already
One more potential issue - if const { email, password } = await request.json(); rejects, then the error will be thrown to whatever called post - is that what you want? or did that also need to be handled inside post?
Anyway here's how to fix your code:
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
// note: if this throws then the error will be handled by whatever calls `post`
const { email, password } = await request.json();
function login() {
// add logic to authenticate user with external service here
const sdk = new Appwrite();
sdk
.setEndpoint('https://') // API Endpoint
.setProject('') // project ID
;
const promise = sdk.account.createSession(email, password);
return promise.then(function(response) {
let userLogin = response.providerUid;
return userLogin;
// or without redundant `userLogin` variable
// return response.providerUid;
});
}
try {
const user = await login();
locals.user = user;
return { status: 200 };
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return { status: 500, body: message };
}
}
Or, making login async
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
// note: if this throws then the error will be handled by whatever calls `post`
const { email, password } = await request.json();
async function login() {
// add logic to authenticate user with external service here
const sdk = new Appwrite();
sdk
.setEndpoint('https://') // API Endpoint
.setProject('') // project ID
;
let response = await sdk.account.createSession(email, password);
let userLogin = response.providerUid;
return userLogin;
}
try {
const user = await login();
locals.user = user;
return {
status: 200
};
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return {
status: 500,
body: message
};
}
}
Or, removing inner Login function completely
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
// note: if this throws then the error will be handled by whatever calls `post`
const { email, password } = await request.json();
try {
const sdk = new Appwrite();
sdk.setEndpoint('https://') // API Endpoint
.setProject(''); // project ID
const response = await sdk.account.createSession(email, password);
console.log(response); // Success
locals.user = response.providerUid;
return { status: 200 };
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return { status: 500, body: message };
}
}
For some reason, only the adminConfirmSignup gives the user pool does not exist error. The CognitoUser doesn't give that error.
Please refer to the code below:
let cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData)
var cognitoAdmin = new AWS.CognitoIdentityServiceProvider({ region: process.env.COGNITO_POOL_REGION! });
await cognitoAdmin.adminConfirmSignUp(confirmParams, async(err, data) => { //Only this gives the user pool does not exist error
if (err) {
console.log(`This is the admin user confirm error ---> ${err}`)
} else {
console.log(`Entered else`);
await cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: async(result) => {
cognitoUser.changePassword(resetDetails.currentPassword, resetDetails.newPassword, (err, data) => {
if (err) {
reject(err);
} else {
console.log(`This is the success response of cognito change password -----> ${JSON.stringify(data)}`);
resolve(data);
}
})
},
onFailure: (error) => {
console.log(`This is the onFailure error ----> ${JSON.stringify(error)}`);
reject(error);
}
})
}
})
The password reset works if I use the CognitoUser methods (when I manually confirm the user and use only the cognitoUser methods to authenticate and reset the password).
Your param UserPoolId in SDK calls is incorrect. Won't be able to see that with only what you have posted.
Need another await in front of calling that function
await cognitoUser.changePassword(...)
like this
So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {
I'm scratching my head trying to figure out the best way to handle errors from specific user actions. I'm using Express as my web server and even though it works, for the most part, I am getting not-so-useful, generic error messages. For instance, in the code below, I get the Request failed with status code 400 error message on the client side for the first two conditions/exceptions in the try block.
How do I approach this in the following example?
Express Server-side Controller
async function voteInPoll (req, res) {
const { category, pollId } = req.params;
const { name, choiceId, voterId } = req.body;
try {
const poll = await Poll.findById(pollId);
// Check if user has already voted in poll
const hasVoted = poll.votedBy.some(voter => voter.equals(voterId));
if (!voterId) { // Check if user is authenticated
res
.sendStatus(400)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res
.sendStatus(400)
.json({ message: 'Sorry, you can only vote once' });
} else {
const choice = await poll.choices.id(choiceId);
const votedChoice = { name, votes: choice.votes + 1 };
await choice.set(votedChoice);
await poll.votedBy.push(voterId);
poll.save();
res
.sendStatus(200)
.json({
message: 'Thank you for voting. Find other polls at: ',
poll,
});
}
} catch (error) {
throw new Error(error);
}
}
React/Redux Action
export const voteInPoll = (category, pollId, votedItem, voterId) => async dispatch => {
try {
const response = await axios.post(
`http://localhost:3050/polls/${category}/${pollId}/vote`,
{
...votedItem,
voterId,
}
);
dispatch({ type: store.polls.VOTE_SUCCESS, payload: response.data.poll });
} catch (error) {
console.log(error);
dispatch({ type: store.polls.VOTE_FAILURE, payload: error.message });
}
};
Edit
What I find rather bizarre is I get the expected error response sent, as seen below under the Network tab of Chrome's Developer tools.
You should not be using res.sendStatus(statusCode) because of the following as defined in the docs here:
Sets the response HTTP status code to statusCode and send its string representation as the response body.
The key thing about the above is:
and send its string representation as the response body.
So doing: res.sendStatus(400).json({ message: 'Oops 400!'}) will not give you a JSON response which is what you're expecting, but simply display:
Bad Request
Which is the string representation of the 400 HTTP status code: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_errors
What you need to do is replace all of your res.sendStatus(..).json(..) with res.status(...).json(...) like so:
if (!voterId) { // Check if user is authenticated
res
.status(400)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res
.status(400)
.json({ message: 'Sorry, you can only vote once' });
} else {
// ...
}
and so on.