AJAX post to Google Spreadsheet from Node.js/ Express fails - javascript

I've researched a few questions similar to this but none have helped solve my problem...
Posting to a Google Spreadsheet app from a simple Node.js/ Express script on Heroku (not accessed through the browser) doesn't show up any errors but still fails. Can anyone understand why?
I've created a very small app with Node.js and Express. It basically listens for messages from a Slack channel, evaluates and strips parts of the message it needs and then posts them to a Google Spreadsheet to be stored.
I'm using the node modules express for receiving data posted from Slack and ajax-request to handle the AJAX post to Google Spreadsheets. It looks a little something like this:
var express = require('express');
var bodyParser = require('body-parser');
var ajaxRequest = require('ajax-request');
var app = express();
// Other app.stuff
app.post('/test', function (req, res, next) {
var userName = req.body.user_name;
var message = req.body.text || '';
// DOES SOME OTHER STUFF...
postToSpreadsheet(userName, message);
});
function postToSpreadsheet(user, id) {
ajaxRequest({ // Post request
url: 'https://script.google.com/macros/s/abcdefg123456/exec', // Google Spreadsheet app url
method: 'POST',
data: {Id : id, User : user}
}, function(err, res, body) { // Request callback
console.log("ERROR: " + err + " / BODY: " + body);
});
}
I receive no errors from the request callback, however the body of the post contains the HTML from Google's file not found page, which makes me think it's posting to an incorrect url.
I know it's not:
A Google script problem. The Google script works fine and the published app is public and accessible to anyone. When I just enter the url with encoded parameters into my browser e.g. https://script.google.com/macros/s/abcdefg123456/exec?Id=id&User=johnsmith it works and stores the parameters in my Google Spreadsheet.
An data to url params serialisation problem - I've tried just posting to the url above with pre-defined parameters and no data option.
A dependency problem - I've tried to get it working with a few different AJAX node modules.

Related

"redirect_uri_mismatch" when sending authentication code to GoogleAPI

I am having trouble with the authentication process for the GoogleAPI. In the end I want to be able to read the users steps using the GoogleFit API and then store that value in a database. Currently I'm using restdb.io and executing javascript in codehooks.
The documentation from Google that I am following can be found here, clicking on the HTTP/REST option in the code examples. At the moment I am at step 5: I have gotten the users authentication code and stored it in the database. Now I have to POST the code along with some other parameters and get the access and refresh tokens.
If the POST is successful (from what I understand) I should get back a 200-OK message that the request was valid. Google will then POST a JSON body with the access and refresh token to the redirect_uri that I have specified in my GoogleAPI credentials page and the initial request. At redirect_uri I have to handle the request and save the two values.
The problem is that I receive a redirect_uri_mismatch - Bad Request message from Google as a response when executing the request. I get it at the log.debug("ERROR HERE: " + [...]); in the code below:
async function mainFunction(){
const authCode = THIS_IS_MY_AUTHENTICATION_CODE;
try {
var answer = await postRequestToGoogle(authCode);
//do stuff with response from Google
} catch (error) {
//do stuff
}
}
async function postRequestToGoogle(authCode){
//body for the request
const params = "code=" + authCode + "&" +
"client_id=THIS_IS_MY_CLIENT_ID" + "&" +
"client_secret=THIS_IS_MY_CLIENT_SECRET" + "&" +
"redirect_uri=THIS_IS_MY_REDIRECT_URI" + "&" +
"grant_type=authorization_code";
try{
const result = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: params})
.then(res => {
log.debug("ERROR HERE: " + JSON.stringify(res.json()));
return res.json();
})
//return JSON back main function
return result;
}catch(error){
//do stuff
}
}
I looked up the error message and tried some things:
Copy and pasted multiple different Authorized redirect URI from the GoogleAPI credentials page into the code to make sure that there is no problem with
http/https
www/no www
trailing slashes
typos or capitalization
Waited for changes to be processed by Google (read that it can more than 30min)
Changed all the other parameters to see if the redirect_uri is actually the problem
If code is changed the message is invalid_grant - Bad Request
If client_id is changed the message is invalid_client - The OAuth client was not found
If client_secret is changed the message is invalid_client - Unauthorized
If the grant_type is changed the message is unsupported_grant_type - Invalid grant_type
That's why I think the issue is the redirect_uri, but it's unclear to me how since I copy&pasted it. Something that came to mind was that maybe the value of redirect_uri gets changed when it's read by Google? Or maybe when the request is being put together? Do some characters have to be replaced?
I tried to analyze the request with Wireshark but didn't think about the fact that it's HTTPS so I would have I would have to decrypt it.. Is that something I should look into?
Thank you for taking the time to read all of this! If you have any advice please let me know :)
Update 16.11.20:
I have created a new OAuth 2.0 Client ID and used the new id/secret in my request. The resulting message the same as before. I will wait and try again tomorrow to see if maybe Google needs some more time. Then I'll try to delete all current IDs and start with a fresh GoogleAPI project.
Update 19.11.20:
Creating a new OAuth 2.0 Client ID did not resolve my problem, neither did creating a whole new GoogleAPI project and adding those credentials into the request. I am in contact with the developers of restdb.io and have asked them to add the Google Auth Library: Node.js Client to the list of supported Node.js packages. Hopefully that will help, I will give it a try as soon as it can be used :)
Update 02.12.20:
No progress so far, but I'm optimistic that the developers will add the package soon. I will post a final update as soon as I am done with this project.

documentPath is not a valid resource path when deployed to Google App Engine with javascript

Description:
I have a frontend React client that is hosted on Firebase Hosting and a NodeJS Express API that is hosted on Google App Engine. The client needs to send a POST request to a NodeJS Express route, the request need to contain a variable called formid that holds the name of a firebase document. When both the server and client is run locally the formid variable gets sent to the API and it is not empty or undefined. But when the API is deployed and the request is sent to GAE instead I get this error:
UnhandledPromiseRejectionWarning: Error: Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.
The error appears in the Google Cloud Platform Console. In left hand menu I go to; Operations > Logging > Logs Viewer. But I can also tail the logs to my local console with the command "gcloud app logs tail -s default".
Question: Why does the error appear only when the request is sent to GAE and not when I run them locally?
request.headers.form_id contains the name of the document stored in a collection (which is an autoID) in Google Firestore.
async function postFormQuestionHandler(request, response) {
let form_ref = db.collection("forms").doc(request.headers.form_id);
... other code
Express router that recieves the request, forwards it to postFormQuestionHandler function.
router.post("/question", (req, res) => {
postFormQuestionHandler(req, res);
});
Here is where the request firstly is being sent:
async addQuestionsToFormDb(idToken, fomrid, questionids) {
let result = await questionids.map(async (questionid) => {
let data = {
idToken: idToken,
form_id: formid,
question_id: questionid,
};
return await fetch(`${process.env.REACT_APP_API}/form/question`, {
method: 'POST',
mode: 'cors',
headers: data
});
}
From above code I have also tried using the Axios library but it also produces the same error on when the API is deployed to GAE. I have tried reading the documentation of both Axios (Github Axios Documentation) and Fetch (Firefox MDN web docs) but it have not helped me, am I missunderstanding something? Below is what I tried with Axios:
return await axios.post(`${process.env.REACT_APP_API}/form/question`, {}, {
headers: data,
}
);
Other information
Im using Express v4.17.1 and Node v10.19.0, I am also developing this with WSL 2 on Windows 10 2004.
your request.headers.form_id is either empty string or contain invalid character. I guess it contains slash /. Here is more info https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields

Neither Node.js PUT or POST routes are presenting the data that is being received

I tried this with a number of different modules and methods.
Even by proving the other modules do work as expected in Node by building a separate test project and testing each module individually.
Posting FROM Node's hosted router to a remote API (not TO Node's hosted API)
This problem is not one of SENDING data to an API. It must IMO a problem in the receiving API's not giving up the data it IS receiving for some reason.
I've proven the PUT or POST calls are sending the data by sending the call to http://httpbin/org. That site shows me I'm sending what I expect to be sending.
Here is how I'm sending. I can even see in the receiving API that that API is certainly getting called successfully.
-- sending -- ((Again. This shows my node.http attempt. But I get the same problem using requestjs, requestifyjs, needlejs))
router.get('/', function (req, res, next) {
var hst = req.headers.host.split(':');
var lookbackURL = 'https://' + req.headers.host + req.baseUrl;
lookbackURL = 'http"httpbin.org/put';
var dat = {
what: 'ever'
, try: 'again'
};
var bdy = JSON.stringify(dat);
var options = {
host: hst[0], port: hst[1], path: req.baseUrl, method: 'PUT'
, headers: { 'Content-Type': 'application/json' }
};
var r = nodeHttp.request(options); r.write(bdy); r.end();
res.sendStatus(200);
});
-- receiving --
router.put('/', function (req, res, next) {
console.log('r', req);
});
No matter what module or method I use, in all cases, the receiving req object doesn't contain the what or try data.
BUT in my test project the data is there as I expect it to be, in all cases.
Doing the same console.log(req); in the test project, reqestjs, requestjs, needlejs, node.http all show a proper body object.
But in this problem there isn't a body object in req.
And sending this put/post to http://httpbin.org I can see the body object is being sent.
Any ideas?
Issue found. And it was something no one on here could have gotten for the code I posted.
For reasons I will not go into I have to take body-parser out this application. This also means app.use() won't have a parser given to it.
And that means I have to deal with getting the data on my own. So I've added a req.on('data') listener to read the chunk from the call to the API.
router.put('/', function (req, res, next) {
var data = '';
req.on('data', function (chunk) {
data += chunk;
.....
});
.....
I also decided to do this as a PUT using Requestify.
This just goes to show how easy it is to become complacent and forget how things really work; the assumption of body-parser (or other things) always being there for instance. Or what it is really doing for you.
NEXT I have to figure out how to get a value out of the `req.on('data) back to the method PUTting to the API. Any tips? Appreciated.

How to handle a get request with node.js (express)

I'm a node.js newbie and I'm creating my first big app with it (I'm using express). I need to have my webpage perform some javascript canvas-drawing when the user loads an id with a get request, e.g.
www.mywebsite.com/page?id=22
I know I can handle this with a simple
var express = require("express");
var app = express();
app.get('handle',function(request,response){
//request.id
});
but I don't know how to start my webpage with the asked drawing for that id. Every tutorial on the internet on express and get explains how to handle get requests... well this question is about "what happens next?"
Rephrased: I'm not sure how should I tell the html page "you need to draw what is associated with this id" from express and then send that page back to the user.
You can take the id from params and after this to return a response based on that id.
var express = require('express');
var app = express();
app.get("/page/:id",function(request, response){
var id = request.params.id;
// do something with id
// send a response to user based on id
var obj = { id : id, Content : "content " +id };
response.writeHead(200, {"Content-Type": "application/json"});
response.write(JSON.stringify(obj));
});
Notes:
You use /page/:id to make urls like www.mywebsite.com/page/22 or www.mywebsite.com/page?id=22 and you can have acces to id on server with request.params.id (output: 22).
With response you can write a response to the server. In this example i returned a json object.
In writeHead 200 come from status which means OK , and content-type means that I return a json object
You can return what you want, a page or something else, this is just an example (PoC).
If you want to pass multiple variables in the request, you can pass it in the following way:
var emailVar = "someEmail#gmail.com";
var nameVar = "someName";
var url = `/home?email=${emailVar}&name=${nameVar}`;
//Now make the request.
and in the backend logic, you can retrieve these values as:
app.get('/home', function(request, response)
{
console.log(request.query.email+" "+request.query.name);
var email = request.query.email;
var name = request.query.name;
response.setHeader('Content-Type', 'application/json');
if(request.query.email)
{
response.send(JSON.stringify({
message: 'Got the email'
}));
}
else
{
response.send(JSON.stringify({
message: 'No email sent'
}));
}
});
This approach is useful for performing query operations in the backend.

Making a POST request to external API with multipart form data in Express

I am developing an app for a course and I am having trouble getting photo upload to work. I didn't have much trouble with other endpoints, but this particular one has me stuck for a week now. Below I have copied the part I have issue with from the iNaturalist API reference.
POST /observation_photos
Auth required.
Add photos to observations. This is only for iNat-hosted photos. For adding photos hosted on other services, see POST /observations and PUT /observations/:id.
Params
observation_photo[observation_id]
ID of the observation receiving this photo.
Allowed values: Valid iNat observation ID
file
The photo data.
Allowed values: Multipart photo data
On the front end, I have this form. I made sure I only uploading .png images.
form(role='form', method='post', enctype='multipart/form-data')
div.form-group
input.form-control#image(type='file', accept='png', name='file')
button.btn.btn-default(type='submit') Submit
Here is an excerpt of my code in the attempt to do a POST request following the API instructions.
var express = require('express');
var router = express.Router();
var Client = require('node-rest-client').Client;
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
router.post('/playground', multipartMiddleware, function(req,res){
client = new Client();
var args = {
parameters:{'observation_photo[observation_id]': '1055033', //id for a record
'file' : req.files.file},
headers:{'Authorization': 'Bearer ' + token}
}
client.post('https://inaturalist.org/observation_photos',args, function(data,response){
console.log(response.body);
res.send(data);
});
}
I guess the confusion I have is the file parameter and what form the POST endpoint is expecting for that parameter. Any help will be appreciated.

Categories

Resources