Firebase Function/ Express: Can't set headers after they are sent - javascript

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.

Related

How to make a post request by SERVER not by user

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.

How do I set specific roles to redirect from specific routes with Custom Claims?

I am using NodeJS, Express and plain vanilla javascript/html. Not React or anything else.
With firebase I made it to when the user registers, they will automatically be called a customer (on the server-side). As shown:
server.js
app.post('/register', (req,res) => {
let {first_name, last_name, email, uid} = req.body;
db.collection('users').doc(uid).set(req.body)
.then(data => {
res.json({
uid: req.body.uid,
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
seller: req.body.seller
})
})
admin.auth()
.setCustomUserClaims(uid, {
type: "customer",
})
.then(() => console.log('done'))
})
But now, I would like to make this route to where it will redirect if the type is a customer.
if(idToken.claim.type === 'customer') {redirect('/') }
app.get('/seller', (req,res) => {
res.sendFile(path.join(staticPath, "seller.html"));
})
So I thought, what if I were to get the Token from the user and the type as soon as they log in, and send it back to the client. This will work.
login.js
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
fetch('/getMyClaims', {
method: 'post',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({uid: user.uid,
idToken: idTokenResult.claims.type}),
})
.then(() => res.json)
.catch(err => console.log(err));
});
and now my server.js now includes:
app.post('/getMyClaims', async(req,res) => {
let {uid,idToken} = req.body;
admin.auth()
.getUser(uid)
.then((userRecord) => console.log(userRecord))
})
and this is where I get stuck, because I am trying to find out how can I call the results of '/getMyClaims' to redirect a user, if they are a customer and are trying to access the '/seller' URL.
I did read the documents as given https://firebase.google.com/docs/auth/admin/custom-claims, but it does not really show how to re-route if claim has a specific type in the backend.
I've figured things out after hours of this!
server.js
var block;
var blockware = (req,res,next) => {
if(block == true || block == undefined){
console.log("deny access", block);
return res.sendStatus(401);
}
console.log("allow",block);
next();
}
app.post('/getMyClaims', async(req,res) => {
let {uid,idToken} = req.body;
if(idToken === 'customer'){
block = true;
} else if(idToken === 'admin'){
block = false;
} else {
block = true;
}
admin.auth()
.getUser(uid)
.then((userRecord) => console.log(userRecord))
})
app.get(['/seller', '/products', '/'], blockware, (req,res) => {
res.sendFile(path.join(staticPath, ""));
})
So now if user has a customer type claim then they are blocked from accessing seller. Otherwise, admin can access seller.
Even when user is logged out since it is automatically set to true, everyone will be blocked from it.
referenced this: express.js - single routing handler for multiple routes in a single line

Next-Auth signIn with Credentials is not working in NextJS

I'm integrating next-auth package to my fresh Next.js project. I have followed all of the Next.js and next-auth documentations but not able to find a solution.
The issue I'm facing goes like this:
I want to Login to my Next.js app using Email & Password submitted to my API Server running on Laravel.
When submitting the login form I'm executing the below function.
import { signIn } from "next-auth/client";
const loginHandler = async (event) => {
event.preventDefault();
const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("finished signIn call");
console.log(result);
};
And code shown below is in my pages/api/auth/[...nextauth].js
import axios from "axios";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
session: {
jwt: true,
},
providers: [
Providers.Credentials({
async authorize(credentials) {
axios
.post("MY_LOGIN_API", {
email: credentials.email,
password: credentials.password,
})
.then(function (response) {
console.log(response);
return true;
})
.catch(function (error) {
console.log(error);
throw new Error('I will handle this later!');
});
},
}),
],
});
But when try to login with correct/incorrect credentials, I get the below error in Google Chrome console log.
POST http://localhost:3000/api/auth/callback/credentials? 401 (Unauthorized)
{error: "CredentialsSignin", status: 401, ok: false, url: null}
Am I missing something here?
From the documentation (https://next-auth.js.org/providers/credentials#example)
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' }
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null or false then the credentials will be rejected
return null
// You can also Reject this callback with an Error or with a URL:
// throw new Error('error message') // Redirect to error page
// throw '/path/to/redirect' // Redirect to a URL
}
}
You are not currently returning a user or null from the authorize callback.
Answer posted by shanewwarren is correct, but here is more elaborated answer,
Using axios to solve this
async authorize(credentials, req) {
return axios
.post(`${process.env.NEXT_PUBLIC_STRAPI_API}/auth/login`, {
identifier: credentials.identifier,
password: credentials.password,
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error.response);
throw new Error(error.response.data.message);
}) || null;
},

Javascript :( 500 Internal server error) in the postman

Material that i used
windows10
Firebase (fire-store)
postman
Javascript , express
situation
I tried to sign up to firebase and make data on the firestore. but the internal error occurs. Does someone confirm my code?
index.js
// Signup route
app.post('/signup', (req,res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassward: req.body.confirmPassword,
handle: req.body.handle
};
//TODO validate data
let token, userId;
db.doc(`/users/${newUser.handle}`)
.get()
.then(doc => {
if(doc.exists) {
return res.status(400).json({ hanldle: 'this hanlde is already taken'});
}else {
return firebase()
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password);
}
})
.then((data) => {
userId = data.user.uid;
return data.usergetIdToken()
})
.then( ( idToken ) => {
token = idToken ;
const userCredentials = {
handle: newUser.handle,
email: newUser.email,
createdAt: new Date().toISOString(),
userId
};
return db.doc(`/users/${newUser.handle}`).set(userCredentials);
})
.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 alread is used '})
} else {
return res.status(500).json({ err : err.code });
}
});
});
exports.api = functions.https.onRequest(app);
My console visual studio
! functions: The Cloud Firestore emulator is not running, so calls to Firestore will affect production.
i functions: Beginning execution of "api"
! External network resource requested!
- URL: "http://169.254.169.254/computeMetadata/v1/instance"
- Be careful, this may be a production service.
! External network resource requested!
- URL: "http://metadata.google.internal./computeMetadata/v1/instance"
- Be careful, this may be a production service.
i functions: Finished "api" in ~9s
i functions: Beginning execution of "api"
i functions: Finished "api" in ~8s
Postman

Why am I not getting the appropriate error messages displayed upon (network) request?

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'

Categories