How do I update a google calendar event using Node js? - javascript

I'm following the google calendar documentation to update an event via a service account here. Unfortunately, besides describing the initial setup with node in mind (which still doesn't show the process via a service account) google doesn't seem to include the node flavor in any further documentation.
My authentication is setup below:
//import google library
const {google} = require("googleapis")
//setup scope of auth
const scopes = ['https://www.googleapis.com/auth/calendar.events'];
//load client secrets from a local file
let credentialsRaw = fs.readFileSync(process.env.GOOGLE_CALENDAR_SECRET);
//get credentials as json parsed from raw file contents
let credentials = JSON.parse(credentialsRaw);
//setup auth obj
let auth = new google.auth.JWT(
credentials.client_email, null,
credentials.private_key, scopes
);
Followed by my code that tries to update the event:
//setup calendar object
const calendar = await google.calendar({
version: "v3",
auth
});
//make call to update calendar event
let updateResults = await calendar.events.update({
auth: auth,
calendarId: req.body.calendar_id, //log looks like: i798978asdfjka678sagsdfv3344#group.calendar.google.com
eventId: req.body.event_id, //log looks like: 12432dsfkjhhwqejhkj12
resource: {
"summary": "Test Summary",
"description": "Test Description",
"end": req.body.end_time, //log for end and start look like: { dateTime: '2021-08-09T11:30:00-04:00', timeZone: 'America/New_York' }
"start": req.body.start_time,
}
});
The error I get is "Not found". That's it. I've tried encoding the calendarId with encodeURIComponent() according to this answer but that didn't change anything. The poster of that answer also mentioned later that the encoding fix isn't needed anymore.
Some common issues on the account side I think I've handled correctly:
The project that the service account is in has the calendar api enabled
The service account has domain wide authority

The following should show you how to authorize the service account.
const {google} = require('googleapis');
const auth = new google.auth.GoogleAuth({
keyFile: '/path/to/your-secret-key.json',
scopes: ['https://www.googleapis.com/auth/calendar.events'],
});
const service = google.calendar({
version: 'v3',
auth: auth
});
For more info see service-account-credentials
Not found
Error normally means that you do not have access to the calendar you are trying to access. I would check to be sure the delegation was setup properly to the calendar. Try testing with a calendar.get to be sure you have access.

Related

Get Events From A Calendar Using Google's NodeJS OAuth2 Client

I'm using this nodejs google library: https://github.com/googleapis/google-api-nodejs-client#getting-supported-apis
Here is the api call so far (in the actual code I have my real secrets and ids):
const { google } = require("googleapis");
const oauth2Client = new google.auth.OAuth2(
"MY_CLIENT_ID",
"MY_CLIENT_SECRET",
"http://localhost:3000"
);
export default function handler(req, res) {
res.status(200).json(oauth2Client);
}
That outputs
{"_events":{},"_eventsCount":0,"transporter":{},"credentials":{},"eagerRefreshThresholdMillis":300000,"forceRefreshOnFailure":false,"certificateCache":{},"certificateExpiry":null,"certificateCacheFormat":"PEM","refreshTokenPromises":{},"_clientId":"MY_CLIENT_ID","_clientSecret":"MY_CLIENT_SECRET","redirectUri":"http://localhost:3000"}
Does anybody know how to use that oauth2Client to get a list of events from a specific calendar? I can't find any information on that anywhere.
The info I do find uses something called gapi, but this is what is on Google's documentation.
Edit
I tried Ricardo's suggestion but it said it could not load the default credentials:
const authClient = await auth.getClient();
Is there a way to specify the default credentials in the code without doing it via the command line via
gcloud auth application-default login

Authenticate to google cloud as a service account on browser

I am trying to migrate my google cloud app from Nodejs to native JavaScript, such that it can be run in the browser. However, I can't seem to find any examples of how to authenticate as a service account in the browser. Authenticating in Nodejs looked like this:
const textToSpeech = require('#google-cloud/text-to-speech');
const {Storage} = require('#google-cloud/storage');
const projectId = 'project'
const keyFilename = 'key.json'
const storage = new Storage({projectId, keyFilename});
const client = new textToSpeech.TextToSpeechClient({projectId, keyFilename});
In all of the searching I've done, I've only ever found solutions that use API keys and Client IDs. Additionally, all of these solutions prompt for a user to login, which is not what I want. I'd like to do exactly what the Nodejs code is doing, but in native browser JavaScript.
google-auth-library allows loading creds from hardcoded text:
const {auth} = require('google-auth-library');
const {Storage} = require('#google-cloud/storage');
const authClient = auth.fromJSON({
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "your-private-key-id",
"private_key": "your-private-key",
"client_email": "your-client-email",
"client_id": "your-client-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "your-cert-url"
});
const storage = new Storage({authClient});
However you should be careful. Embedded credentials can be extracted, and then anyone can make any call as this service account. Theoretically you can be safe with carefully restricted access, but this is easy to make a mistake.
For GCS you are likely better off with Signed URLs or some other solution that keeps credentials on the server.

How to impersonate a Google Workspace account through ADC on Google Cloud Functions?

What I'm trying to do is to:
Create a service account
Give that service account Domain Wide Delegations
Use the Application Default Credentials to impersonate a Google Workspace user and access it's email with gmail API
My code works fine in local development, by using the credential key json file generated for the Service Account mentioned in step 1, through Application Credentials Default mechanism (I have set up the environment variable GOOGLE_APPLICATION_CREDENTIALS to the path of the credentials.json)
import {google} from 'googleapis'
const scopes = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.modify',
];
const auth = new google.auth.GoogleAuth({
clientOptions: {
subject: 'user#email.com',
},
scopes,
});
const gmail = google.gmail({
version: 'v1',
auth,
});
const list = await gmail.users.messages.list({
userId: 'me',
maxResults: 10,
});
The problem is AFAIK, in local environment (aka not GCE) GoogleAuth uses JWT and in GCE it uses Compute Auth method, where a subject can't be configured or if configured is ignored.
So that's why when deploying the Cloud Function it throws an error about Precondition Check Failed and nothing else more specific.
In my limited knowledge and research I think the solution would be to somehow convert the Compute Auth -> JWT with a subject defined.
The current solution I have implemented and works, consists in saving the credentials.json into the Google Secret Manager:
// Acquire credentials from secret manager:
const secret = await getApiKeyFromSecretManager('SECRET_NAME');
const jsonCreds = JSON.parse(Buffer.from(secret).toString());
// Create the JWT
const auth = google.auth.fromJSON(jsonCreds);
auth.subject = 'user#email.com';
auth.scopes = scopes;
But I'm not really comfortable having to access or save the credentials in the Secret Manager, as I think the solution is not as elegant as it could be.

how to work with google calender API in Nodes js and React

my organization is creating a hotel booking website for another company. Apart from the website we are helping this company to create, They also have their rooms or spaces listed on the external website, Airbnb precisely. so they have the same spaces and hotels listed on the website we created for them and also in Airbnb. so we need to implement a way that won't allow their clients to book the same space or room at the same range of time. I.E if a client books a room at 9:45 am in the morning and will lodge only for 2 days, that room should no longer be available for the dates range the other user has booked it. So we decided to use the google calendar API as a middle man between our own website and AirbnB. if a user books any room or space in our website, on payment successful on the client at our ends here, The days the client wanna lodge should be added to the google calendar API to be exported to Airbnb and this should avoid bringing that consent screen google normally brings out to authenticate as we don't need to show anything to the client, we just want everything to be done underground. Most importantly I wanna do this using React.
I followed one node js video tutorial and he was able to create and read events successfully. he used the googleOauth2 playground API for authenticating. The API gave us a client Id, client Secret and most importantly a refresh token that would be used mainly to authenticate. This is the code below
// Require google from googleapis package.
const { google } = require('googleapis')
// Require oAuth2 from our google instance.
const { OAuth2 } = google.auth
// Create a new instance of oAuth and set our Client ID & Client Secret.
const oAuth2Client = new OAuth2(
'YOUR CLIENT ID GOES HERE',
'YOUR CLIENT SECRET GOES HERE'
)
// Call the setCredentials method on our oAuth2Client instance and set our refresh token.
oAuth2Client.setCredentials({
refresh_token: 'YOUR REFRESH TOKEN GOES HERE',
})
// Create a new calender instance.
const calendar = google.calendar({ version: 'v3', auth: oAuth2Client })
I didn't post everything because the remaining codes is just creating events and the likes. So I was following this format to make the events work here in react but I was unable to pass the authenticate block in react.
According to the documentation for javascript
It states that I have to add the script src file below to make gapi works for javascript
<script async defer type='text/Javascript' src="https://apis.google.com/js/api.js"></script>
But the problem now is how to implement the authentication using the refresh token available that was downloaded as json in react. The below code is how this was implemented using client and api key always showing windows for users to login before they can create events which in my case, I dont want
// Client ID and API key from the Developer Console
var CLIENT_ID = '<YOUR_CLIENT_ID>';
var API_KEY = '<YOUR_API_KEY>';
// Array of API discovery doc URLs for APIs used by the quickstart
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = "https://www.googleapis.com/auth/calendar.readonly";
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
listUpcomingEvents();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
function initClient() {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
}, function(error) {
appendPre(JSON.stringify(error, null, 2));
});
}
So the below is what have done but am stuck as I dont know what to else from here. I dont even know if what am doing is right or wrong
useEffect(()=>{
gapi.load("client:auth2", () => {
gapi.client
.init({
clientId: "myClientId",
clientKey: "myClientKey",
})
.setToken({
access_token:
"myRefreshToken",
});
});
},[])
I need to get pass authentication to be able to create, read events in google calendar in REACT

Add google calendar to specific users calendarList using a service account

I am using a service account to create new calendars in our system.
async function newCalendar (email) {
console.log('creating calendar for username', email, 'as', config.google.calendar.clientEmail);
const auth = await getAuth();
const calendar = google.calendar({version: 'v3', auth});
const newCal = await calendar.calendars.insert({
resource: {summary: `SYSCALENDAR(${email})`}});
const calendarId = newCal.data.id;
const associated = await calendar.acl.insert({
calendarId,
requestBody: {
role: 'reader',
scope: {
type: 'user',
value: email
}
},
sendNotifications: false
});
return calendarId;
}
As shown above, the calendars are created as owned by the service account that performs the calendars.insert call.
I am also injecting a calendar.acl record, which I believe grants permission for the user identified by 'email' to access this calendar.
In this snippet, I have:
sendNotifications: false
If I set this to true, the user receives an email about the new ACL entry, and can click to add the calendar to their own calendarList.
I don't want the user to have to do this, and instead would like to add the calendar to the calendarList in code, as the service account.
This is not as simple as calendarList.insert(calendarId) as that will insert the calendar into the service accounts calendarList.
There has been a recent change in the way Google treats calendars created / modified by a service account.
It is no longer possible to add users to such a calendar without the users' manual approval.
The only way to get around it (works only for domain users) is to Perform G Suite Domain-Wide Delegation of Authority. Basically, make the service account work on the user's behalf and make the service account accept the invitation on the users behalf.

Categories