CURRENTLY
I have a Google Sheets App Script 'web app'
Script in Goolge Sheets
function doPost(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A1").setValue("Hello!")
return "Success!"
}
Google Apps Script Web App Config:
Execute as: Me // or as User. I've tried both.
Who has access: Anyone within MyOrganisation
I want to make a POST request to the above Web App from AWS Lambda.
AWS Lambda .js:
const { GoogleSpreadsheet } = require("google-spreadsheet");
const doc = new GoogleSpreadsheet(
{spreadsheetId}
);
await doc.useServiceAccountAuth({
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n"),
});
let token = doc["jwtClient"]["credentials"]["access_token"];
await new Promise((resolve, reject) => {
const options = {
host: 'script.google.com',
path: "/macros/s/{myscriptid}/exec", //<-- my web app path!
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': "Bearer "+ token
}
};
//create the request object with the callback with the result
const req = HTTPS.request(options, (res) => {
resolve(JSON.stringify(res.statusCode));
});
// handle the possible errors
req.on('error', (e) => {
reject(e.message);
});
//do the request
req.write(JSON.stringify(data));
//finish the request
req.end();
});
console.log("response:"+JSON.stringify(response))
GCP Service Account
I have a GCP Service Account, with permission to Google Sheets API, and otherwise unrestricted access.
This Service account has EDIT access to the Google Sheet with the doPost(e) script.
Token Output:
"jwtClient": {
"_events": {},
"_eventsCount": 0,
"transporter": {},
"credentials": {
"access_token": "somelongvalue...............", //<-- what I use
"token_type": "Bearer",
"expiry_date": 1661662492000,
"refresh_token": "jwt-placeholder"
},
"certificateCache": {},
"certificateExpiry": null,
"certificateCacheFormat": "PEM",
"refreshTokenPromises": {},
"eagerRefreshThresholdMillis": 300000,
"forceRefreshOnFailure": false,
"email": "serviceaccount#appspot.gserviceaccount.com",
"key": "-----BEGIN PRIVATE KEY-----\nsomelongvalue=\n-----END PRIVATE KEY-----\n",
"scopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"subject": null,
"gtoken": {
"key": "-----BEGIN PRIVATE KEY-----\nsomelongvalue=\n-----END PRIVATE KEY-----\n",
"rawToken": {
"access_token": "somelongvalue...............",
"expires_in": 3599,
"token_type": "Bearer"
},
"iss": "serviceaccount#appspot.gserviceaccount.com",
"sub": null,
"scope": "https://www.googleapis.com/auth/spreadsheets",
"expiresAt": 1661662492000
}
}
ISSUE
Current response:
response:"401"
I cannot find any Google documentation on how to setup the headers to authenticate a request (from my service account) to my organisation restricted web app.
When the Web App is open to "Anyone" then it runs fine, but as soon as I restrict to MyOrganisation, I struggle to find a way to authenticate my POST request.
HELP!
How do I set up a POST request to my Google Sheets web app such that it can be protected by authentication? Right now, I'd be happy to find ANY means to authenticate this request (not necessarily a service account) that doesn't leave it completed open to public.
Should I use this hack?
One idea I had was to put a "secret" into my lambda function, and then make the web app public. The web app would check the secret, if if matched, would execute the function.
Modification points:
In order to access Web Apps using the access token with a script, the scopes of Drive API are required to be included. Those are https://www.googleapis.com/auth/drive.readonly, https://www.googleapis.com/auth/drive, and so on. Ref
When I saw your showing script, it seems that the access token is retrieved using google-spreadsheet. When I saw the script of google-spreadsheet, it seems that this uses only the scope of https://www.googleapis.com/auth/spreadsheets. Ref
From this situation, I thought that the reason for your current issue might be due to this. If my understanding is correct, how about the following modification? In this modification, the access token is retrieved by googleapis for Node.js from the service account. Ref
Modified script:
Google Apps Script side:
function doPost(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A1").setValue("Hello!")
return ContentService.createTextOutput("Success!"); // Modified
}
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in the report "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
Node.js side:
const { google } = require("googleapis");
const HTTPS = require("https");
const auth = new google.auth.JWT(
"###", // Please set client_email here.
null,
"###", // Please set private_key here. When you set private_key of service account, please include \n.
["https://www.googleapis.com/auth/drive.readonly"],
null
);
function req(token) {
return new Promise((resolve, reject) => {
const data = { key1: "value1" }; // Please set your value.
const options = {
host: "script.google.com",
path: "/macros/s/{myscriptid}/exec", //<-- my web app path!
method: "POST",
headers: {Authorization: "Bearer " + token},
};
const req = HTTPS.request(options, (res) => {
if (res.statusCode == 302) {
HTTPS.get(res.headers.location, (res) => {
if (res.statusCode == 200) {
res.setEncoding("utf8");
res.on("data", (r) => resolve(r));
}
});
} else {
res.setEncoding("utf8");
res.on("data", (r) => resolve(r));
}
});
req.on("error", (e) => reject(e.message));
req.write(JSON.stringify(data));
req.end();
});
}
auth.getAccessToken().then(({ token }) => {
req(token).then((e) => console.log(e)).catch((e) => console.log(e));
});
When this script is run, when the Web Apps is correctly deployed, the script of Web Apps is run and Success! is returned.
Note:
If this modified script was not useful for your Web Apps setting, please test as follows.
Please confirm whether your service account can access to the Spreadsheet again.
Please share the email address of the service account on the Spreadsheet. From your showing Google Apps Script, I thought that your Google Apps Script is the container-bound script of the Spreadsheet.
Please reflect the latest script to the Web Apps.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in the report "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
When you set private_key of service account, please include \n.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Added:
When you will directly put the value to the Spreadsheet using Sheets API with google-spreadsheet module, you can also use the following script.
const { GoogleSpreadsheet } = require("google-spreadsheet");
const sample = async () => {
const doc = new GoogleSpreadsheet("###"); // Please set your Spreadsheet ID.
await doc.useServiceAccountAuth({
client_email: client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY,
});
await doc.loadInfo();
const sheet = doc.sheetsByTitle["Sheet1"];
await sheet.loadCells("A1");
sheet.getCell(0, 0).value = "Hello!";
await sheet.saveUpdatedCells();
};
sample();
In this case, your service account is required to be able to access to the Spreadsheet. Please be careful about this.
I am busy with a angular4 web app that uses the youtubeInMp3 API. Everything works as it should when downloading mp3 files using the API however it doesn't work for all videos, I'm not sure which videos give the issue, I've noticed that it often happens when the YouTube video ID contains the _ character. when it does I get the below error... I know that the issue isn't with the video as it works when I test it http://www.youtubeinmp3.com I've tried urlEncoding the id before passing it to the API, replacing _ with - and replacing _ with %20 with no success. Any idea what might be going wrong here/how I can go about fixing it?
My error (from chrome developer console)
core.es5.js:3046 Angular is running in the development mode. Call
enableProdMode() to enable the production mode.
main.bundle.js:368 id: t0sS60Jx51c main.bundle.js:788 url
main.bundle.js:789
http://www.youtubeinmp3.com/fetch/?format=JSON&video=https://www.youtube.com/watch?v=t0sS60Jx51c
main.bundle.js:376 SyntaxError: Unexpected token < in JSON at position
0
at Object.parse ()
at Response.Body.json (vendor.bundle.js:37736)
at MapSubscriber.project (main.bundle.js:791)
at MapSubscriber._next (vendor.bundle.js:22171)
at MapSubscriber.Subscriber.next (vendor.bundle.js:242)
at XMLHttpRequest.onLoad (vendor.bundle.js:38165)
at ZoneDelegate.webpackJsonp.576.ZoneDelegate.invokeTask (polyfills.bundle.js:2496)
at Object.onInvokeTask (vendor.bundle.js:5501)
at ZoneDelegate.webpackJsonp.576.ZoneDelegate.invokeTask (polyfills.bundle.js:2495)
at Zone.webpackJsonp.576.Zone.runTask (polyfills.bundle.js:2263)
When I make this request that gives me an error in the insomnia REST client I get the following response:
metahttp-equiv="refresh" content="0" url="http://www.youtubeinmp3.com/download/?video=https://www.youtube.com/watch?v=t0sS60Jx51cautostart=1n=x="
An example of a response successful request to the youtubeInMp3 Api, the id logged in the console is the YouTube video id:
id: bjLorSJAB70 main.bundle.js:788 url main.bundle.js:789
http://www.youtubeinmp3.com/fetch/?format=JSON&video=https://www.youtube.com/watch?v=bjLorSJAB70
main.bundle.js:371 Objectlength: "199"link:
"http://www.youtubeinmp3.com/download/get/?i=fOzuoPTm06oBoLw3aidWejXF7Yc2JB3T&e=33"title:
"Santo Daime EU CHAMO A CURA"proto: Object main.bundle.js:373
http://www.youtubeinmp3.com/download/get/?i=fOzuoPTm06oBoLw3aidWejXF7Yc2JB3T&e=33
My code:
//function that passes the video id to a restService for downloading:
download(id: string) {
console.log("id: " + id);
this.restService.yDownload(id)
.subscribe(
(res) => {
console.log(res);
let url = res.link;
console.log(url);
window.location.href = `${url}target="_blank"`;
}, (res) => {
console.log(res)
});
}
//rest service function for getting the download link from youtubeInMp3 api
yDownload(id: string) : Observable<any> {
let params = [
`format=JSON`,
`video=https://www.youtube.com/watch?v=${id}`
].join('&');
let queryUrl = `${this.Y_DOWNLOAD_URL}?${params}`;
console.log("url");
console.log(queryUrl);
return this.http.get(queryUrl)
.map((res: Response) => res.json())
.catch((err: any) => Observable.throw(err || 'server error'));
}
I am busy with a very basic Angular2/Nativescript app. I Use a RestService to talk to a local API and seem to be having some inconsistent results. This Rest Service is also used by a web app without any issues. When I call the ping(ip: string) function to test the IP address of the local server I get an error, when I console.log it the output of the error is:
JS: TypeError: error.json() is not a function
Any idea what this means? Is there any way to debug/see what's going on using Nativescript?
The only difference between my mobile app and webb app that I can think of is that each uses a different version of Typescript, could this have anything to do with my issue?
The ping function that doesn't work on the mobile app:
ping(ip: string) : Observable<any> {
let tempUrl: string = "http://" + ip + "/BRMServices/WebEnquiry/ping";
return this.http.get(tempUrl)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'server error'));
}
The following function works perfectly on the mobile and web apps:
getProduct(barcode: string): Observable<ProductModel> {
return this.http.get('http://10.60.160.34/BRMServices/WebEnquiry/' + "/POSEnquiry/" + barcode)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
The Nativescript app uses Typescript V2.0.2,
The webb app uses Typescript V~2.0.3
I use iron.io to call the following parse.com function to get Facebook details of my user's friends.
var getDetailsForID = function (fbID) {
var thePromise = new Parse.Promise();
// TODO: maybe we can batch several users together into a single request................
console.log("Enter getDetailsForID");
FB.api('/v1.0', 'post', {
batch: [
{ method: 'get', name: 'basic', relative_url: fbID + '?fields=id,name,gender&include_headers=false', omit_response_on_success: false },
]
}, function(res) {
console.log("Enter callback in getDetailsForID");
if(!res || res.error) {
console.log(!res ? 'error occurred' : res.error);
return;
}
console.log(" getDetailsForID res: " + res);
thePromise.resolve(res);
});
console.log("Exit getDetailsForID");
return thePromise;
}
In the iron.io log I see:
Enter callback in getDetailsForID
[Error: 139994800940864:error:0607907F:digital envelope routines:EVP_PKEY_get1_RSA:expecting an rsa key:../deps/openssl/openssl/crypto/evp/p_lib.c:288:
The following are not called:
console.log(" getDetailsForID res: " + res);
thePromise.resolve(res);
Any idea how to resolve this problem?
Since the answer to this question, IronWorker has released a Docker workflow. Feel free to use our official iron/node Docker Image. https://github.com/iron-io/dockerworker/tree/master/node
Ahh this is definitely not a problem with Iron.io but a problem with your post to the Facebook v1.0 API call.
+ '?fields=id,name,gender&include_headers=false', omit_response_on_success: false
do you really want to omit response on success? Which Facebook endpoint are you sending a Post to?
edit
IronWorker is currently set to 0.10.25 as of 07/22/2014, Use if your node version is < 0.10.25 you may receive this error.
fix: load your own version of node
in your .worker file add the following
deb "http://ppa.launchpad.net/chris-lea/node.js/ubuntu/pool/main/n/nodejs/nodejs_0.10.29-1chl1~trusty1_amd64.deb"
# OR you can download it from a local copy
deb "nodejs_0.10.29-1chl1~trusty1_amd64.deb"
You can install other missing or updated versions of binaries in a similar manner if there is a .deb for it.
Example in practice here on github
tldr.
use latest version of node, possibly openssl also.
I am new to titanium appcelerator family. Also I am not sure if somebody has posted the same problem before. I couldn't found it so posting it here.
I am developing one mobile application. And in my application I am trying to call one web service which is hosted on my local environment, but it is always returning response with status code 0 and error message "Can't reach host". So I thought that there should be some problem with my web service that I deployed on remote location. And hence I have tried to call some other web services like twitter, but it's same.. :( It is giving me an error while calling twitter web service also.
I also check for a cross domain policy issue. So I have also tried to set rule on server for web service, but it didn't work out. Following is code snippet and environment details that I am using.
Application type: Mobile
Titanium SDK: 2.1.4 GA
Device: Web Browser
Host Operating System: Windows 7
Titanium Studio: Titanium Studio, build: 2.1.2.201208301612
Files for sample web service which is I am calling is present on following locations:
http://db.tt/XoSCujux (api.php)
http://db.tt/bJG2A5XT (Rest.inc.php)
http://db.tt/Hxt6Oojx (.htaccess)
Code:
//declare the http client object
var xhr = Titanium.Network.createHTTPClient();
//this method will process the remote data
xhr.onload = function() {
//check to see if we are refreshing the data via our
//pull and release mechanism
//create a json object using the JSON.PARSE function
result = JSON.parse(this.responseText);
Ti.API.info(result);
console.log(result);
var msgTitle = "data:";
var msgText = result;
var alertBox = Ti.UI.createAlertDialog({
title: msgTitle,
message: msgText
});
alertBox.show();
};
//this method will fire if there's an error in accessing the remote data
xhr.onerror = function(e) {
//log the error to our titanium developer console
Ti.API.error(this.status + ' - ' + this.statusText);
console.log(this.status + ' - ' + this.statusText);
var msgTitle = "Error : " + this.status;
var msgText = "Error Text : " + this.statusText;
var alertBox = Ti.UI.createAlertDialog({
title: msgTitle,
message: msgText
});
alertBox.show();
Ti.API.debug("STATUS: " + this.status);
Ti.API.debug("TEXT: " + this.responseText);
Ti.API.debug("ERROR: " + e.error);
alert('There was an error retrieving the remote data. Try again.' + e.error);
};
//open up the recipes xml feed
xhr.open("POST", "http://127.0.0.1/rest_example/api.php?rquest=locations");
//finally, execute the call to the remote feed
xhr.send();
Any help would be really appreciated.