googleapis / google-api-nodejs-client

Google's officially supported Node.js client library for accessing Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT (Service Tokens) is included.
https://googleapis.dev/nodejs/googleapis/latest/
Apache License 2.0
11.43k stars 1.92k forks source link

Access a user's calendar events server side with a service account. #3173

Open mikemilla opened 1 year ago

mikemilla commented 1 year ago

I am trying to build this:

On frontend, user signs in with google and allows calendar scope permissions for full access to calendar.

On backend, some changes happen in the database of my app, I check the user's calendar events and potentially edit, insert or delete events.


My current implementation is like this:

import * as devKey from '../service-accounts/dev_key.json'

const SCOPES = [
  'https://www.googleapis.com/auth/calendar',
  'https://www.googleapis.com/auth/calendar.events',
]

const client = new google.auth.JWT(
  devKey.client_email,
  undefined,
  devKey.private_key,
  SCOPES,
)

await client.authorize()

const gcal = google.calendar({
  version: 'v3',
  auth: client
})

// Link my app to the user's calendar
const aclRes = await gcal.acl.insert({
  calendarId: 'primary',
  sendNotifications: true,
  requestBody: {
    scope: {
      type: 'user',
      value: 'the_user_i_want_to_access_the_calendar_of@gmail.com'
      },
    role: 'owner'
  }
})

console.log(aclRes)

// Create a new event in the user's calendar
const event = {
  summary: 'Awesome new event',
  location: '800 Howard St., San Francisco, CA 94103',
  description: 'This would be really cool if it worked',
  start: {
    'dateTime': '2023-05-28T09:00:00-07:00',
    'timeZone': 'America/Los_Angeles',
  },
  end: {
    'dateTime': '2023-05-28T17:00:00-07:00',
    'timeZone': 'America/Los_Angeles',
  },
  recurrence: [
    'RRULE:FREQ=DAILY;COUNT=2'
  ],
  attendees: [
    { 'email': 'the_user_i_want_to_access_the_calendar_of@gmail.com' },
  ],
  reminders: {
    useDefault: false,
    overrides: [
      { method: 'email', minutes: 24 * 60 },
      { method: 'popup', minutes: 10 },
    ],
  },
}

const eventRes = await gcal.events.insert({
  calendarId: 'primary',
  requestBody: event
})

console.log(eventRes)

The insert results in this error:

errors: [
    {
      domain: 'calendar',
      reason: 'forbiddenForServiceAccounts',
      message: 'Service accounts cannot invite attendees without Domain-Wide Delegation of Authority.'
    }
  ]

I've added the proper domain-wide delegation to the project that contains the service account.

Screenshot 2023-02-28 at 10 01 23 PM

What am I missing here?

AyushKaithwas commented 1 year ago

It is recommended to use the OAuth consent flow, when accessing user data https://cloud.google.com/iam/docs/best-practices-service-accounts#user-consent