StackExchange API authentication in Google Apps Script - google-apps-script

I'm trying to use the V2.2 of StackExchange API in Google Apps Script.
The problem comes in the last step of the explicit OAuth 2.0 flow, when I try to send the POST request to obtain the access token. I receive a 404 error, but making the same request manually (using postman extension) everything is ok.
To simplify the problem, if I send this POST request with no payload I receive the same 404
var response = UrlFetchApp.fetch("https://stackexchange.com/oauth/access_token", {
method: 'post',
muteHttpExceptions: true
});
Logger.log(response);
while in postman I receive this 400:
{
"error": {
"type": "invalid_request",
"message": "client_id not provided"
}
}
I guess this will be a problem with UrlFetchApp, but does anyone know how to solve it? Thanks!

The problem is related with the Origin header.
You cannot remove from the header directly but you can perform the call via a proxy :)

You need to provide the data for the post by adding an 'option' object to the call.
For example:
var options = { "method" : "post", "payload" : payload };
UrlFetchApp.fetch("https://stackexchange.com/oauth/access_token", options);
Btw, have you tried you use the OAuth that UrlFetch got: https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#addOAuthService(String) - It might be better way.

Related

How to allow Cors Headers in Google AppScript for making an XMLHttpRequest?

I have created a doGet and doPost endpoints in my appscript. When I hit the endpoint to make a post request from Python, it does work perfectly and as expected.
But when I try to hit the same url with my Flutter based mobile App, it throws me an XML error. (Which I suspect is related to CORSING).
When I hit the url with get request, I get the right response, but post request is failing. To ensure that my Post request is properly configured, I have made a post request to public API and it worked like charm.
Is it possible to add headers, where I could enable cors like this:
allowHeaders = {
"Access-Control-Allow-Origin": "*", // Required for CORS support to work
"Access-Control-Allow-Credentials": true, // Required for cookies, authorization headers with HTTPS
"Access-Control-Allow-Headers": "Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale",
"Access-Control-Allow-Methods": "POST, OPTIONS"
}
Here is what my doPost request returns:
ContentService.createTextOutput(JSON.stringify(
{
data: isAuthenticated.data,
error: true,
//request: request,
msg: query.apiKey,
//paramters:request.parameters
})).setMimeType(ContentService.MimeType.JSON)
Here is my python script to get the post response:
requests.post("https://script.google.com/macros/s/AKfycbz7kTROol8u509M_p9pMZ9XRnL-myVjcRQKeb9Etp_OIMPnH640vHf_0Jp2dvvrbto7kOg/exec",
json = requestObject)
And here is my Flutter function:
Future<http.Response> createAlbum() async{
print("Trying to make a post request");
var result = await http.post(Uri.parse('https://script.google.com/macros/s/AKfycbz7kTROol8u509M_p9pMZ9XRnL-myVjcRQKeb9Etp_OIMPnH640vHf_0Jp2dvRIco7kOg/exec'),
headers: {"Content-Type": "application/json"},
body: jsonEncode(<String, dynamic>{
"apiKey":apiKey,
"operationType":"register_user",
"operationData": {
"email": "shivam#yoptima.com",
"otp": 318728
}
}),
);
print("Here is the result: " + result.body);
}
Just to clarify things:
Get Request works for both the platforms.
Post Request works with python for AppScript.
Post Request works for any other public API from flutter.
Post Request doesn't work for Flutter when Hitting AppScript API.
I suspect it to be something to do with CORS. (But not very sure).
Flutter http library makes request via XMLHttpRequest.

Why am I getting an empty response back from UrlFetchApp in Google Apps Script?

I am trying to make a GET request to an external API from a Google Apps Script using UrlFetchApp. When I make this request with Postman or curl, I get back the expected response. However, when I try it with UrlFetchApp, I get back an empty response, {}.
I have tried using Basic Auth and OAuth 2, as well as explicitly setting the oauthScopes property in the manifest as described here.
I have confirmed with the API team that they are indeed sending back a full response when I hit the endpoint, but all I receive is {}. My problem seems similar to this StackOverflow question which went unanswered.
var headers = {
"X-Client-Key": "KEY",
"Authorization": "Bearer TOKEN"
};
var options = {
method: "get",
headers: headers,
}
var response = UrlFetchApp.fetch(ENDPOINT, options);
console.log(JSON.stringify(response)); // returns {}
Do not take what you see in logs at face value. fetch method of the UrlFetchApp service always returns an instance of HTTPResponse which is an object first and foremost. This is what the logs show you (I am assuming you are logging the response because this is the only context I am aware of where {} is displayed).
To extract useful information from the response, use the appropriate methods exposed on HTTPResponse instances, like getResponseCode or getContentText.

Google Chat API: Delete message from bot gives a 401 error

This is the code i'm trying to use for deleting messages posted through a webhook to a chat room (I grab messageID when I create It with this same request but POST method and sending message in a text variable):
var WEBHOOK_URL2 = "https://chat.googleapis.com/v1/spaces/<SPACE>/messages/<MESSAGEID>.<MESSAGEID>?key=<KEY>&token=<TOKEN>%3D";
var options = {
'method' : 'delete',
'muteHttpExceptions' : true,
};
var response = UrlFetchApp.fetch(WEBHOOK_URL2, options);
Logger.log (response);
The response is
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
As commented, creating messages works with those credentials (key / token from chatroom webhook) Maybe i am missing something from the documentation at https://developers.google.com/hangouts/chat/reference/rest/v1/spaces.messages/delete ?
Thank you in advance,
That error usually occurs because the request lacks the access token or that access token wasn't validated. I assume that you are using Apps Script for this task. If my assumption is true, you'll find this reference useful; it summarizes the OAuth 2.0 protocol for Google APIs using JavaScript. For obtaining access tokens, please refer to this other guide for a complete step-by-step. Please, follow those steps on your request routine and don't hesitate to write back if you need more help.

How to turn on email forwarding with Google Script

A brief description of the project: I am looking to toggle the email forwarding option in the settings of one of my gmail accounts through a google script. This will be a function I would like to call every night between certain hours forwarding my mail from main_email#gmail to secondary_email#gmail.
I am having a difficult time finding the easiest way to toggle this through a google script. The simplest solution seems to be described here where they use an HTTP request. However in all honesty I don't completely understand how it all works, much less if it is the simplest way.
https://developers.google.com/gmail/api/v1/reference/users/settings/updateAutoForwarding
The code that I try and run on the gmail account to enable/disable email forwarding is the following:
function updateForwarding() {
var userID = "main_email#gmail.com"
var response = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/" + userID + "/settings/autoForwarding", {
method: 'put',
enabled: true,
emailAddress: "secondary_email#gmail.com",
disposition: "leaveInInbox"
});
Logger.log(response.getContentText());
}
However I get the following error:
Request failed for
https://www.googleapis.com/gmail/v1/users/main_email#gmail.com/settings/autoForwarding
returned code 401. Truncated server response: { "error": { "errors": [
{ "domain": "global", "reason": "required", "message": "Login
Required", "locationType": "header", ... (use muteHttpExceptions
option to examine full response) (line 4, file "Code")
I recognize this is shows I need to provide credentials for making the request, but I don't understand how I would do that. I read on the tutorial (https://developers.google.com/gmail/api/auth/about-auth) I need to authorize my app with gmail and get an API key, so I have gone to the google developers console to create that. However, I have no idea how to authenticate or make the call through a Google script after a few hours of google.
Here are the key and secret I was given:
Is this the easiest solution to toggle gmail forwarding? If so, how do I authenticate my call? If not, what is the easiest solution to being able to toggle my gmail forwarding off/on?
You need to pass oAuth token in header information
function updateForwarding() {
var userID = "main_email#gmail.com";
var header = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
}
var response = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/" + userID + "/settings/autoForwarding", {
method: 'put',
enabled: true,
headers: header,
emailAddress: "secondary_email#gmail.com",
disposition: "leaveInInbox"
});
Logger.log(response.getContentText());
}
As noted in the authorization section of https://developers.google.com/gmail/api/v1/reference/users/settings/updateAutoForwarding, you need to use OAuth with the given scopes to make that call, not just an API key. You appear to have a client id, but you need to plug this into a library to handle the OAuth process for you. The OAuth process will then give you a Bearer token to add to your request (although most OAuth libraries will handle this for you).
It looks like https://github.com/googlesamples/apps-script-oauth2 is the current recommened way to do this if you're using UrlFetchApp (based on https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app).

Google Apps Script UrlFetchApp isn't encoding a JSON array correctly

I've looked everywhere and can't find this issue. I've come over from PeopleSoft to .NET and have only recently began learning JavaScript and I'm attempting to use Google Apps Script to send email notification messages to Slack.
It appears to me that GAS's UrlFetchApp isn't handling an array correctly. Below I didn't include all the Slack API options for clarity. Here how I constructed the payload, where 'attachments' contains the array in question:
var payload =
{
// ...
"username": "Test webhook Bot",
"attachments": [
{
"pretext": "pre-hello1",
"text": "text-world1"
},
{
"pretext": "pre-hello2",
"text": "text-world2"
}
]
// ...
};
var options =
{
"method" : "post",
"payload" : payload,
"contentType":"application/json"
};
var response = UrlFetchApp.fetch(requestURL, options);
When testing I found that the post was occuring but Slack was ignoring the attachments portion of the message. I used the following to examine the outgoing POST:
var response = UrlFetchApp.getRequest(requestURL, options);
And what I found looking at the execution transcript I find that the JSON array in my payload isn't being encoded the way I expected. Before execution, I clearly see the properly formatted array.
[16-01-26 07:26:39:050 MST] UrlFetchApp.getRequest([https://slack.com/api/chat.postMessage?, {method=post, payload={attachments=[{pretext=pre-hello1, text=text-world1}, {pretext=pre-hello2, text=text-world2}], username=Test webhook Bot}, contentType=application/json}]) [0 seconds]
But what is actually sent, in place of the attachments array is: %5BLjava.lang.Object;#37f01fb3
[16-01-26 07:26:39:051 MST] Logger.log([Test:https://slack.com/api/chat.postMessage?attachments=%5BLjava.lang.Object;#37f01fb3&username=Test+webhook+Bot, []]) [0 seconds]
I tried searching this out as much as I could before asking for help, but I'm not sure if I'm either loss. Does anyone know where I may look to find out what I'm missing? Thanks.
To the extent that this information is helpful almost 4 years out, I've been running into the same problem and here's the solution I came up with:
- I will be including all relevant information encoded in the URL JSON structure
- The "options" portion of the UrlFetchApp is then just specifying the method and contentType
An example would look like this:
var url = "https://slack.com/api/chat.postMessage?token=the-token-here&channel=channel_id_here&text=hello%20world";
var options = {
"method": "post",
"contentType": "application/json",
};
return UrlFetchApp.fetch(url,options);
}
I also got some more helpful information at this Stack Overflow thread.
I think this is the Slack API documentation that helps explain the constraints:
JSON-encoded bodies
For these write methods, you may alternatively send your HTTP POST
data as Content-type: application/json.
There are some ground rules:
You must explicitly set the Content-type HTTP header to
application/json. We won't interpret your POST body as such without
it. You must transmit your token as a bearer token in the
Authorization HTTP header. You cannot send your token as part of the
query string or as an attribute in your posted JSON. Do not mix
arguments between query string, URL-encoded POST body, and JSON
attributes. Choose one approach per request. Providing an explicitly
null value for an attribute will result in whichever default behavior
is assigned to it.
Based on a comment I received from a Google Drive Help Forum discussion , I wanted to pass on more information on what I found regarding the use of JSON.stringify() in creating my Slack request. I modified my options JSON
var options = {
'method': 'post',
'payload': JSON.stringify(payload)
};
Google then interprets the 'attachments' array correctly when constructing the request and I no longer see the java.lang.Object error.
Additional lessons learned: prior to using JSON.stringify() Slack would let me post using my personal developer token as part of the payload. Once I began using JSON.stringify() Slack would not accept my personal token nor could I pass a channel parameter. This resulted in me creating a Slack Incoming Webhook direct to the channel I wanted. I haven't tracked down why that would be the case. It may be in Slack's documentation somewhere, I just haven't had time to look yet.