2

How to use googleapis google.auth.GoogleAuth() for google API service account in Twilio serverless function, since there is no FS path to provide as a keyFile value?

Based on the example here ( https://www.section.io/engineering-education/google-sheets-api-in-nodejs/ ) and here ( Google api node.js client documentation ) my code is based on the example here ( Receive an inbound SMS ) and looks like...

const {google} = require('googleapis')
const fs = require('fs')
exports.handler = async function(context, event, callback) {
  const twiml = new Twilio.twiml.MessagingResponse()
  
  // console.log(Runtime.getAssets()["/gservicecreds.private.json"].path)
  console.log('Opening google API creds for examination...')
  const creds = JSON.parse(
    fs.readFileSync(Runtime.getAssets()["/gservicecreds.private.json"].path, "utf8")
  )
  console.log(creds)
  
  // connect to google sheet
  console.log("Getting googleapis connection...")
  const auth = new google.auth.GoogleAuth({
    keyFile: Runtime.getAssets()["/gservicecreds.private.json"].path,
    scopes: "https://www.googleapis.com/auth/spreadsheets",
  })
  const authClientObj = await auth.getClient()
  const sheets = google.sheets({version: 'v4', auth: authClientObj})
  const spreadsheetId = "myspreadsheetID"
  
  console.log("Processing message...")
  if (String(event.Body).trim().toLowerCase() == 'KEYWORD') {
    console.log('DO SOMETHING...')
    try {
      // see https://developers.google.com/sheets/api/guides/values#reading_a_single_range
      let response = await sheets.spreadsheets.values.get({
        spreadsheetId: spreadsheetId,
        range: "'My Sheet'!B2B1000"
      })
      console.log("Got data...")
      console.log(response)
      console.log(response.result)
      console.log(response.result.values)
    } catch (error) {
      console.log('An error occurred...')
      console.log(error)
      console.log(error.response)
      console.log(error.errors)
    }
  } 
  
  // Return the TwiML as the second argument to `callback`
  // This will render the response as XML in reply to the webhook request
  return callback(null, twiml)

...where the Asset referenced in the code is for a JSON generated from creating a key pair for a Google APIs Service Account and manually copy/pasting the JSON data as an Asset in the serverless function editor web UI.

I see error messages like...

An error occurred...

{ response: '[Object]', config: '[Object]', code: 403, errors: '[Object]' }

{ config: '[Object]', data: '[Object]', headers: '[Object]', status: 403, statusText: 'Forbidden', request: '[Object]' }

[ { message: 'The caller does not have permission', domain: 'global', reason: 'forbidden' } ]

I am assuming that this is due to the keyFile not being read in right at the auth const declaration (IDK how to do it since all the example I see assume a local filepath as the value, but IDK how to do have the function access that file for a serverless function (my attempt in the code block is really just a shot in the dark)).

FYI, I can see that the service account has an Editor role in the google APIs console (though I notice the "Resources this service account can access" has the error

"Could not fund an ancestor of the selected project where you have access to view a policy report on at least one ancestor"

(I really have no idea what that means or implies at all, very new to this)). Eg... enter image description here

Can anyone help with what could be going wrong here?

(BTW if there is something really dumb/obvious that I am missing (eg. a typo) just LMK in a comment so can delete this post (as it would then not serve any future value of others))

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
lampShadesDrifter
  • 3,925
  • 8
  • 40
  • 102

1 Answers1

1

The caller does not have permission', domain: 'global', reason: 'forbidden

This actually means that the currently authenticated user (the service account) does ot have access to do what you are asking it to do.

You are trying to access a spread sheet.

Is this sheet on the service accounts google drive account? If not did you share the sheet with the service account?

The service account is just like any other user if it doesn't have access to something it cant access it. Go to the google drive web application and share the sheet with the service account like you would share it with any other user just use the service account email address i think its called client id its the one with an @ in it.

delegate to user on your domain

If you set up delegation properly then you can have the service account act as a user on your domain that does have access to the file.

delegated_credentials = credentials.with_subject('userWithAccess@YourDomain.org')
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • IC, interesting. Did not know that. Unfortunately, can't test as I have just learned that "an item cannot be shared outside of " when trying to share the sheet with my .iam.gserviceaccount.com email (I assume this is G Suite related). Anyway to fix/work-around this (or should I make this another post)? Any idea about how to pass the right `keyFile` value to the `google.auth.GoogleAuth()` function for the sake of others (assuming the upvotes are from other with this similar question)? – lampShadesDrifter Sep 03 '21 at 09:09
  • 1
    If its gsuite then try to make sure that you have added delegation to the service account. The service account needs to be delegated to a user on the domain. Check update you just need to add the with_subject line of code to the authorization. – Linda Lawton - DaImTo Sep 03 '21 at 09:50
  • Where is this line of code supposed to go in the code (IDK how this would fit in the `google.auth.GoogleAuth` or `auth.getClient` connection steps)? BTW, just tried setting up delegation ( developers.google.com/admin-sdk/directory/v1/guides/delegation ) and notice that it creates OAuth2 credentials. I am trying to call on the googleapis from a Twilio serverless function that runs automatically. In all cases I done, OAuth2 clients require manually clicking through a consent screen. Is that going to be an issue here (still setting this up but would be good to know this early on)? – lampShadesDrifter Sep 03 '21 at 20:38
  • 1
    try reading though https://google-auth.readthedocs.io/en/master/reference/google.oauth2.service_account.html or check this https://github.com/googleapis/google-api-nodejs-client/issues/1699 – Linda Lawton - DaImTo Sep 04 '21 at 12:10
  • Note for others: this other post helped me the most (https://stackoverflow.com/a/61932919/8236733), so my code for accessing the sheets with minimal changes to what is in most examples ended up looking like... `const sheets = google.sheets({version: 'v4', auth: jwtClient})` (based on the linked answer here). – lampShadesDrifter Sep 09 '21 at 04:49