I have a function to signup users in my express API. Please have a look at signup route:
//Route to SIGN UP
app.post('/signup', (req, res) => {
const newUser = {
email : req.body.email,
password : req.body.password,
confirmPassword: req.body.confirmPassword,
handle: req.body.handle
}
let errors = {};
if(isEmpty(newUser.email)) errors.email = 'Must not be empty';
else if (!isEmail(newUser.email)) errors.email = 'Must be a valid email address';
if(isEmpty(newUser.password)) errors.password = 'Must not be empty';
if(newUser.password !== newUser.confirmPassword) errors.confirmPassword = 'Passwords must match';
if(isEmpty(newUser.handle)) errors.handle = 'Must not be empty';
if(Object.keys(errors).length > 0) return res.status(400).json(errors);
let token, userId;
db.doc(`/users/${newUser.handle}`).get()
.then(doc => {
if(!doc.exists) {
return firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password)
} else {
return res.status(400).json({ handle: 'this handle already exists'})
}
})
.then(data => {
userId = data.user.uid;
return data.user.getIdToken()
})
.then(idToken => {
token = idToken;
const userCredential = {
handle: newUser.handle,
email: newUser.email,
createdAt: new Date().toISOString(),
userId
}
db.doc(`/users/${newUser.handle}`).set(userCredential);
})
.then ( () => {
return res.status(201).json({ token })
})
.catch(err => {
if(err.code === 'auth/email-already-in-use') {
return res.status(400).json({ email: 'Email is already in use'})
} else {
return res.status(500).json(console.log(err));
}
}
)
})
//end of SIGN UP
Postman API doesn't show anything, if I send some JSON message in my last else statement, that gets shown up in the postman.
It shows me error in powershell when I run => firebase serve
i functions: Beginning execution of "api"
Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync (D:\Projects\socialape\functions\node_modules\google-auth-library\build\src\auth\googleauth.js:161:19)
at process._tickCallback (internal/process/next_tick.js:68:7)
i functions: Finished "api" in ~1s
Related
Sorry for this probably very basic question but I dont fully understand error handling.
I have a login route. I want to send an error msg for example the case where a wrong email is provided
const user = await User.findOne({email})
if (!user) return res.status(404).send({error: "email not found"});
However that one is never received in my client. The only err I see when i log it is
[AxiosError: Request failed with status code 404]
How do I do it? Thanks !!
const authReducer = (state, action) => {
switch (action.type) {
case 'add_error':
return {...state, errorMessage: action.payload}
case 'signin_success':
return {errorMessage: '', token: action.payload}
default:
return state;
}
};
const login = dispatch => async ({email, password}) => {
try {
const response = await API.login(email, password); // I also tried to put in outside the try block to maybe then get the err msg in the response but didnt
await AsyncStorage.setItem('token', response.data.token);
dispatch({type: 'signin_success', payload: response.data.token});
Navigation.navigate('Tabs', { screen: 'Home' });
} catch (err) {
dispatch({type: 'add_error', payload: 'Something went wrong with signing up'})
}
};
let API = {
login: (email, password) => {
return BaseAPI.post('/login', {email, password})
}
}
route handler
app.use(router.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(422).send({error: "-Must provide email and password"})
}
const user = await User.findOne({email})
if (!user) return res.status(404).send({error: "email not found"});
try {
await user.comparePassword(password);
const token = jwt.sign({userId: user._id}, 'MY_SECRET_KEY')
res.send({token});
} catch (err) {
return res.status(422).send({error: 'Invalid password or me'});
}
}
I'm trying to make email verification in my vue.js/express app.
I can create the user and send emails. But showing a message like "verification mail sent" won't work.
The error occurs when executing the code in the then() callback after the execution in DataService.
When registering the following functions are executed:
vuex
const actions = {
registerUser({
commit
}, user) {
commit('registerRequest', user)
return DataService.registerUser(JSON.stringify(user))
// HERE'S THE ERROR
.then(response => {
commit('confirmation', response.message)
setTimeout(() => {
state.status = {
confirmHere: ''
}
}, 4000);
})
.catch(...)
confirmation:
confirmation: (state, msg) => {
state.status = {
confirmHere: msg
}
},
DataService
registerUser(user) {
// Send email for registration
apiClient.post('/user/register/sendMail', user)
.then(res => {
return apiClient.post(`/user/register`, user)
})
.catch(err => {
throw err;
})
},
The sendmail function is using nodemailer to send an email and returns
res.status(200).json({
message: "success"
});
The register function in express is:
router.post('/register', async (req, res) => {
try {
if (req.body.username !== undefined && req.body.password !== undefined) {
let password = await bcrypt.hashSync(req.body.password, saltRounds);
let compareUser = await db.getObject({}, User, 'SELECT * FROM app_users WHERE username=? LIMIT 1', [req.body.username]);
if (compareUser !== undefined) {
res.status(409).json('User already exists');
return;
}
const tmp = {
username: req.body.username,
password: password
};
await db.query('INSERT INTO app_users SET ?', [tmp]);
let user = await db.getObject({}, User, 'SELECT * FROM app_users WHERE username=? LIMIT 1', [req.body.username]);
if (user === undefined)
res.status(500).json('Internal server error');
res.status(201).json({
"message": "Bestätigungs-Email gesendet."
});
} else {
res.sendStatus(400);
}
} catch (error) {
res.sendStatus(500);
}
});
You forgot to return the response from DataService.registerUser
// DataService.js
registerUser(user) {
// Send email for registration
return apiClient.post('/user/register/sendMail', user)
.then(res => {
return apiClient.post(`/user/register`, user)
})
.catch(err => {
throw err;
})
The issue is that your registerUser function doesn't return anything whereas you're expecting it to return a promise.
Change your registerUser to:
registerUser(user) {
// Send email for registration
return apiClient.post('/user/register/sendMail', user)
.then(res => {
return apiClient.post(`/user/register`, user)
})
}
(FYI in the example, I left the .throw out because it already gets handled by the Promise you return ;)
I'm testing out a route in a firebase app I'm building. The route in question recieves 'shouts' which are akin to status updates. Anyway, I just integrated auth to protect this route using FBuath, but I keep getting the following error:
Firebase ID token has expired. Get a fresh ID token from your client app and try again
I've tried relogging in using valid credentials, and then instantly trying to post something via the route, but keep getting this error. Any thoughts as to why? Code as follows, and the route in question has the endpoint '/shout'. Cheers
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
const config = {
apiKey: "AIzaSyBZjz9BNwj4UDwWLoQ1SOD5hB5QcNw3qqs",
authDomain: "social-ape-21874.firebaseapp.com",
databaseURL: "https://social-ape-21874.firebaseio.com",
projectId: "social-ape-21874",
storageBucket: "social-ape-21874.appspot.com",
messagingSenderId: "323044904203",
appId: "1:323044904203:web:edcbc619169a2087f8e60e",
measurementId: "G-T34PXDM1X7"
}
admin.initializeApp
const express = require('express')
const app = express()
const firebase = require('firebase')
firebase.initializeApp(config)
const db = admin.firestore()
app.get('/shouts', (req,res) => {
db
.collection('shouts')
.orderBy('createdAt', 'desc') //returns shouts in order in which they were made
.get()
.then((data) => {
let shouts = []
data.forEach((doc) => {
shouts.push({
shoutId: doc.id,
body: doc.data().body,
userHandle: doc.data().userHandle,
createdAt: doc.data().createdAt
})
})
return res.json(shouts)
})
.catch((err) => console.error(err))
})
const FBauth = (req,res,next) => {
let idToken
if(req.headers.authorization && req.headers.authorization.startsWith('Bearer ')){
idToken = req.headers.authorization.split('Bearer ')[1]
}else{
console.error('No token found')
return res.status(403).json({error: 'Unauthorized'})
}
//verify that this token was issued by our application
admin.auth().verifyIdToken(idToken)
.then(decodedToken => {
req.user = decodedToken
return db.collection('users')
.where('userId', '==', req.user.uid)
.limit(1) //limits results to one document
.get()
})
.then(data => {
req.user.handle = data.docs[0].data().handle //data() is a function that extracts data from document
return next() //next() is a function that allows request to proceed to shout post route
})
.catch(err => {
console.error('Error while verifying token', err)
return res.status(403).json(err)
})
}
app.post('/shout', FBauth, (req,res) => {
const newShout = {
body: req.body.body,
userHandle: req.body.userHandle, //userhandle identifies who is owner of shout
createdAt: new Date().toISOString()
}
db
.collection('shouts')
.add(newShout)
.then((doc) => {
res.json({message: `document ${doc.id} created successfully`})
})
.catch((err) =>{
res.status(500).json({error: 'something went wrong'})
console.error(err)
})
})
//helper function to determine if string is empty or not
//note: .trim() removes whitespace from email field
const isEmpty = (string) => {
if (string.trim()=== '') {
return true
} else {
return false
}
}
//helper function to determine if valid email
const isEmail = (email) => {
const regEx = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (email.match(regEx)) {
return true
} else {
return false
}
}
//Sign up route
app.post('/signup', (req,res) => {
//here we need to extract form data from request body
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassword: req.body.confirmPassword,
handle: req.body.handle,
}
let errors = {}
if(isEmpty(newUser.email)){
errors.email = 'Email must not be empty'
} else if(!isEmail(newUser.email)) {
errors.email = 'Must be a valid email address'
} //if not empty, need to check if valid email
if(isEmpty(newUser.password)){
errors.password = 'Must not be empty'
}
if(newUser.password !== newUser.confirmPassword) {
errors.confirmPassword = 'Passwords must match'
}
if(isEmpty(newUser.handle)){
errors.handle = 'Must not be empty'
}
if(Object.keys(errors).length>0) {
return res.status(400).json(errors)
}
//sign up user
let token
db.doc(`/users/${newUser.handle}`).get()
.then((doc)=> {
if(doc.exists){
return res.status(400).json({handle: 'this handle is already taken'})
} else {
return firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password)
}
})
.then(data => {
userId = data.user.uid
return data.user.getIdToken()
})
.then(token => {
token=token
const userCredentials = {
handle: newUser.handle,
email: newUser.email,
createdAt: new Date().toISOString(),
userId:userId
}
db.doc(`/users/${newUser.handle}`).set(userCredentials)
return res.status(201).json({token})
})
.then(() => {
return res.status(201).json({token})
})
.catch(err => {
console.error(err)
return res.status(500).json({error:err.code})
})
})
//token is used to access route that is protected
//login route
app.post('/login', (req,res) => {
const user = {
email: req.body.email,
password: req.body.password
}
let errors = {}
if(isEmpty(user.email)){
errors.email = 'Must not be empty'
}
if(isEmpty(user.password)){
errors.password = 'Must not be empty'
}
if(Object.keys(errors).length >0) {
return res.status(400).json(errors)
}
firebase.auth().signInWithEmailAndPassword(user.email, user.password)
.then(data => {
return data.user.getIdToken()
})
.then(token => {
return res.json({token})
})
.catch(err => {
console.error(err)
if(err.code ==="auth/wrong-password" ){
return res.status(403).json({general: 'Wrong credentials, please try again'})
} else
return res.status(500).json({error: err.code})
})
})
exports.api = functions.https.onRequest(app)
I don't understand it. I am not able to login. User is already in my database, and when I log in, it simply says:
POST http://localhost:3000/api/v1/users/login 402 (Payment Required)
When I register for the first time, and then login, login is successful. If I logout, and then try to log in with that same email and password, it's throwing me the above error. I'm not even using someone's API. It's my own created one. It's sending me a response of "incorrect password"
Here's the controller:
loginUser: (req, res, next) => {
const { email, password } = req.body
if (!email || !password) {
return res.status(400).json({ message: "Email and password are must" })
}
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" })
}
})
}
User model
const mongoose = require("mongoose")
const bcrypt = require("bcrypt")
const Schema = mongoose.Schema
const userSchema = new Schema({
username: { type: String, required: true },
email: { type: String, reuired: true },
password: { type: String, required: true },
posts:[{ type: Schema.Types.ObjectId, ref: "Post" }]
}, { timestamps: true })
userSchema.pre("save", function (next) {
if (this.password) {
const salt = bcrypt.genSaltSync(10)
this.password = bcrypt.hashSync(this.password, salt)
}
next()
})
userSchema.methods.confirmPassword = function (password) {
return bcrypt.compareSync(password, this.password)
}
const User = mongoose.model("User", userSchema)
module.exports = User
registration controller
registerUser: (req, res) => {
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 })
}
})
}
Edit
loginUser: async (req, res, next) => {
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" })
}
})
}
new post 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 })
})
})
}
})
}
You might want to refactor your code so that you do the bcrypt operations in controller not in the model. You are checking this.password after the user is updated (creating new posts) and since this is the user, the below code is being met each time you update the user object.
if (this.password) {
const salt = bcrypt.genSaltSync(10)
this.password = bcrypt.hashSync(this.password, salt)
}
So your hashing it every time you update the user (create a post). Instead, remove the above code from the userSchema.pre(...) and try doing the bcrypt hashing only when the user first registers.
registerUser: (req, res) => {
var { username, email, password } = req.body
if (password) {
const salt = bcrypt.genSaltSync(10)
password = bcrypt.hashSync(password, salt)
}
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 })
}
})
}
This way the hashing occurs only once at the creation of the user and should remain consistent throughout other operations.
As for the Can't set headers after they are sent error, you might be sending a response twice, since the error appears to come from the posts controller. You are likely sending the user response and the post response. Maybe don't send the posts response since you will be sending it along in the user response.
More info on the error here.
I am able to register and login to the application but I receive the following server error:
"Unhandled rejection Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" upon registration. I came across similar questions here but none of them resolved my problem.
authController.js:
const User = require("../models/User");
const jwt = require("jsonwebtoken");
const simplecrypt = require("simplecrypt");
const sc = simplecrypt();
process.env.SECRET_KEY = "secret";
exports.postLogin = (req, res, next) => {
const { username, password } = req.body;
let validationMessages = [];
if (!username || !password) {
validationMessages.push({ message: "Please fill in all fields" });
}
if (password.length < 6) {
validationMessages.push({
message: "Password should be at least 6 characters"
});
}
if (validationMessages.length > 0) {
res.sendStatus(403).json(validationMessages);
} else {
User.findOne({ where: { username: username } })
.then(user => {
if (!user) {
res.sendStatus(400).json({
message: "Invalid username or password"
});
} else if (password == sc.decrypt(user.password)) {
const token = jwt.sign(user.dataValues, process.env.SECRET_KEY, {
expiresIn: 1440 // expires in 24 hours
});
res.send(token);
}
})
.catch(err => {
res.send("Error: " + err);
});
}
};
exports.postRegister = (req, res, next) => {
const { username, password, password2 } = req.body;
let validationMessages = [];
//Check required fields
if (!username || !password || !password2) {
validationMessages.push({ message: "Please fill in all fields" });
}
if (password.length < 6 || password2.length < 6) {
validationMessages.push({
message: "Password should be at least 6 characters"
});
}
if (password !== password2) {
validationMessages.push({
message: "Passwords do not match"
});
}
if (validationMessages.length > 0) {
return res.sendStatus(400).json(validationMessages);
} else {
User.findOne({ where: { username: username } })
.then(user => {
if (user) {
return res.sendStatus(403).json("User already exists");
}
const hashedPassword = sc.encrypt(password);
User.create({ username: username, password: hashedPassword })
.then(user => {
return res.sendStatus(200).send(user);
})
.catch(err => {
throw new Error(err);
});
})
.catch(err => {
throw new Error(err);
});
}
};
exports.getProfile = (req, res, next) => {
const decoded = jwt.verify(
req.headers["authorization"],
process.env.SECRET_KEY
);
User.findOne({
where: {
id: decoded.id
}
})
.then(user => {
if (user) {
res.statusCode(200).json(user);
} else {
throw new Error("User does not exist");
}
})
.catch(err => {
throw new Error(err);
});
};
I am using Node.JS v12.14.0 and Express.JS v4.17.1.
I resolved it myself. My problem was using res.sendStatus which sets the given response HTTP status code and sends its string representation as the response body. res.json will set the content-type response header, but at time time the response will already have been sent to the client. So simply res.send() should replace res.sendStatus().