Google Cloud Function - SendGrid - 400 Bad Request - node.js

I'm trying to send an email using SendGrid in my Google Cloud Function. I have my key in the Firebase environment variables, so that's present.
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
Here is my GCF .ts code:
Code
const msg = {
to: to,
from: from,
subject: 'Email Subject',
content: [{
type: "text/html",
value: body
}]
};
await sgMail.send(msg)
.then(r => {
console.log(r);
});
When I trigger the function and check my logs, this is the error I get:
Error
error: { Error: Bad Request
at ResponseError (node_modules/#sendgrid/mail/node_modules/#sendgrid/helpers/classes/response-error.js:19:5)
at Request.http [as _callback] (node_modules/#sendgrid/mail/node_modules/#sendgrid/client/src/classes/client.js:124:25)
at Request.self.callback (node_modules/#sendgrid/mail/node_modules/request/request.js:185:22)
at emitTwo (events.js:106:13)
at Request.emit (events.js:191:7)
at Request.<anonymous> (node_modules/#sendgrid/mail/node_modules/request/request.js:1161:10)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at IncomingMessage.<anonymous> (node_modules/#sendgrid/mail/node_modules/request/request.js:1083:12)
at IncomingMessage.g (events.js:292:16)
code: 400,
message: 'Bad Request',
response:
{ headers:
{ server: 'nginx',
date: 'Sun, 16 Dec 2018 16:27:56 GMT',
'content-type': 'application/json',
'content-length': '402',
connection: 'close',
'access-control-allow-origin': 'https://sendgrid.api-docs.io',
'access-control-allow-methods': 'POST',
'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl',
'access-control-max-age': '600',
'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html' },
body: { errors: [Object] } } }
Does anyone have any familiarity with this error? It mentions CORS, but that makes no sense, because it's a cloud function, not browser. The SendGrid API isn't that great, it mostly goes over the field names and provides no examples. Thanks for any help provided!
EDIT
Just to update the question, in the front end response I send myself I found a different error than the console.log(error) in the GCF logs:
"Email failed: Bad Request (400)
The from object must be provided for every email send.
It is an object that requires the email parameter,
but may also contain a name
parameter. e.g. {"email" : "example#example.com"} or
{"email" : "example#example.com", "name" : "Example Recipient"}.
from.email
http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.from"
EDIT 2: SOLUTION
My from email source comes from a document in Firestore, and I was trying to get a field that didn't exist because I was querying the wrong document, therefore from was undefined. Corrected it, as well as removed "content" in the msg object per the suggestion / answer below, and everything works fine.

According to the documentation ...the msg has the wrong elements:
const sg = require('#sendgrid/mail');
sg.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'test#example.com',
from: 'test#example.com',
subject: 'Sending with SendGrid is Fun',
text: 'and easy to do anywhere, even with Node.js',
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
};
sg.send(msg).then(() => {
/* assume success */
})
.catch(error => {
/* log friendly error */
console.error(error.toString());
/* extract error message */
const {message, code, response} = error;
/* extract response message */
const {headers, body} = response;
});

Related

Sign in with apple REST api keep getting [Request failed with status code 400] error

I am implementing apple sign in on my website
On my backend(Nodejs), I need to request an authentication token using https://appleid.apple.com/auth/token REST api.
I used Axios and coded as following
export const createSignWithAppleSecret = () => {
const token = jwt.sign({}, signWithApplePrivateKey, {
algorithm: 'ES256',
expiresIn: '1h',
audience: APPLE_DOMAIN,
issuer: APPLE_TEAM_ID,
subject: APPLE_SERVICE_ID,
keyid: APPLE_KEY_ID,
});
return token;
};
export const getAppleToken = async (code: string) =>
axios.post(
'https://appleid.apple.com/auth/token',
qs.stringify({
grant_type: 'authorization_code',
code,
client_secret: createSignWithAppleSecret(),
client_id: APPLE_SERVICE_ID,
redirect_uri: APPLE_REDIRECT_URI,
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
But I am getting Reqest failed with status code 400
Error: Request failed with status code 400\n at createError (/home/ubuntu/sooldamhwa/www/node_modules/axios/lib/core/createError.js:16:15)\n at settle (/home/ubuntu/sooldamhwa/www/node_modules/axios/lib/core/settle.js:17:12)\n at IncomingMessage.handleStreamEnd (/home/ubuntu/sooldamhwa/www/node_modules/axios/lib/adapters/http.js:260:11)\n at IncomingMessage.emit (events.js:327:22)\n at IncomingMessage.EventEmitter.emit (domain.js:485:12)\n at endReadableNT (_stream_readable.js:1201:12)\n at processTicksAndRejections (internal/process/task_queues.js:84:21)
The api endpoint is correct, and I have configured header as document instructed( https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens)
Could someone please let me know what I did wrong?

Use google calendar API and Getting 401 error message with Axios get

When developing the Alexa skill. I'm trying to use Axios get to get the whole day's events information from google calendar api. When I redo the account linking, it works well. I thought the asscessToken could only last for 2 hours, and I get the 401 error message because the token is expired.
Here is my Axios code
const getEvents = async (url, accessToken) => {
try {
const config = {
headers: {'Authorization': 'Bearer ' + accessToken}
};
return await Axios.get(url, config);
} catch (error) {
console.log('Error getting events');
console.error(error);
}
}
Code for my url and the asscesstoken
const url = EVENTS_URL + "?orderBy=updated&timeMin=" + timeMin + "&timeMax="
+ timeMax;
const accessToken =
handlerInput.requestEnvelope.context.System.user.accessToken;
Error Message:
ERROR { Error: Request failed with status code 401
at createError (/var/task/node_modules/axios/lib/core/createError.js:16:15)
at settle (/var/task/node_modules/axios/lib/core/settle.js:17:12)
at IncomingMessage.handleStreamEnd (/var/task/node_modules/axios/lib/adapters/http.js:260:11)
at IncomingMessage.emit (events.js:203:15)
at IncomingMessage.EventEmitter.emit (domain.js:448:20)
at endReadableNT (_stream_readable.js:1145:12)
at process._tickCallback (internal/process/next_tick.js:63:19)
method: 'get',
headers:
{ Accept: 'application/json, text/plain, */*',
Authorization: 'Bearer undefined',
'User-Agent': 'axios/0.21.1' },

Securing Cloud Functions using IAM + NodeJS application running on AppEngine

I need some help with example if possible of code changes to be done on my NodeJS server code running on AppEngine in order to allow it to securely access my cloud fucntions.
I created a cloud function and I have a public URL for it. I then went ahead and removed allUser access in the Permissions' tab of the function. Under Service account I have App Engine Default Service account` selected.
My server on AppEngine was calling the public URL of the CF and when `allUser' permission was there, everything was hunky dory. But when it was removed, I started getting 403 error.
I need help with code changes on NodeJS side to be able to invoke the cloud fucntion again please.
My CF and App engine are in the same project and in the same region.
My server code is as follows using an https library to make the post request.
const checkingData = JSON.stringify({
'check' : 123
})
const checkingOptions = {
hostname: MY_CLOUD_PUBLIC_URL,
port: 443,
timeout: 5000,
path: MY_CLOUD_URL_PATH,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': checkingData.length,
}
}
const checkRequest = https.request(checkingOptions, checkD => {
if (checkD.statusCode==200) { // OK?
checkD.on('data', d => {
// Do something useful with the data
})
})
checkRequest.write(checkingData);
checkRequest.end();
I have been trying to find an answer for this for days now and started having migranes because of this. Any help with example code would be much appreciated. Many thanks!
EDIT
I can see the token now! (Yayyy!)
I changed my checkingOptions to work with request-promise in the following way:
const checkingOptions = {
uri: 'https://'+process.env.CLOUD_URL+process.env.CHECK,
port: 443,
timeout: 5000,
body:checkingData,
json: true,
// path: process.env.CHECK,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': checkingData.length,
}
}
I am getting the following errors:
Unhandled rejection StatusCodeError: 401 - "\n<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<title>401 Unauthorized</title>\n</head>\n<body text=#000000 bgcolor=#ffffff>\n<h1>Error: Unauthorized</h1>\n<h2>Your client does not have permission to the requested URL <code>/check</code>.</h2>\n<h2></h2>\n</body></html>\n"
2021-02-27 19:38:01 default[20210227t192944] at new StatusCodeError (/workspace/node_modules/request-promise-core/lib/errors.js:32:15)
2021-02-27 19:38:01 default[20210227t192944] at Request.plumbing.callback (/workspace/node_modules/request-promise-core/lib/plumbing.js:104:33)
2021-02-27 19:38:01 default[20210227t192944] at Request.RP$callback [as _callback] (/workspace/node_modules/request-promise-core/lib/plumbing.js:46:31)
2021-02-27 19:38:01 default[20210227t192944] at Request.self.callback (/workspace/node_modules/request/request.js:185:22)
2021-02-27 19:38:01 default[20210227t192944] at Request.emit (events.js:314:20)
2021-02-27 19:38:01 default[20210227t192944] at Request.<anonymous> (/workspace/node_modules/request/request.js:1154:10)
2021-02-27 19:38:01 default[20210227t192944] at Request.emit (events.js:314:20)
2021-02-27 19:38:01 default[20210227t192944] at IncomingMessage.<anonymous> (/workspace/node_modules/request/request.js:1076:12)
2021-02-27 19:38:01 default[20210227t192944] at IncomingMessage.emit (events.js:326:22)
2021-02-27 19:38:01 default[20210227t192944] at endReadableNT (_stream_readable.js:1241:12)
Do I need a middleware in my cloud function as well? My cloud function looks like the follow:
exports.check = functions.https.onRequest((req, res) => {
console.log('----CHECKING----');
res.status(200).send('Hello from checking');
})
IAM ROLES:
CLOUD FUNCTION > PERMISSIONS TAB
You have a great example in the documentation. If you want I adapt it to your code, it could look like to this
// Make sure to `npm install --save request-promise` or add the dependency to your package.json
const request = require('request-promise');
// Set up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
uri: metadataServerTokenURL + MY_CLOUD_PUBLIC_URL,
headers: {
'Metadata-Flavor': 'Google'
}
};
const checkingData = JSON.stringify({
'check' : 123
})
const checkingOptions = {
uri: MY_CLOUD_PUBLIC_URL, //!!!changed here!!!
port: 443,
timeout: 5000,
path: MY_CLOUD_URL_PATH,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': checkingData.length,
}
}
request(tokenRequestOptions).then((token) => {
request(checkingOptions).auth(null, null, true, token).then((response) => { //!!!changed here!!!
checkD.on('data', d => {
// Do something useful with the data
})
})
.catch((error) => {
res.status(400).send(error);
}); => {
})
checkRequest.write(checkingData);
checkRequest.end();
Don't forget to add the role role/cloudfunctions.invoker to the App Engine default service account, either at the project level or at the Cloud Functions level.
EDIT 1
Correct. request-promise has been deprecated for year. What alternative? I absolutely don't know because NodeJS hurts me (as I said in the comment).
As a lazy man, I found an alternative. I found the request-promise in the Cloud Run documentation. But you have to know that Cloud Run and Cloud Functions are very closed (they share the same underlying platform). I take my chance with Cloud Functions authentication documentation and bingo! There is an example with node-fetch
const fetch = require('node-fetch');
const MY_CLOUD_PUBLIC_URL = '....'
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
// Fetch the token
const tokenResponse = await fetch(metadataServerTokenURL + MY_CLOUD_PUBLIC_URL, {
headers: {
'Metadata-Flavor': 'Google',
},
});
const token = await tokenResponse.text();
console.log(token)
const checkingData = JSON.stringify({
'check' : 123
})
// Provide the token in the request to the receiving function
try {
const functionResponse = await fetch(MY_CLOUD_PUBLIC_URL, {
method: 'post',
body: checkingData,
headers: {
'Content-Type': 'application/json',
Authorization: `bearer ${token}`},
});
console.log(await functionResponse.text());
} catch (err) {
console.error(err);
}
Add your check functions and it should work now!

fetch. Stripe integration works on a localhost and stop working after uploaded to a firebase

Try ti integrate Stripe using firebase platform. On a lolalhost (firebase emulators) test payment using cards works. After uploading to a server, got 500
As an example use this code (client on a web, server using node.js)
https://github.com/stripe-samples/accept-a-card-payment/tree/master/using-webhooks
Client:
var orderData = {
items: [{ id: "photo-subscription" }],
currency: "usd"
};
fetch("/create-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(orderData)
})
.then(function(result) {
console.log(result);
return result.json();
})
.then(function(data) {
console.log(data);
return setupElements(data);
})
.then(function({ stripe, card, clientSecret }) {
// Handle form submission.
var form = document.getElementById("payment-form");
form.addEventListener("submit", function(event) {
event.preventDefault();
// Initiate payment when the submit button is clicked
pay(stripe, card, clientSecret);
});
});
Server (Cloud Functions)
app.post("/create-payment-intent", async (req, res) => {
const { items, currency } = req.body;
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount: 1400,
currency: currency,
payment_method_types: ['card'],
});
// Send publishable key and PaymentIntent details to client
res.send({
publishableKey: 'pk_test_...',
clientSecret: paymentIntent.client_secret
});
});
Response on a localhost:
Response {type: "basic", url: "http://localhost:5000/jobs/create-payment-intent", redirected: false, status: 200, ok: true, …}
{publishableKey: "pk_test_...", clientSecret: "pi_1Fij2FH..."}
Response on a server:
POST https://pd...b.firebaseapp.com/create-payment-intent 500
Response {type: "basic", url: "https://pd...b.firebaseapp.com/create-payment-intent", redirected: false, status: 500, ok: false, …}
Uncaught (in promise) SyntaxError: Unexpected token E in JSON at position 0
Cloud functions log:
Error: An error occurred with our connection to Stripe.
at /srv/functions/node_modules/stripe/lib/StripeResource.js:212:9
at ClientRequest.req.on
(/srv/functions/node_modules/stripe/lib/StripeResource.js:467:67)
at ClientRequest.emit (events.js:189:13)
at ClientRequest.EventEmitter.emit (domain.js:441:20)
at TLSSocket.socketErrorListener (_http_client.js:392:9)
at TLSSocket.emit (events.js:189:13)
at TLSSocket.EventEmitter.emit (domain.js:441:20)
at emitErrorNT (internal/streams/destroy.js:82:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
at process._tickCallback (internal/process/next_tick.js:63:19)
Function execution took 346 ms, finished with status: 'crash'
This is late, but I believe you have to upgrade to Firebase Blaze plan. Firebase does not allow API requests to non-Google services on the free plan.

CouchDB throws '404 missing' error when inserting a user using nano

It seems CouchDB will randomly throw an error when trying to insert a user.
Here is the full response from CouchDB:
{ [Error: missing]
name: 'Error',
scope: 'couch',
status_code: 404,
'status-code': 404,
request:
{ method: 'POST',
headers:
{ 'content-type': 'application/json',
accept: 'application/json',
'X-CouchDB-WWW-Authenticate': 'Cookie',
cookie: [Object] },
uri: 'http://127.0.0.1:5984/_users',
body: '{"_id":"org.couchdb.user:fake_user","name":"fake_user","realname":"fake user","institution":"366a8e5ba861bdd9cad5cd318a002ee4","email":"fake_user#gmail.com","phone":"123-456-7890","type":"user","roles":[],"password":"123456","level":"user","unconfirmed":"true"}',
jar: false },
headers:
{ date: 'Tue, 21 Jan 2014 20:32:23 GMT',
'content-type': 'application/json',
'cache-control': 'must-revalidate',
'status-code': 404,
uri: 'http://127.0.0.1:5984/_users' },
errid: 'non_200',
error: 'not_found',
reason: 'missing',
description: 'missing',
stacktrace:
[ 'Error: missing',
' at Request._callback (/apps/arcapp/node_modules/nano/nano.js:304:39)',
' at Request.self.callback (/apps/arcapp/node_modules/nano/node_modules/request/request.js:129:22)',
' at Request.EventEmitter.emit (events.js:98:17)',
' at Request.<anonymous> (/apps/arcapp/node_modules/nano/node_modules/request/request.js:873:14)',
' at Request.EventEmitter.emit (events.js:117:20)',
' at IncomingMessage.<anonymous> (/apps/arcapp/node_modules/nano/node_modules/request/request.js:824:12)',
' at IncomingMessage.EventEmitter.emit (events.js:117:20)',
' at _stream_readable.js:872:14',
' at process._tickCallback (node.js:415:13)' ] }
This is obviously not very insightful. All I get is error: not_found, reason: missing, which doesn't tell me much. It only happens sometimes and I can't really tell what's triggering it. Any insight into this is much appreciated.
Here is the relevant code. I'm using nano, but it's just a basic post request to CouchDB.
var user = {
"_id": "org.couchdb.user:" + username,
"name": username,
"realname": first + " " + last,
"institution": institution,
"email": email,
"phone": phone,
"type": "user",
"roles": [],
"password": password,
"level": "user",
"unconfirmed": "true",
"verificationCode": verificationCode
};
nano.request({
db: '_users',
method: 'POST',
body: user
}, function(err, body) {
if(err) {
console.log("ERROR INSERTING USER");
console.log(err);
res.send(err.status_code + " " + err.reason, err.status_code);
return;
}
...
The problem here was that the cookie I was using to validate a user was not being properly unset after logout. So if you logged out and immediately tried to register, the database would authenticate the request as the logged in user. Since only db administrators are allowed access to the _users db, CouchDB would throw a '404 missing' error.
I was setting nano.config.cookie on login, so when I unset it on logout, it worked!
This error rings a bell but unfortunately I'm not 100% how/if I fixed it.
One thing I did notice you are including "roles:[]" I dont think this is allowed unless your post as an admin.
I haven't enough rep to comment but can you send the code that's making the request ?

Resources