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
});
Related
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.
To simplify the situation I'll just post the following controller for an express route which interactions with a Postgres Database. My question is about error handling. If an error occurs it will be caught within the catch clause. But how can I access the errors thrown by the database queries itself. If I make several await several queries and one of them fails I need probably to restore stuff in the database? For example if the insertion of the user in the user table is a success, but the following query of inserting the user in another table fails, I need to delete the user from the user table again. How does one model such flows?
//
// Register User
//
export const registerUser = async (request, response, next) => {
try {
const usersWithSameMail = await client.query(`SELECT * FROM public.users WHERE email = '${user.email}'`);
if(usersWithSameMail.rows.length > 0){
return response.status(403).json({"code": "ERROR", "message": "Email is already registered"})
} else {
await client.query(`
INSERT INTO public.users(first_name, last_name, email, password)
VALUES ('${user.first_name}', '${user.last_name}', '${user.email}', crypt('${user.password}', gen_salt('bf', 8)));
`);
// more await statements...
return response.status(200).json({"code": "INFO", "message": "Verification mail sent to user"});
}
} catch (error) {
return response.status(500).json({"code": "ERROR", "message": "Error occured while registering the user. Please try again."});
}
}```
You can use middlewares chaining your routes handler. In order to it work, you will have to change your current working code to use Single-responsibility principle. Do only one responsability per middleware and chain all handlers to work as one.
Lets say you want to insert new user, to perform this operation we should:
lookup if email is unique
hash password
Insert new user
return inserted data in postgres back as a response
Following the middleware chaining we should implement a function for each action and chain each action in route definition:
const postgres = require('../../lib/postgres');
const crypto = require('crypto');
exports.insertedData = (req, res) => {
res.status(200).json(req.employee);
};
exports.hashPassword = (req, res, next) => {
crypto.scrypt(req.body.password.toString(), 'salt', 256, (err, derivedKey) => {
if (err) {
return res.status(500).json({ errors: [{ location: req.path, msg: 'Could not hash password'}] });
}
req.body.kdfResult = derivedKey.toString('hex');
next();
});
};
exports.lookupEmailUnique = (req, res, next) => {
const sql = 'SELECT e.email FROM public.users e WHERE e.email=$1';
postgres.query(sql, [req.body.email], (err, result) => {
if (err) {
return res.status(500).json({ errors: [{ location: req.path, msg: 'Could not query database' }] });
}
if (result.rows.length > 0) {
return response.status(403).json({"code": "ERROR", "message": "Email is already registered"})
}
next()
});
}
exports.insertNewUser = (req, res, next) => {
const sql = 'INSERT INTO public.users(first_name, last_name, email, password) VALUES ($1,$2,$3,$4} RETURNING *';
postgres.query(sql, [req.body.first_name, req.body.last_name, req.body.email, req.body.kdfResult], (err, result) => {
if (err) {
return res.status(500).json({ errors: [{ location: req.path, msg: 'Could not query database'}] });
}
req.employee = result.rows[0];
next();
});
};
here is your route declaration:
const router = require('express').Router();
const userService = require('../controllers/user.controller');
router.post('/register', userService.lookupEmailUnique, userService.hashPassword, userService.insertNewUser, userService.insertedData);
module.exports = router;
Here in routes you are using the middeware to do the chaning, you only pass the control to next middleware if all conditions are met and has full control from database erros.
In my example I do not used the async/await but I can change my example to have a version using async/await.
example middleware with transaction
exports.deletePostagem = async (req, res, next) => {
try {
await postgres.query('BEGIN');
const sql2 = 'UPDATE comentario SET postagem = null WHERE postagem = $1';
await postgres.query(sql2, [req.params.id]);
const sql3 = 'DELETE FROM postagem WHERE id = $1';
await postgres.query(sql3, [req.params.id]);
await postgres.query('COMMIT');
res.status(204).json();
res.end();
} catch (err) {
await postgres.query('ROLLBACK');
return res.status(500).json({ errors: [{msg: 'Could not perform operation' }]})
}
}
I used this only as an example, but in my projects I always have a middeware for validate/sanitize the data that comes in request before using in database query prepared statements.
based on transaction documentation in node.js you can use rollback
export const registerUser = async (request, response, next) => {
try {
let error = null;
const client; // create a client, connect to the db
try {
await client.query("begin");
await client.query("first query");
await client.query("second query");
await client.query("third query");
await client.query("commit"); //do commit when is finished all queries
} catch (error) {
error = error;
await client.query("rollback");
} finally {
client.release(); // close the connection
}
if (error) {
return response.status(500).json({ message: error }); // error message
}
return response.status(200).json({ message: "My message" }); // success message
} catch (err) {
return response.status(500).json({ message: err });
}
}
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 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.
I've mostly utilised the Hapi framework to build RESTful APIs. For this project I'm using Express and I'm a bit lost as to why this is happening.
When I test the POST endpoint using Postman, the first request is fine, but I would get an error when I make the second request.
Error: Can't set headers after they are sent.
The code for the route handler is below:
const login = (req, res) => {
const validation = authScema.loginPayload.validate(req.body)
if (validation.error) {
return res.status(400).send(validation.error.details[0].message)
}
const { email, password } = req.body
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.catch(error => {
// Handle Errors here.
if (error) {
return res.status(400).send('Invalid login details.')
}
})
firebase.auth().onAuthStateChanged(user => {
if (user) {
const userObject = {
email: user.email,
uid: user.uid
}
const token = jwt.sign(userObject, secret)
return res.status(200).send(token)
}
})
}
I don't understand why headers are resent since in every branch, I return. It should have exited the function, right?
Turns out, signInWithEmailAndPassword
is a promise that returns the user in the happy path
So, the following is the final code:
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(user => {
const userObject = {
email: user.email,
uid: user.uid
}
const token = jwt.sign(userObject, secret)
res.status(200).json({ token })
})
.catch(error => {
if (error) {
res.status(400).json({ message: 'Invalid login details.' })
}
})
The onOnAuthStateChanged is not necessary in this case.