Google service account unable to list calendars? - node.js

Can anyone indicate the right settings to give allow a service account access to a Google calendar?
We have a node.js project that is meant to provide access to the Google calendars it has been given access to, but we can't work out why it can't see the calendars.
At the same time, we were able to trying a different account that has been working in another environment and this worked, but the problem is that no one who previously worked on the project has access to the configuration for it to compare.
The response.data we are getting with the problem account:
{
"kind": "calendar#calendarList",
"etag": "\"p33k9lxxjsrxxa0g\"",
"nextSyncToken": "COias9Pm4vUCEjxvdGurdXRob24tZGV2LXNlcnZpY2UxQG90YxxxdGhvbi1kZXYuaWFtLmdzZXJ2aWNlYWNxb3VudC5jb20=",
"items": []
}
Either way this would suggest the issue is with the configuration of the service account, rather than the code itself, but will share the code anyhow:
import { Auth, calendar_v3 as calendarV3 } from 'googleapis';
const Calendar = calendarV3.Calendar;
async getCalendarApi () {
const jwtClient = new Auth.JWT(
this.keyConfig.clientEmail,
undefined,
this.keyConfig.privateKey.replace(/\\n/g, '\n'),
[
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/calendar.events'
]
);
const calendarApi = new Calendar({
auth: jwtClient
});
await jwtClient.authorize();
return calendarApi;
}
async init () {
// loads the configuration from our app's configuration file
this.keyConfig = getKeyConfig('google-calendar');
const calendarApi = await this.getCalendarApi();
const response = await calendarApi.calendarList.list();
console.log(JSON.stringify(response.data, undefined, 2));
}
As for the service account, in the Google console, this is the how it was set up:
Open up https://console.cloud.google.com/
Select the appropriate project at the top
Search for 'Google Calendar API' in the search bar
Validate it is enabled
Click 'Manage', which takes me to https://console.cloud.google.com/apis/api/calendar-json.googleapis.com/metrics?project=xxxxxx ('xxxxxx' is the project id, but masked here)
Click on 'Credentials
Click on 'Create Credentials' at the top and select 'service account'
Provide a name of the service account
Click 'Create and Continue'
Add a role: Basic -> Viewer
Click 'Done'
In the 'Credentials' page (https://console.cloud.google.com/apis/credentials?project=xxxxxx) click edit for the account we just created
In the 'keys' section: Add key -> Create New Key and then specify JSON
From the above steps we take the client_email and the private_key field values to user with our nodejs app.
Then in the calendar we want it to access, we add the email address of the service account as a viewer.
Trying all the above still results in the list of calendars visible by the service account to be empty.
Any ideas?

From your explanation and the returned value, I'm worried that in your situation, you might have never inserted the shared Calendar with the service account. If my understanding is correct, how about the following modification?
Modification points:
Insert the shared Calendar to the service account.
In this case, please modify the scope https://www.googleapis.com/auth/calendar.readonly to https://www.googleapis.com/auth/calendar.
async init () {
// loads the configuration from our app's configuration file
this.keyConfig = getKeyConfig('google-calendar');
const calendarApi = await this.getCalendarApi();
const response = await calendarApi.calendarList.insert({resource: {id: "####group.calendar.google.com"}}); // Please set the Calendar ID here. Or {requestBody: {id: "####group.calendar.google.com"}}
console.log(JSON.stringify(response.data, undefined, 2));
}
Retrieve the calendar list using your script.
After the Calendar was inserted to the service account using the above script, you can obtain the shared Calendar in the Calendar list.
Reference:
CalendarList: insert

Related

Stripe connect onboarding test data for standard accounts

I'm trying to follow this document to setup Stripe Connect: https://stripe.com/docs/connect/enable-payment-acceptance-guide?platform=web&ui=checkout#create-account-link
At the account link phase it redirects me to Stripe for creating an account etc - for development purposes I would like to skip this instead of entering real data every time.
I came accross this document for testing Stripe: https://stripe.com/docs/connect/testing
It says there is a way to force skip but I don't see anything pop up. Is there a special value I need to pass in to enable the force skip option?
Here are the code snippets I've been using to test account linking
const stripe = new Stripe(secrets.STRIPE_SECRET_KEY, {
apiVersion: "2022-11-15"
});
export class StripeClient {
/**
* Create a Stripe account for a user. The account will be associated with the ZCal account
*/
static accountCreationRequest = async (): Promise<Stripe.Response<Stripe.Account>> => {
const account: Stripe.Response<Stripe.Account> = await stripe.accounts.create({
type: "standard"
});
return account;
};
static accountLinkRequest = async (stripeAccountId: string): Promise<Stripe.Response<Stripe.AccountLink>> => {
const accountLink: Stripe.Response<Stripe.AccountLink> = await stripe.accountLinks.create({
account: stripeAccountId,
refresh_url: `${config.CLIENT_BASE_URL}/account/integrations`,
return_url: `${config.CLIENT_BASE_URL}/account/integrations`,
type: "account_onboarding"
});
return accountLink;
};
}
The "force skip" line on testing Doc is for OAuth flow, which is unrelated to your Onboarding flow, so first let's ignore it.
When you make your first request to create the account, you can prefill as much information as you want, and it will be automatically populated in the flow later. Here is the full list of fillable fields.
You can pre-fill any information on the account, including personal
and business information, external account information, and more.

How to get statistics using Google Analytics Data

I wanted to bring statistics from Firebase to my website. To do this, I linked Firebase with Google Analytics in the settings in the "Integrations" tab
From there I copied the Property ID
Enabled Google Analytics Data API in my project
Then I replaced the Property ID with my own in this script and ran the script.
But I got an error:
Unable to detect a Project Id in the current environment. To learn more about authentication and Google APIs, visit:https://cloud.google.com/docs/authentication/getting-started
I suspect the issue is that you have not configured GOOGLE_APPLICATION_CREDENTIALS
You should be following the API Quickstart it shows how to configure the credentials needed to authorize this code.
Once your authorization is configured you should be able to access this. Remember that the service account needs to be granted access to your google analytics account or it wont be able to access the property.
/**
* TODO(developer): Uncomment this variable and replace with your
* Google Analytics 4 property ID before running the sample.
*/
// propertyId = 'YOUR-GA4-PROPERTY-ID';
// Imports the Google Analytics Data API client library.
const {BetaAnalyticsDataClient} = require('#google-analytics/data');
// Using a default constructor instructs the client to use the credentials
// specified in GOOGLE_APPLICATION_CREDENTIALS environment variable.
const analyticsDataClient = new BetaAnalyticsDataClient();
// Runs a simple report.
async function runReport() {
const [response] = await analyticsDataClient.runReport({
property: `properties/${propertyId}`,
dateRanges: [
{
startDate: '2020-03-31',
endDate: 'today',
},
],
dimensions: [
{
name: 'city',
},
],
metrics: [
{
name: 'activeUsers',
},
],
});
console.log('Report result:');
response.rows.forEach(row => {
console.log(row.dimensionValues[0], row.metricValues[0]);
});
}
runReport();

Google Analytics RunReport return 7 PERMISSION_DENIED: User does not have sufficient permissions for this property

I want to run a google analytics report using runReport.
I have followed the instructions at https://developers.google.com/analytics/devguides/reporting/data/v1/quickstart-client-libraries and made a copy of the code that uses json-credentials at https://github.com/googleapis/nodejs-analytics-data/blob/main/samples/quickstart_json_credentials.js
I have one Analytics Account with two Properties & Apps - DEV and STAGE.
In each of them I have created a service account with OWNER permissions. After that I created a key and downloaded the generated JSON-file.
I can test and run the API from the "Try this API" link at https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runReport for both my properties and it works well for both of them. (But then I use OAuth authentication of course).
However when I run the code using the JSON-credentials the DEV always work. The STAGE always fails. I have tried running the code both from my machine as well as from https://shell.cloud.google.com/ with the same result.
The failure message is:
7 PERMISSION_DENIED: User does not have sufficient permissions for this property. To learn more about Property ID, see https://developers.google.com/analytics/devguides/reporting/data/v1/property-id.
I have checked several times that the DEV and STAGE properties are correct and that the I use the JSON-credentials file associated with the correct property.
In STAGE, where calls always fail I have several times created new service accounts (with Basic->Owner credentials), created keys and downloaded JSON-credentials. But all with the same result. STAGE always fails.
I have compared the permissions for the service accounts in DEV and STAGE and they seems to be identical.
I have read and tried tips from other people with "permission denied" issues here at stack overflow but none that solves my issue.
Are there any authorization logs that I read from google console?
I'm kind of stuck now.
The code I run (properties and file name obfuscated):
"use strict";
function main(propertyId = "YOUR-GA4-PROPERTY-ID", credentialsJsonPath = "") {
propertyId = "27..DEV property";
credentialsJsonPath = "./DEVcredentials.json";
// propertyId = "27..STAGE property";
// credentialsJsonPath = "./STAGEcredentials.json";
const { BetaAnalyticsDataClient } = require("#google-analytics/data");
const analyticsDataClient = new BetaAnalyticsDataClient({
keyFilename: credentialsJsonPath,
});
async function runReport() {
const [response] = await analyticsDataClient.runReport({
property: `properties/${propertyId}`,
dateRanges: [
{
startDate: "2020-03-31",
endDate: "today",
},
],
dimensions: [
{
name: "country",
},
],
metrics: [
{
name: "activeUsers",
},
],
});
console.log("Report result:");
response.rows.forEach((row) => {
console.log(row.dimensionValues[0], row.metricValues[0]);
});
}
runReport();
}
process.on("unhandledRejection", (err) => {
console.error(err.message);
process.exitCode = 1;
});
main(...process.argv.slice(2));
User does not have sufficient permissions for this property.
Means that the user who you have authenticated with does not have permissions to access that view. In this case we are talking about properties/${propertyId}
If you are using a service account make sure to grant it permissions at the account level not the view level.
Permissions need to be granted thorough the google analytics website in the admin section

Using Google Drive API

I am in the process of setting up data scraper that writes the data to an excel file and then I want to upload those files to a folder in my own drive account. This will be done once a day via a scheduler, so fully automated is the aim here.
I am looking at this quickstart guide and it goes through the process of Oauth2. I don't want to access any others user data, just push up the files i create. This may be a stupid question but do i have to go through the oAuth process and not just use an api key and secret for example?
In Step 4 it says The first time you run the sample, it will prompt you to authorize access:, how would i do that if this is running on an EC2 instance for example
Thanks
If you are only going to be accessing your own account then you should look into a service account. Service accounts are preauthorized so you wont get a window popping up asking for access. I am not a node.js programer so i cant help much with code. I dont see a service account example for node.js for google drive you may be able to find one for one of the other apis or check the client library sample examples
A service account is not you its a dummy user you can take the service account email address and share a folder or file on your personal google drive account with the service account and it will have access. read more about service accounts
I thought i would post how i got this to work (i need to look into checking if the file exists and then replace it, but that's later), firstly #DalmTo and #JoeClay had good points so i looked into them further and come across this blog post.
# index.js
const google = require('googleapis');
const fs = require('fs');
const config = require('./creds.json');
const drive = google.drive('v3');
const targetFolderId = "123456789"
const jwtClient = new google.auth.JWT(
config.client_email,
null,
config.private_key,
['https://www.googleapis.com/auth/drive'],
null
);
jwtClient.authorize((authErr) => {
if (authErr) {
console.log(authErr);
return;
}
const fileMetadata = {
name: './file.txt,
parents: [targetFolderId]
};
const media = {
mimeType: 'application/vnd.ms-excel',
body: fs.createReadStream('./file.txt' )
};
drive.files.create({
auth: jwtClient,
resource: fileMetadata,
media,
fields: 'id'
}, (err, file) => {
if (err) {
console.log(err);
return;
}
console.log('Uploaded File Id: ', file.data.id);
});
});
As I said previously my next step is to check if the file exists and if it does replace it

Getting Error: unauthorized_client when trying to authorize script

I've created service account with domain wide delegation and its scopes (in Admin console and Developer console) as described in documentation. I've been trying this for a week now and I am stuck. This is my code:
const google = require('googleapis');
const gmail = google.gmail('v1');
const directory = google.admin('directory_v1');
const scopes = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/admin.directory.user.readonly'
];
const key = require('./service_key.json');
var authClient = new google.auth.JWT(
key.client_email,
key,
key.private_key,
scopes,
"kruno#example.com"
);
authClient.authorize(function(err, tokens){
if (err) {
console.log(err);
return;
}
// API call methods here...
});
I get this error:
Error: unauthorized_client
I am unable to understand:
Is this proper technique for calling Google API methods from server-side scripts without any user interaction? (under domain only)
How do service account and actual user account communicate this way?
I heard about callback URI, am I missing it?
I think you are missing the final step which is giving access to your application in the control panel of your domain.
You can follow doc properly to activate it with your application
https://developers.google.com/+/domains/authentication/delegation
Also you can start with your first call step here
https://developers.google.com/adwords/api/docs/guides/first-api-call

Resources