Search code examples
node.jsgoogle-calendar-apiservice-accounts

Can't authenticate google service account using Node.js


My goal is to allow people to add events to a calendar through my website (which uses Firebase currently.) I'm attempting to allow them to add the events to a service account; however, I'm having issues getting the service account authenticated. When I attempt to deploy my code, I get an 'unexpected token google' error at the await google.auth.getClient part. I have google API tools installed globally.

//ADD CALENDAR EVENT
const {google} = require('googleapis');
const calendar = google.calendar('v3');

//Authenticate Service Acounnt
function getCredentials() {
  const filePath = path.join(__dirname, 'credentials.json';
  if (fs.existsSync(filePath)) {
    return require(filePath)
  }
  if (process.env.CREDENTIALS) {
    return JSON.parse(process.env.CREDENTIALS)
  }
  throw new Error('Unable to load credentials')
}

//Create Calendar Event
function addEvent(event) {
  return new Promise(function(resolve, reject) {
      calendar.events.insert({
          calendarId: 'primary',
          resource: {
              'summary': event.eventName,
              'description': event.description,
              'start': {
                  'dateTime': event.startTime,
                  'timeZone': TIME_ZONE,
              },
              'end': {
                  'dateTime': event.endTime,
                  'timeZone': TIME_ZONE,
              },
          },
      }, (err, res) => {
          if (err) {
              console.log('Rejecting because of error');
              reject(err);
          }
          console.log('Request successful');
          resolve(res.data);
      });
  });
}

//Add Event To Service Acount 
exports.addEventToCalendar = functions.https.onRequest((request, response) => {

  const eventData = {
      eventName: request.body.eventName,
      description: request.body.description,
      startTime: request.body.startTime,
      endTime: request.body.endTime
  }; 

  const credentials = getCredentials();
  const client = await google.auth.getClient({
    credentials,
    scopes: 'https://www.googleapis.com/auth/calendar',
  }) 

  addEvent(eventData, client).then(data => {
      response.status(200).send(data);
      return;
  }).catch(err => {
      console.error('Error adding event: ' + err.message); 
      response.status(500).send(ERROR_RESPONSE); 
      return;
  });

}); 

I've been following a combination of these two tutorials: https://medium.com/zero-equals-false/integrating-firebase-cloud-functions-with-google-calendar-api-9a5ac042e869 https://dev.to/mornir/create-a-service-account-to-authenticate-with-google-5b1k

I've spent a lot of time googling what could be wrong, but to be honest, this google authentication stuff confuses the heck out of me. I'd appreciate whatever help I can get, thanks :)


Solution

    • You want to create new event to the Google Calendar using the service account.
    • credentials.json is the credential file of the service account.
    • You want to achieve this using googleapis with Node.js.
    • You have already been able to use Calendar API.

    If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.

    Modification points:

    • When the service account is used, you can use google.auth.JWT instead of google.auth.getClient.
    • In your script, client is given to addEvent(eventData, client). But at function addEvent(event) {}, client is not used.
    • About path.join(__dirname, 'credentials.json';, ) is required to be added. And I think that const path = require("path"); is also required to added.

    Modified script:

    //ADD CALENDAR EVENT
    const { google } = require("googleapis");
    const path = require("path");  // Added
    
    // --- I added below script.
    const key = require(path.join(__dirname, 'credentials.json'));
    const jwtClient = new google.auth.JWT(
      key.client_email,
      null,
      key.private_key,
      ["https://www.googleapis.com/auth/calendar"],
      null
    );
    const calendar = google.calendar({ version: "v3", auth: jwtClient });
    // ---
    
    //Create Calendar Event
    function addEvent(event) {
      return new Promise(function(resolve, reject) {
        calendar.events.insert(
          {
            calendarId: "primary",
            resource: {
              summary: event.eventName,
              description: event.description,
              start: {
                dateTime: event.startTime,
                timeZone: TIME_ZONE
              },
              end: {
                dateTime: event.endTime,
                timeZone: TIME_ZONE
              }
            }
          },
          (err, res) => {
            if (err) {
              console.log("Rejecting because of error");
              reject(err);
              return;
            }
            console.log("Request successful");
            resolve(res.data);
          }
        );
      });
    }
    
    //Add Event To Service Acount
    exports.addEventToCalendar = functions.https.onRequest((request, response) => {
      const eventData = {
        eventName: request.body.eventName,
        description: request.body.description,
        startTime: request.body.startTime,
        endTime: request.body.endTime
      };
    
      addEvent(eventData) // Modified
        .then(data => {
          response.status(200).send(data);
          return;
        })
        .catch(err => {
          console.error("Error adding event: " + err.message);
          response.status(500).send(ERROR_RESPONSE);
          return;
        });
    });
    

    Note:

    • This modified script supposes that the object of request.body can be used for resource of calendar.events.insert().
    • Is TIME_ZONE declared elsewhere? Please be careful this.
    • When the email of service account is not shared with the Google Spreadsheet, the event cannot be created. So please be careful this.

    References:

    If I misunderstood your situation and this was not the direction you want, I apologize.