Got 401 response when calling my own Google Apps Script - google-apps-script

I wrote a test web app using apps script. I deployed it to execute as "User accessing the web app" and set access to "Anyone who has a Google account". I then tried to call the web app function from another script which acts as the client. I did all of this while logged into my google account, both web app and client are in the same browser window (in different tabs). But I always get a 401 error. I then tried adding some oauthScopes to the appsscript.json file for the client script, but this didn't help. Any help solving this would be appreciated.
Web app code:
function doPost(e) {
if(typeof e !== 'undefined') {
let par = e.parameter;
let retval = par.x*3;
let retobj = {"retval":retval};
return ContentService.createTextOutput(JSON.stringify(retobj));
}
}
Client code:
function callWebApp(x) {
Logger.log("starting call webapp");
var payload = {
"info" : "some text",
"x" : 3,
"type" : "post",
};
var options = {
"method" : "POST",
"payload" : payload,
"followRedirects" : true,
"muteHttpExceptions": true
};
var result = UrlFetchApp.fetch(urlString, options);
Logger.log(result.getResponseCode());
if (result.getResponseCode() == 200) {
Logger.log('got response');
var params = JSON.parse(result.getContentText());
Logger.log(params.retval);
}
return 1;
}
appscript.json for client:
{
"timeZone": "America/New_York",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/script.deployments",
"https://www.googleapis.com/auth/script.deployments.readonly",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/script.processes",
"https://www.googleapis.com/auth/script.projects.readonly"
]
}
Execution log:
7:47:30 PM Notice Execution started
7:47:30 PM Info starting call webapp
7:47:30 PM Info 401.0
7:47:30 PM Notice Execution completed

Modification points:
When you deployed the Web Apps as Execute as: User accessing the web app and Who has access: Anyone with Google account and you want to access to the Web Apps using the script (in this case, it's Google Apps Script.), it is required to request to the Web Apps by including the access token in the request header.
And, when the access token is used for requesting to the Web Apps, please include the scope of https://www.googleapis.com/auth/drive.readonly and/or https://www.googleapis.com/auth/drive to your current scopes in your client.
In your script, it seems that urlString is not declared. Although I'm not sure whether this might be declared elsewhere, please confirm it again.
When these points are reflected to your script, it becomes as follows.
Modified script:
From:
var options = {
"method" : "POST",
"payload" : payload,
"followRedirects" : true,
"muteHttpExceptions": true
};
To:
var options = {
"method": "POST",
"payload": payload,
"muteHttpExceptions": true,
"headers": {authorization: "Bearer " + ScriptApp.getOAuthToken()}
};
Note:
If you want to make users request to your Web Apps using a script, it is also required to request to the Web Apps by including the access token in the request header. And also, it is required to share the Google Apps Script project with the user. Please be careful this.
If you cannot retrieve your expected result when above script is run, please redeploy the Web Apps as new version and test it again.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Related

Adding a doPost and doGet into google app script ("GAS") code

I am a total newbie when it comes to programming.
I have put a very simple switchbot script into google app script ("GAS") to make the switchbot bot do a press. While it can run when clicking on the "run" button in GAS, when sending a http post (i.e. via android's HTTP Shortcut app) to GAS, it connects but the action fails.
I do understand later that a doPost or doGet is required to run it when sending a post to the script from an external source, but after trying various methods with doPost and doGet, I still have no idea how to integrate it or where to put it into the code.
The code is below:
function main() {
var headers = {
"Authorization" : "SWITCHBOT_TOKEN_KEY",
"Content-type" : "application/json; charset=utf-8"
};
var data = {
"command" : "press",
"parameter" : "default",
"commandType": "command"
};
var options = {
'method' : 'post',
"headers" : headers,
muteHttpExceptions : true,
"payload" : JSON.stringify(data)
};
var deviceid = "INSERT_SWITCHBOT_DEVICEID";
var url1 = https://api.switch-bot.com/v1.0/devices/${deviceid}/commands;
var response = UrlFetchApp.fetch( url1, options );
var json = JSON.parse( response.getContentText() );
console.log( json )
}
Any assistance or lesson on how to do / understand this would be great!
Tried checking and looking at various codes with dePost and doGet and integrating it into the code but all seems not work.
While it connects to GAS via the deployed web app link, I am not able to get the actual script running. It simply logs it as failed in the GAS.

Update gs files from script using API

Can you please tell me if it is possible to change the contents of the gs files of the same project from the google app script. This is required to receive code updates.
I am looking for opportunities to use the API, similar to how google clasp does it.
How realistic is it to do this?
[UPDATE]
Try this code:
var scriptID = ScriptApp.getScriptId();
var url = 'https://script.googleapis.com/v1/projects/' + scriptID + '/content';
var token = ScriptApp.getOAuthToken();
var options = {
'method' : 'get',
'headers' : {'Authorization':'Bearer '+ token},
'muteHttpExceptions' : true
};
var response = UrlFetchApp.fetch(url, options);
var json = JSON.parse(response.getContentText());
Logger.log(json);
In Logger:
{error={details=[{domain=googleapis.com, #type=type.googleapis.com/google.rpc.ErrorInfo, reason=ACCESS_TOKEN_SCOPE_INSUFFICIENT, metadata={service=script.googleapis.com, method=google.apps.script.management.v1.ProjectsService.GetContent}}], message=Request had insufficient authentication scopes., code=403.0, status=PERMISSION_DENIED}}
As Tanaike mentioned, you can in fact edit gs files from Google Apps Script using the Google Apps Script API.
Actually Google Clasp does use the Google Apps Script API to make the exact same type of changes.
You can take the following request as an example using the method projects.updateContent to make a simple change in the source parameter. In addition to that, you need to make sure that the Google Apps Script API is enabled from your Apps Script Settings, and not only add the gs file you want to send in the request, but also the appsscript.json file from the manifest is required in the request.
{
"files": [
{
"name": "appsscript",
"type": "JSON",
"source": "{\n \"timeZone\": \"America/El_Salvador\",\n \"dependencies\": {},\n \"exceptionLogging\": \"STACKDRIVER\",\n \"runtimeVersion\": \"V8\"\n}"
},
{
"name": "Code",
"type": "SERVER_JS",
"source": "function myFunction() {\n Logger.log(\"This is a test\");\n}\n",
"functionSet": {
"values": [
{
"name": "myFunction"
}
]
}
}
]
}
References:
Method: projects.updateContent
Clasp

Fetch JSON object from GAS WebApp from another development GAS WebApp?

I have two different GAS projects Script1 and Script2.
Script1:
It is a development project with doPost() function. It uses the e.parameter or e.postData.contents to do something.
Script2:
It is a test script. It has also doPost() function. I want to transfer the doPost() e.parameter to Script1 by a post request. But the URLFetchApp success when I use the Current web app URL and ends in /exec. But I want to use the latest code and ends in /dev. Because of the Script1 is a development project and I can't update its version for a small change.
I tried this code. It not working
function myFunction() {
//var URL = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxx/exec";
var URL = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/dev";
var data = {
'message' : "This is working"
}
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data)
};
var response = UrlFetchApp.fetch(URL, options);
}
I believe your goal as follows.
You want to access to Web Apps with the dev mode using Google Apps Script.
For this, How about this answer?
Modification points:
In order to access to the Web Apps with the dev mode, please use the access token. And in this sample, the scope of https://www.googleapis.com/auth/drive.readonly is used for the access token.
Modified script:
When your script is modified, please modify as follows.
function myFunction() {
//var URL = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxx/exec";
var URL = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/dev";
var data = {
'message' : "This is working"
}
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data),
'headers': {'authorization': 'Bearer ' + ScriptApp.getOAuthToken()} // Added
};
var response = UrlFetchApp.fetch(URL, options);
}
// DriveApp.getFiles() // Added
Note:
The comment line of // DriveApp.getFiles() is used for automatically detecting the scope of https://www.googleapis.com/auth/drive.readonly by the script editor.
When the access token is used, even when Who has access to the app: is Only myself, the script works.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

getOAuthToken returns null when called from Google Sheets

I'm working on a way to limits some Google Sheets for specific users, with the oAuth specification and AWS API Gateway calls, but i'm facing a problem with the ScriptApp.getOAuthToken() function.
When i'm running the code with the Google Apps Script debugger, everything's fine, ScriptApp.getOAuthToken() returns me a token i can pass to my AWS API. The expected result for now is just to recieve the username.
But if i try to use my function as a macro in a Google Sheets cell, i have the following error Header:null (line 13)
Here is the code in the Code.gs file
function HelloW() {
var token = ScriptApp.getOAuthToken();
var headers = {
'Authorization' : token
}
var options = {
'headers' : headers,
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data)
};
var response = UrlFetchApp.fetch('https://###/demo-lambda', options);
var txt = response.getContentText();
var json = JSON.parse(txt);
var name = json.Message;
return name;
}
And the manifest just in case
{
"timeZone": "Europe/Paris",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive"],
"sheets": {
"macros": [{
"menuName": "HelloW",
"functionName": "HelloW"
}]
}
}
I've got an error because token is null, but i don't understand why it runs well with the debugger, and it doesn't in the Sheets document. I'm missing something and i don't find what.
Any help would be much appreciated.
You cannot make calls inside macros that require user authorization.
Unlike most other types of Apps Scripts, custom functions never ask
users to authorize access to personal data. Consequently, they can only call services that do not have access to personal data.
Source

Google Apps Standalone Script to use Google Apps Script API to update many bound scripts

I am trying to write a standalone Google Apps script that uses the Google Apps Script API to update the the bound script contents of many Google Sheets.
I have the Sheet IDs of roughly 200 Google Sheets that I've created from a template. I would like to update the project contents of the bound scripts on each of these sheets to be identical to a set of master scripts.
I am stuck with an authentication error while using the urlFetchApp to get the contents of the bound script of one sheet as a test. The error looks like:
Request failed for
https://script.googleapis.com/v1/projects/<SCRIPTID>/content returned code 401.
Truncated server response: { "error": { "code": 401,
"message": "Request is missing required authentication credential.
Expected OAuth 2 access token, login cookie ...
(use muteHttpExceptions option to examine full response) (line 34, file "AddScriptsToSheets")
The test function I'm using looks like:
function getSheetScriptContent(sheetId) {
var sheet = SpreadsheetApp.openById(sheetId);
// Make a POST request with a JSON payload.
// Make a GET request and log the returned content.
var url = PROJECTS_GET_CONTENT_URL.format(sheetId);
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
I think this OAuth2 library may be useful in this case, I'm just not sure how to use it. Could anyone point me in the right direction?
If you own all the files, then you don't need to use an OAuth library or any special code to get the access token. You can get the access token from the ScriptApp class.
var theAccessTkn = ScriptApp.getOAuthToken();
You may need to manually edit the appsscript.json manifest file and add the scope:
https://www.googleapis.com/auth/script.projects
appsscript.json
{
"timeZone": "Yours will display here",
"dependencies": {
},
"webapp": {
"access": "MYSELF",
"executeAs": "USER_DEPLOYING"
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/drive.scripts",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/script.external_request"
],
"runtimeVersion": "DEPRECATED_ES5"
}
Code to overwrite an Apps Script file:
function updateContent(scriptId,content,theAccessTkn) {
try{
var options,payload,response,url;
if (!content) {
//Error handling function
return;
}
if (!theAccessTkn) {
theAccessTkn = ScriptApp.getOAuthToken();
}
//https://developers.google.com/apps-script/api/reference/rest/v1/projects/updateContent
url = "https://script.googleapis.com/v1/projects/" + scriptId + "/content";
options = {
"method" : "PUT",
"muteHttpExceptions": true,
"headers": {
'Authorization': 'Bearer ' + theAccessTkn
},
"contentType": "application/json",//If the content type is set then you can stringify the payload
"payload": JSON.stringify(content)
};
response = UrlFetchApp.fetch(url,options);
//Logger.log('getResponseCode ' + response.getResponseCode())
//Logger.log("Response content: " + response.getContentText())
} catch(e) {
console.log("Error: " + e + "\nStack: " + e.stack)
}
};