简体   繁体   中英

POST data to Google Sheet web app from AWS Lambda

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
}

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 set private_key of service account, please include \n .

References:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM