What does this cloud function error mean? - javascript

I have this code to check whether an email is taken or not, it works it just throws back an error in the cloud functions logs, Here's the swift code:
let functions = Functions.functions()
functions.httpsCallable("uniqueEmail").call(email) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
}
// ...
}
if let text = (result?.data as? [String: Any])?["text"] as? String {
//self.resultField.text = text
}
}
Here's the cloud function code:
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp()
exports.uniqueEmail = functions.https.onCall((req, res) => {
const email = req.query.email;
if (!email) {
throw new functions.https.HttpsError('invalid-argument', 'Missing email parameter');
}
admin.auth().getUserByEmail(email)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
return({taken: true}); // Returns {"taken": "true"} or {"taken": "false"}
})
.catch(function(error) {
console.log('Error fetching user data:', error);
return({taken: false}); // Returns {"taken": "true"} or {"taken": "false"}
});
});
And here's the error's I'm getting in cloud functions, I'm not sure what both mean, so could someone explain them both to me?
1.
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions
2.
Unhandled error TypeError: Cannot read property 'email' of undefined
at exports.uniqueEmail.functions.https.onCall (/user_code/lib/index.js:7:28)
at /user_code/node_modules/firebase-functions/lib/providers/https.js:330:32
at next (native)
at /user_code/node_modules/firebase-functions/lib/providers/https.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/providers/https.js:24:12)
at func (/user_code/node_modules/firebase-functions/lib/providers/https.js:294:32)
at corsHandler (/user_code/node_modules/firebase-functions/lib/providers/https.js:350:44)
at cors (/user_code/node_modules/firebase-functions/node_modules/cors/lib/index.js:188:7)
at /user_code/node_modules/firebase-functions/node_modules/cors/lib/index.js:224:17
at originCallback (/user_code/node_modules/firebase-functions/node_modules/cors/lib/index.js:214:15)

The first message is just a warning and you can ignore it unless you're trying to make outbound connections.
The second message is saying that your function code is expecting to receive an input object with a property called "query" that's an object with a property called "email". But that's not what you're passing from the client. In the client, you passed a string (I assume, as you didn't say exactly what email is in your swift code).
Futhermore, I suspect that you might be confused about what callable functions take as arguments. You're showing (req, res) as arguments, a request and a response, which is what you would normally expect from a HTTP type function. But that's not how callables work. Now might be a good time to review the documentation for callable functions. The first argument to a callable function is the data object itself passed from the client. Use that instead, don't assume that the data is coming in through a query string.

Related

Not able to extract custom error messages from Firebase Cloud Function

I have a firebase cloud function running locally. Its implemented like this
exports.verifyApp = functions.https.onRequest((req, res) => {
const {ref} = req.query;
// some business logic
if( ref === "123") return res.status(200).json({message: "success"});
return res.status(400).json({ error: "invalid-argument", message: "ref not provided" });
})
On frontend I am calling the cloud function like this
import {
getFunctions,
httpsCallableFromURL,
} from "firebase/functions";
const verifyClaim = httpsCallableFromURL(
getFunctions(),
"http://localhost:5001/{placeholder}/us-central1/verifyApp?ref=23"
);
verifyClaim()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error.code, error.message);
});
I am not able to read/parse the custom error message being sent from the function. The .catch block only prints generic firebase error code and message i.e invalid-argument and internal. I can see the response in network tab though but .catch never seems to pick up the custom error message I am sending from cloud function.
You are mixing up Callable Cloud Functions and HTTP Cloud Functions.
You Cloud Function code corresponds to an HTTP one (functions.https.onRequest(...)) but the code in your front-end calls a Callable one (const verifyClaim = httpsCallableFromURL(...)).
You should adapt one or the other. It's difficult to advise which one to adapt with the few details shared in your answer.

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to client [duplicate]

I'm facing this weird issue in NodeJS when using with Passport.js, Express and Mongoose. Basically, I get an error saying "Cannot set headers after they are sent to the client" even though I don't send more than one header.
I've read other posts and tried them out as well, and none of them worked.
app.get - is there any difference between res.send vs return res.send
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Cannot set headers after they are sent to the client
I've dug through github issues and I can't seem to find a solution. I get the problem that this error is triggered when I send multiple response headers, but the fact is that I am not sending multiple headers. It seems just weird.
This is my stack trace:
(node:9236) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
Server Running on port 5000
MongoDB Connected Error
[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the
client
at validateHeader (_http_outgoing.js:503:11)
at ServerResponse.setHeader (_http_outgoing.js:510:3)
at ServerResponse.header (/Users/lourdesroashan/code/github/devlog/node_modules/express/lib/response.js:767:10)
at ServerResponse.json (/Users/lourdesroashan/code/github/devlog/node_modules/express/lib/response.js:264:10)
at Profile.findOne.then.profile (/Users/lourdesroashan/code/github/devlog/routes/api/profile.js:27:30)
at <anonymous>
This is my server code:
router.get("/userprofile", passport.authenticate('jwt', { session: false }), (req, res) => {
Profile.findOne({ user: req.user.id }).then(profile => {
if (!profile) {
return res.status(404).json({ error: "No Profile Found" });
}
else {
res.json(profile);
}
}).catch(err => {
console.log(err);
})
});
I understand what the error means, but from what I know, I don't think I am sending multiple headers, I even checked by console.log that only one of the blocks is run.
Thank you so much in advance! :)
Full Code at: https://github.com/lourdesr/devlog
EDIT:
I figured it out. It was a problem in my passport.js while trying to get the authenticated user. I forgot to use 'return' on the 'done' method, which had caused it. Just added the return statement and it worked!
That particular error occurs whenever your code attempts to send more than one response to the same request. There are a number of different coding mistakes that can lead to this:
Improperly written asynchronous code that allows multiple branches to send a response.
Not returning from the request handler to stop further code in the request handler from running after you've sent a response.
Calling next() when you've already sent a response.
Improper logic branching that allows multiple code paths to execute attempt to send a response.
The code you show in your question does not appear like it would cause that error, but I do see code in a different route here that would cause that error.
Where you have this:
if (!user) {
errors.email = "User not found";
res.status(404).json({ errors });
}
You need to change it to:
if (!user) {
errors.email = "User not found";
res.status(404).json({ errors });
// stop further execution in this callback
return;
}
You don't want the code to continue after you've done res.status(404).json({ errors }); because it will then try to send another response.
In addition, everywhere you have this:
if (err) throw err;
inside an async callback, you need to replace that with something that actually sends an error response such as:
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
throwing inside an async callback just goes back into the node.js event system and isn't thrown to anywhere that you can actually catch it. Further, it doesn't send a response to the http request. In otherwords, it doesn't really do what the server is supposed to do. So, do yourself a favor and never write that code in your server. When you get an error, send an error response.
Since it looks like you may be new here, I wanted to compliment you on including a link to your full source code at https://github.com/lourdesr/devlog because it's only by looking at that that I was able to see this place where the error is occuring.
I was receiving this error because of a foolish mistake on my part. I need to be more careful when referencing my other working code. The truly embarrassing part is how long I spent trying to figure out the cause of the error. Ouf!
Bad:
return res
.send(C.Status.OK)
.json({ item });
Good:
return res
.status(C.Status.OK)
.json({ item });
Use ctrl + F hotkey and find all 'res.' keywords
then replace them with 'return res.',
change all 'res.' to 'return res.'
something like this:
res.send() change to --> return res.send()
maybe you have 'res.' in some block, like if() statement
Sorry for the Late response,
As per the mongoose documentation "Mongoose queries are not promises. They have a .then() function for co and async/await as a convenience. However, unlike promises, calling a query's .then() can execute the query multiple time"
so to use promises
mongoose.Promise = global.Promise //To use the native js promises
Then
var promise = Profile.findOne({ user: req.user.id }).exec()
promise.then(function (profile){
if (!profile) {
throw new Error("User profile not found") //reject promise with error
}
return res.status(200).json(profile) //return user profile
}).catch(function (err){
console.log(err); //User profile not found
return res.status(404).json({ err.message }) //return your error msg
})
here is an nice article about switching out callbacks with promises in Mongoose
and this answer on mongooses promise rejection handling Mongoose right promise rejection handling
There is a simple fix for the node error [ERR_HTTP_HEADERS_SET]. You need to add a return statement in front of your responses to make sure your router exits correctly on error:
router.post("/", async (req, res) => {
let user = await User.findOne({email: req.body.email});
if (!user) **return** res.status(400).send("Wrong user");
});
Because of multiple response sending in your request. if you use return key word in your else condition your code will run properly
if (!profile) {
return res.status(404).json({ error: "No Profile Found" });
}
else {
**return** res.json(profile);
}
This also happen when you tries to send the multiple response for a same request !!
So make sure you always use return keyword to send response to client inorder to stop the further processing !!
Where you have this:
if (!user) { errors.email = "User not found"; res.status(404).json({ errors }); }
You need to change it to:
if (!user) { errors.email = "User not found"; return res.status(404).json({ errors }); }
I got the same error using express and mongoose with HBS template engine. I went to Expressjs and read the docs for res.render, and it says // if a callback is specified, the rendered HTML string has to be sent explicitly. So I wasnt originally sending my html explicitly in the callback,. This is only for a contact form btw, not login info, albeit GET
//Original
let { username, email } = req.query; //My get query data easier to read
res.status(200).render('index', { username, email });
//Solution without error. Second param sending data to views, Third param callback
res.status(200).render('index', { username, email }, (err, html)=>{
res.send(html);
});
In react, if your are calling the function in useEffect hook, make sure to add a dependency to the dependency Array.
I had this error from an if statement not having an else block.
if(someCondition) {
await () => { }
}
await () => { }
I changed the above to this below and solved my issue
if(someCondition) {
await () => { }
} else {
await () => { }
}
For me, I accidentally put a res.status inside of a for loop. So my server would trigger the error the second time a res.status was returned. I needed to put the res.status outside of the for loop so it would only trigger once within the function.
First of all : make sure you didn't miss any asynchronous action without an async/await or use promises/callbacks.
Then attach any res with the return keyword : return res.status(..).json({});
And finally which was my problem: don't use return res.sendStatus if you always have some return res... inside a callback function, but you can always do a retun res.status();
in my case it was :
users.save((err,savedDoc){
if(err) return res.status(501).json({})
res.status(200).json({});
});
return res.status(500); // instead ofdoing return res.sendStatus(500)
you have to enable Promises in your programm, in my case i enabled it in my mongoose schema by using mongoose.Promise = global.Promise .
This enables using native js promises.
other alternatives to this soloution is :
var mongoose = require('mongoose');
// set Promise provider to bluebird
mongoose.Promise = require('bluebird');
and
// q
mongoose.Promise = require('q').Promise;
but you need to install these packages first.
My problem besides not returning, i was forgetting to await an asynchronous function in the handler. So handler was returning and after a bit the async function did its thing. 🤦🏻‍♀️
Before:
req.session.set('x', {...});
req.session.save();
return req.status(200).end();
When i needed to await:
req.session.set('x', {...});
await req.session.save();
return req.status(200).end();
I'm putting this here for anyone else who has the same problem as me- this happened to me because I'm using the next() function without a return preceding it. Just like a lot of the other answers state, not using return with your response will / can cause / allow other code in the function to execute. In my case, I had this:
app.get("/customerDetails", async (req, res, next) => {
// check that our custom header from the app is present
if (req.get('App-Source') !== 'A Customer Header') next();
var customerID = req.query.CustomerID
var rows = await get_customer_details(customerID);
return res.json(rows);
});
In my case, I forgot to include the header in my request, so the conditional statement failed and next() was called. Another middleware function must have then been executed. After the middleware finishes, without a return, the rest of the code in the original middleware function is then executed. So I simply added a return before my next() call:
// serve customer details payload
app.get("/customerDetails", async (req, res, next) => {
// check that our custom header from the app is present
if (req.get('App-Source') !== 'A Customer Header') return next();
var customerID = req.query.CustomerID
var rows = await get_customer_details(customerID);
return res.json(rows);
});

How to create a file in the cloud function and upload to a bucket

For some time now, I've been trying to create a file (sample.txt) containing some text (hello world) and finally uploading it into a bucket. Is there a way I can implement this?. My attempted code is below:
exports.uploadFile = functions.https.onCall(async (data, context) => {
try {
const tempFilePath = path.join(os.tmpdir(), "sample.txt");
await fs.writeFile(tempFilePath, "hello world");
const bucket = await admin.storage().bucket("allcollection");
await bucket.upload(tempFilePath);
return fs.unlinkSync(tempFilePath);
} catch (error) {
console.log(error);
throw new functions.https.HttpsError(error);
}
});
Anytime this code is run I get and error like this in there firebase console:
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
at maybeCallback (fs.js:128:9)
at Object.writeFile (fs.js:1163:14)
You're not using fs.writeFile() correctly. As you can see from the linked documentation, it takes 3 or 4 arguments, one being a callback. The error message is saying you didn't pass a callback. On top of that, it doesn't return a promise, so you can't effectively await it. Consider using fs.writeFileSync() instead to make this easier.

Can I use res.send(foo) twice?

I want to send my token and my user's role to my login.component.ts.
When I was trying to find the problem, in my research I came across someone's suggestion to use
res.write(foo1)
res.write(foo2)
res.end
Instead of
res.send(foo1)
res.send(foo2)
But that didn't work.
I then tried using this to test it:
res.write(foo1)
res.end()
But this is giving me an error:
events.js:174
throw er; // Unhandled 'error' event
^
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string or Buffer. Received type object
at write_ (_http_outgoing.js:595:11)
at ServerResponse.write (_http_outgoing.js:567:10)
at User.findOne (C:\Users\notan\GitHub\se3316-notansandwich-lab5\server\controllers\user.controller.js:46:33)
at C:\Users\notan\node_modules\mongoose\lib\model.js:4604:16
at C:\Users\notan\node_modules\mongoose\lib\query.js:4348:12
at process.nextTick (C:\Users\notan\node_modules\mongoose\lib\query.js:2850:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
Emitted 'error' event at:
at C:\Users\notan\node_modules\mongoose\lib\model.js:4606:13
at C:\Users\notan\node_modules\mongoose\lib\query.js:4348:12
at process.nextTick (C:\Users\notan\node_modules\mongoose\lib\query.js:2850:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
This is my user.controller.js, which I use in my route.js which is used in my sever.js
const User = require('../models/user.model')
const jwt = require('jsonwebtoken')
exports.user_create = function (req, res, next) {
let user = new User(
{
_id: Math.random().toString(36).substr(2, 5), // generate a random ID
email: req.body.email,
password: req.body.password,
firstName: req.body.firstName,
lastName: req.body.lastName,
role: "user"
}
);
user.save(function (err, registeredUser) {
if (err) {
return next(err);
}
else {
let payload = { subject: registeredUser._id}
let token = jwt.sign(payload, 'secretKey')
res.status(200).send({token})
}
})
}
exports.user_login = function (req, res, next) {
let userData = req.body
User.findOne({email: userData.email}, (err, user) => {
if (err) {
console.log(err)
}
else {
if (!user) {
res.status(401).send('Invalid email')
}
else if (user.password !== userData.password) {
res.status(401).send('Invalid password')
}
else {
let payload = {subject: user._id}
let token = jwt.sign(payload, 'secretKey')
//res.status(200).send({token})
res.status(200).write({token})
//let role = this.user.role
// res.status(200).write({role})
res.end()
}
}
})
}
Using this works
res.status(200).send({token})
But this does not
res.status(200).write({token})
res.end()
In response to the title of your question:
Can I use res.send(foo) twice?
No, you cannot call that twice for the same request.
See the second part of the answer since the OP changed their question after I wrote this first part
In Express, you can only use res.send() or res.json() or res.end() once per request. When you execute those, it sends the request. If you try to send more on the same request, it will do nothing and will show a warning in Express.
res.write() can be called more than once, then followed by res.end() when you are finally done with the request.
In your example:
res.status(200).send({token})
res.end()
The res.send() already calls .end() for you so trying to call it again is considered an error because the request has already been sent.
FYI, .status(200) is not necessary. The default status is already 200 so res.send({token}) will already have a 200 status.
More Recent Answer for the Modified Question
Now that you've completely changed the question to be about res.write({token}), that does not work because res.write() requires a String or a Buffer as an argument and you were giving it an object. You would have to manually convert the object to JSON yourself:
res.type('application/json');
res.write(JSON.stringify({token}));
res.end();
And note that this also sets the appropriate content type. If your object is large with res.write() you may also have to pay attention to the write buffer being full and listen for the drain event. res.write() is a much lower level facility (it's at the http level, not at the Express level) than the Express functions for sending data.
Built into Express, you can use res.send() or res.json() which are Express methods that will both that you passed an object, automatically convert it to JSON for you and set the content type to JSON also. It will also handle any buffer full issues in the write stream too.
res.send({token});
or, I prefer to be more explicit in my Express code with:
res.json({token});
And, if you're trying to send multiple pieces of data, you can put them into the same object:
res.json({token, role});
Calling res.status(200).send({ token }) ends the request and sends the object back to the client...
Since the request is now ended... Calling res.end() generates the error...
You'd usually use res.end if u were piping some data (usually binary) after several res.write to close the pipe...
For more info... checkout Express' docs in the response object...
https://expressjs.com/en/api.html#res
Also... U can't send an object using res.write...
From your error... it says that it inly accepts a string or buffer. If u want to send plain objects... res.send or res.json would be more appropriate...
I found the solution: I can send multiple things in an res.send(...)
res.status(200).send({token, role})
If you want to use res.write(argument) you have to pass the argument as a string or Buffer, just like the error message says. To get the same effect just convert your response object to a string:
res.status(200).write(JSON.stringify({token}))

AWS Cognito Authentication Returns Error - Javascript SDK

I am using AWS Cognito to authenticate users in a new app that I am building.
I am using the amazon-cognito-identity-js library in my project (link to Github: https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js). Since users in this particular user pool cannot sign themselves up - I sign them up manually - I know that I need "Use case 23" as stated in the README.md from Github.
So my code is as follows:
...
const userPoolData = {
UserPoolId: <MY_USER_POOL_ID>,
ClientId: <MY_CLIENT_ID>
};
const userPool = new CognitoUserPool(userPoolData);
const authenticationData = {
Username: email,
Password: tempPassword
};
const userData = {
Username: email,
Pool: userPool
}
const authenticationDetails = new AuthenticationDetails(authenticationData);
const cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
console.log(result);
},
onFailure: (err) => {
console.log("Error from cognito auth: ", err);
},
newPasswordRequired: (userAttributes) => {
delete userAttributes.email_verified;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
}
})
...
When I execute this code, I successfully confirm my user. I can see this in the AWS Cognito console. However, instead of receiving the result object, I get an error in the javascript console on the client that says:
Uncaught (in promise) TypeError: Cannot read property 'onFailure' of undefined
at eval (CognitoUser.js:572)
at eval (Client.js:108)
But when I attempt to sign in with the newPassword in place of the tempPassword previously sent, I am now able to successfully get the resultobject with the three tokens all present.
So I know that everything is kinda working, but isn't what I am expecting.
What is causing this error? How can I fix it? I want to receive the result object immediately when the user first signs in with the tempPassword and their newPassword so that they can start using the app.
EDIT:
Thinking that I had to retrieve the userAttributes myself was a mistake. The newPasswordRequired function passes them automatically. So I updated my code above to go with "Use case 23" as presented on Github.
But now I get a slightly different error than before:
Uncaught (in promise) TypeError: callback.onFailure is not a function
at eval (CognitoUser.js:572)
at eval (Client.js:108)
Everything still works as far as Cognito is concerned, but there must be something wrong with my onFailure function, which is very strange.
Any thoughts?
Thanks in advance
Alright, I solved it. The issue was that I was using ES6 arrow functions. As Apolozeus pointed out, I needed to pass this into the cognitoUser.completeNewPasswordChallenge function. But due to the way ES6 behaves, this was returning undefined. So, changing my cognitoUser.authenticateUser function to the following solved everything:
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
resolve(result.getAccessToken().getJwtToken());
},
onFailure: function (err) {
console.log("Error from cognito promise: ", err);
reject(err);
},
newPasswordRequired: function (userAttributes) {
delete userAttributes.email_verified;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
}
})
I'm going to play around with the amazon-cognito-identity-js library a bit and see if I can get ES6 arrow functions to work here. It's really annoying to have to work around that.
Shout out to Apolozeus for the help
Please update the line cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes); into cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this) Basically, this will make sure the callback function within the same object.

Categories