So, I've been scratching my head for over a day about this problem. My app supports signup, signin, creating a post. What I have noticed is that the user who doesn't have a post can login successfully any time. But those who have posts cannot login after.
Here's how it looks.
case 1
I register with Jim -> jim is stored in the db -> login with jim(success) -> logout(works fine) and again login(works fine)
case 2
I register with Jim -> jim is stored in the db -> login with jim(success) -> makes a post -> post shows in his feed(success) -> logout(works fine) and again try to login(failed)
The error is->
POST http://localhost:3000/api/v1/users/login 402 (Payment Required)
It's not even entering the login controller when a user who has post(s) try to login. It's working successfully for the user who has no post(s). It's confusing me.
Here's the code:
registerUser: (req, res) => {
console.log("inside register user")
const { username, email, password } = req.body
User.create(req.body, (err, createdUser) => {
if (err) {
return res.status(500).json({ error: "Server error occurred" })
} else if (!username || !email || !password) {
return res.status(400).json({ message: "Username, email and password are must" })
} else if (!validator.isEmail(email)) {
return res.status(400).json({ message: "Invaid email" })
} else if (password.length < 6) {
return res.status(400).json({ message: "Password should be of at least 6 characters" })
}
else {
return res.status(200).json({ user: createdUser })
}
})
},
loginUser: async (req, res, next) => {
console.log("inside login controller")
const { email, password } = req.body
if (!email || !password) {
return res.status(400).json({ message: "Email and password are must" })
}
await User.findOne({ email }, (err, user) => {
if (err) {
return next(err)
} else if (!validator.isEmail(email)) {
return res.status(400).json({ message: "Invalid email" })
} else if (!user) {
return res.status(402).json({ error: "User not found" })
} else if (!user.confirmPassword(password)) {
return res.status(402).json({ error: "incorrect password" })
}
// generate token here
const token = auth.signToken({ userId: user._id })
// const token = auth.signToken({ email })
res.status(200).json({ user, token })
// next()
})
}
newPost controller
newPost: (req, res) => {
const data = {
title: req.body.title,
content: req.body.content,
user: req.user.userId
}
Post.create(data, (err, newPost) => {
if (err) {
return res.status(500).json({ error: err })
} else if (!newPost) {
return res.status(400).json({ message: "No Post found" })
} else if (newPost) {
User.findById(req.user.userId, (err, user) => {
user.posts.push(newPost._id) //pushing posts documnet objectid to the post array of the user document
user
.save()
.then(() => {
return res.json(200).json({ user })
})
.catch(err => {
return res.status(500).json({ error: err })
})
})
}
})
}
I also checked in postman by going to the route /users/login and entering the email and password, but it's saying incorrect password. It's returning the return from
else if (!user.confirmPassword(password)) {
return res.status(402).json({ error: "incorrect password" })
}
If I change the above to ({ error: err}), it's returning in the response:
{
"error": null
}
Update
User.findById(req.user.userId, (err, user) => {
console.log("user before save", user)
user.gratitudes.push(newGratitude._id) //pushing posts documnet objectid to the post array of the user document
user
.save()
.then(() => {
// return res.json(200).json({ user })
console.log("user after saving", user)
})
.catch(err => {
return res.status(500).json({ error: err })
})
)
So, I did this and consoles
user before saving => { posts: [],
_id: 5e78c76381327761329b8dc6,
username: 'king123',
email: 'king123#gmail.com',
password: '$2b$10$CnWib5AMxw1qv5RnBdEisOXUq9X3lqqOMH3HWO3DlWf.iB2Ke8GLS',
createdAt: 2020-03-23T14:27:47.820Z,
updatedAt: 2020-03-23T14:27:47.820Z,
__v: 0 }
user after saving => { posts: [ 5e78c77a81327761329b8dc7 ],
_id: 5e78c76381327761329b8dc6,
username: 'king123',
email: 'king123#gmail.com',
password: '$2b$10$ro4FIO40.3Nwe52RFq/leepH906HvIHYW5A3XrTGfNXcUSIfsx0Bq',
createdAt: 2020-03-23T14:27:47.820Z,
updatedAt: 2020-03-23T14:28:10.290Z,
__v: 1 }
It looks like the hashed password has changed.
It sounds like your blog post method is effecting your user record somehow.
Try making a new user, logging in, checking the user record looks normal.
Then make a post as that user and rather than checking for the post, check the user record for differences before you made the post. I'd expect that something from the post update is changing something in the user record by mistake.
Related
The bounty expires in 7 days. Answers to this question are eligible for a +100 reputation bounty.
AG_HIHI is looking for an answer from a reputable source.
I have successfully implemented authentication using gmail account in my app.
The problem is when the user signs-in again, the browser automatically picks the previous account which breaks the flow as I will explain below. p
Based on my research, adding prompt: "select_account" here should have solved the issue. But, it had no effect.
router.get(
"/auth/google",
passport.authenticate("google", {
scope: [
"email",
"profile",
],
prompt: "select_account",
})
);
Here's how automatically picking the user account that was previously used to sign-in breaks the sign-in if the user tries to sign-in again.
This is how the sign-in works:
STEP 1:
This endpoint is called from the frontend:
router.get(
"/auth/google",
passport.authenticate("google", {
scope: [
"email",
"profile",
],
prompt: "select_account",
})
);
STEP 2:
After, the user picks an account, he is redirected to this callback endpoint:
router.get(
"/auth/google/callback",
passport.authenticate("google", {
failureRedirect: baseFrontendUrl,
session: false,
}),
function (req, res) {
User.findOne({ _id: req.user._id })
.then((user) => {
const payload = {
id: req.user._id,
};
console.log("🚀 ~ file: users.js:178 ~ .then ~ payload", payload);
jwt.sign(
payload,
keys.SecretKey,
{ expiresIn: 3600 * 24 * 356 },
(error, token) => {
if (error) {
res.status(400).json({
message: "Error logging in user.",
});
} else {
const redirect_url = `${baseFrontendUrl}/OAuthRedirecting?token=${token}`;
res.redirect(redirect_url);
}
}
);
})
.catch((error) => {
res.status(500).json({
message: "An error occured authenticating user using google.",
});
});
}
);
The problem is that if the user does not pick an account, he does not get redirected to that endpoint. So the second sign-in fails.
A solution to this could be to force the user to pick an account every time he signs-in but I couldn't find a way to do this.
This is how the google passport strategy is implemented:
passport.use(
new GoogleStrategy(googe_passport_config, function (
request,
accessToken,
refreshToken,
google_profile,
done
) {
let name = !!google_profile._json.given_name
? google_profile.given_name
: "Name";
let surname = !!google_profile._json.family_name
? google_profile.family_name
: "Surname";
let email = !!google_profile._json.email ? google_profile.email : "";
User.findOne({ email: google_profile._json.email })
.then((user) => {
if (!!user) {
return done(null, user);
} else {
userServices
.registerUserThroughGoogleAuth(
name,
surname,
email,
google_profile.id
)
.then((created_user) => {
if (!!created_user) {
return done(null, created_user);
}
})
.catch((error) => {
const error_to_be_returned = new Error("Error creating user");
return done(error_to_be_returned, null);
});
}
})
.catch((error) => {
const error_to_be_returned = new Error("Error finding user");
return done(error_to_be_returned, null);
});
})
);
I added some console logs there and nothing gets logged the second time the user tries to sign-in. So it's not even getting called.
When I add the following line res.status(201).json({ email }); I get the error message UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. What can I do to fix this problem.
Below is a snippet of my code
module.exports.signup_post = (req, res ) => {
const { firstname, lastname, email, password } = req.body;
handleErrorSignup(firstname.trim(), lastname.trim(), email.trim(), password.trim())
.then( async (errors) => {
if(errors.firstname === '' && errors.lastname === '' && errors.email === '' && errors.password === '') {
const hash = bcrypt.hashSync('password', 10);
try {
await db.none('INSERT INTO users(firstname, lastname, email, password) VALUES($1, $2, $3, $4)', [firstname, lastname, email, hash]);
const token = createToken(email);
res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 });
res.status(201).json({ email });
}
catch(err) {
res.status(400).send('Error, user not created');
}
res.redirect('/');
}
else {
res.status(400).json({ errors });
}
});
}
The problem here is that you are sending response from try catch already, so you cannot redirect if you have sent the response already from the earlier parts of your code.
You need to remove this line from your code, or redirect only if response is not already sent in try and catch blocks.
try {
...
res.status(201).json({ email });
} catch (err) {
res.status(400).send('Error, user not created');
}
// Remove below code
res.redirect('/');
If you are looking to redirect to your home screen after signup, you need to handle the same in frontend based on the status code or response received for signup from backend.
I have the code below. Its a standard blog type of setup with users which have posts and comments. Comments are the child of both users and post . Posts belong just to users. Im having a problem posting to comments table. IM not getting any errors when using the insert function , however, when I post a comment to the database nothing gets saved to the comments table . If i do a request to retrieve the comments table , the table still shows empty. What am i doing wrong here .
server.post("/users/:id/posts/:id2/comments", async (req, res) => {
const userID = req.params.id;
const postID = req.params.id2;
db("users")
.where({ id: Number(userID)})
.then((user) => {
db('posts') .where({ id: Number(postID)})
.then((post) => {
//verify if post and user exists
if (post && user) {
req.body.content ? insertComment({
content: req.body.content,
user: userID,
post: postID
})
.then(
res.status(201).json(req.body)
)
.catch((err) => {
console.log(err);
})
: res.status(400).json({
errorMessage: "Please insert text .",
});
} else {
res.status(404).json({
message: "user not found",
});
}
})
})
.catch((err) => {
res.status(500).json({
err,
message: "Error processing request",
});
});
});
function insertComment(comment) {
return db("comments").insert(comment).where({
user: comment.user,
post: comment.post
});
}
since you're already using async function i'd first recommend to use async/await, second notice is that knex returns an array and not an object for example
db("users")
.where({ id: Number(userID)})
.then((user) => {
// user is an array
});
you can chain a query with .first() to retrieve the first object and not an array
Reference from knex documentation
using async/await could save you from callback hell
server.post("/users/:id/posts/:id2/comments", async (req, res) => {
const userID = req.params.id;
const postID = req.params.id2;
try {
const user = await db("users").where("id", Number(userID)).first();
const post = await db("posts").where("id", Number(postID)).first();
if (post && user) {
if (req.body.content) {
await insertComment({
content: req.body.content,
user: userID,
post: postID,
});
return res.status(201).json(req.body);
} else {
return res.status(400).json({
errorMessage: "Please insert text .",
});
}
} else {
return res.status(404).json({
message: "user or post not found",
});
}
} catch (err) {
return res.status(500).json({
err,
message: "Error processing request",
});
}
});
async function insertComment(comment) {
return db("comments").insert(comment).where({
user: comment.user,
post: comment.post,
});
}
and if you have lots of relationships in your application you might find it useful if you want to use an ORM like Objection as it is built on knex.
I'm trying to work out how to receive helpful error messages on the client side, but keep getting generic error messages. For example, trying to sign up with an email that is not available should result in the email#email.com is already in use error message. I, however, get the generic Request failed with status code 409 message, which is obviously unhelpful to the user. The network response is as expected as seen in the screenshot below. What gives? Why am I not getting the same error message as my (Redux) payload?
Below are the relevant code snippets.
Sign up controller
export default {
signup: async (req, res, next) => {
try {
const { fullname, username, email, password } = req.body;
// Check if there is a user with the same email
const foundUser = await User.findOne({ email });
if (foundUser) {
return res.status(409).send({ error: `${email} is already in use` });
}
const newUser = await User.create({
fullname,
username,
email,
password,
});
// Assign token to succesfully registered user
const token = authToken(newUser);
return res.status(200).send({ token, user: newUser });
} catch (error) {
next(error);
}
},
};
Sign up action
export const createAccount = ({
fullname,
username,
email,
password,
history
}) => async dispatch => {
dispatch({
type: actionTypes.CREATE_ACCOUNT_REQUEST,
});
try {
const {
data: {
newUser: { token, user },
},
} = await request.post('/auth/signup', {
fullname,
username,
email,
password,
});
localStorage.setItem('auth-token', token);
dispatch({
type: actionTypes.CREATE_ACCOUNT_SUCCESS,
payload: user
});
// Redirect to home
history.push('/home');
} catch (error) {
dispatch({
type: actionTypes.CREATE_ACCOUNT_FAILURE,
payload: error.message
});
}
};
Sign up network response
Redux sign up error payload
Try 'error.response.data.error' instead of 'error.message'
I'm new to using async await and I'm trying a Auth createUserWithEmailAndPassword in firebase.
signUp
exports.signup = async (req, res) => {
const { email, password, confirmPassword, handle } = req.body
const newUser = {
email,
password,
confirmPassword,
handle
}
try {
const response = await firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password)
const token = response.getIdToken()
console.log('THIS IS THE RESPONSE', token)
// return token
return res.status(200).json({
message: 'User Successfully Added!',
token: token
})
} catch (err) {
if (err.code === 'auth/email-already-in-use') {
return res.status(400).json({
message: 'Email already taken!'
})
} else {
return res.status(500).json({
message: 'Something went wrong, Please try again later'
})
}
}
}
My problem is this is actually creating an account but always returning a status of 500 Something went wrong, Please try again later
EDIT:
console.log(err) gives the following output:
TypeError: response.getIdToken is not a function
I'll try to look into it.
createUserWithEmailAndPassword returns Promise< UserCredential > And getIdToken is a method of user (Documentation)
const response = await firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password);
const token = await response.user.getIdToken(); // getIdToken is a method of user
console.log('THIS IS THE RESPONSE', token);
// return token
return res.status(200).json({
message: 'User Successfully Added!',
token: token
});