google sheets api connection - error JWT cannot be invoked without 'new' - node.js

i try to set connection to google sheets api with this code:
const { google } = require("googleapis");
const keys = require("./keys.json");
const client = google.auth.JWT(keys.client_email, null, keys.private_key, [
"https://www.googleapis.com/auth/spreadsheets",
]);
client.authorized(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("connected");
}
});
i get this error when run on vscode:
TypeError: Class constructor JWT cannot be invoked without 'new'
thank you

Modification points:
In this case, please add new like new google.auth.JWT(). This is from your error message.
client has not method of authorized(). Please modify to authorize().
When above points are reflected to your script, it becomes as follows.
Modified formula:
const { google } = require("googleapis");
const keys = require("./keys.json");
const client = new google.auth.JWT(keys.client_email, null, keys.private_key, [
"https://www.googleapis.com/auth/spreadsheets",
]);
client.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("connected");
}
});
const sheets = google.sheets({ version: "v4", auth: client }); // You can use the methods of Sheets API by this "sheets".
Note:
In this modification, it supposes that your keys.json is the file including the valid credential values of the service account. Please be careful this.
As the additional information, when you want to access to the Google Spreadsheet of your Google Drive, please share your Google Spreadsheet with the email of the service account. By this, you can access to it.

Related

Node.js Google Sheets API: can read but not create spreadsheet

I have been working all day to try to get a Node.js application connected to my Google Drive to programmatically create spreadsheets using the Google Sheets API.
I think I have set up my connection correctly, because the following code block executes correctly:
/**
* Load or request or authorization to call APIs.
*
*/
async function authorize() {
let client = await loadSavedCredentialsIfExist();
if (client) {
return client;
}
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) {
await saveCredentials(client);
}
return client;
}
/**
* Prints the names and majors of students in a sample spreadsheet:
* #see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
* #param {google.auth.OAuth2} auth The authenticated Google OAuth client.
*/
async function listMajors(auth) {
const sheets = google.sheets({ version: 'v4', auth });
const res = await sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
});
const rows = res.data.values;
if (!rows || rows.length === 0) {
console.log('No data found.');
return;
}
console.log('Name, Major:');
rows.forEach((row) => {
// Print columns A and E, which correspond to indices 0 and 4.
console.log(`${row[0]}, ${row[4]}`);
});
}
authorize().then(listMajors).catch(console.error);
As soon as I move from that code to the following code, I get 403 Insufficient Permission errors:
/**
* Create a google spreadsheet
* #param {string} title Spreadsheets title
* #return {string} Created spreadsheets ID
*/
async function create(title) {
const { GoogleAuth } = require('google-auth-library');
const { google } = require('googleapis');
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/spreadsheets',
});
const service = google.sheets({ version: 'v4', auth });
const resource = {
properties: {
title,
},
};
try {
const spreadsheet = await service.spreadsheets.create({
resource,
fields: 'spreadsheetId',
});
console.log(`Spreadsheet ID: ${spreadsheet.data.spreadsheetId}`);
return spreadsheet.data.spreadsheetId;
} catch (err) {
// TODO (developer) - Handle exception
throw err;
}
}
authorize().then(create).catch(console.error);
I have tried this using OAuth2 Client ID, Service Account, and Application Default Credentials. I have enabled all Scopes for my application. I really don't understand what else it wants me to configure to tell it I can have access.
I am attempting to access an application created using my personal Gmail address by logging in using my personal Gmail address. When trying to use a Service Account, I created a new folder in Drive and gave it permissions.
I do not see any additional info in the Create a spreadsheet tutorial.
What other permission is it expecting me to grant and where?
In my case, the application of my scopes took quite a bit longer than I anticipated. Once they were added to my application, the code worked correctly. I would estimate that it took 4-5 hours for the updates to finally show up.

Is there anyway to remove access of google sheet to already shared users by using google sheet api or google drive api , etc in node js?

I am using google sheets api as well as google drive api (node js) and I have created google sheet by using service account and shared with one email "xyz#gmail.com" with role of 'reader'. but now after 5 minutes, I want to remove the access from "xyz#gmail.com", so that it must not be accessible to 'xyz#gmail.com".
Note: the owner of google sheet is service account.
Below is the snippet of code.
const auth = new google.auth.GoogleAuth({
keyFile: "private_keys.json", //the key file
//url to spreadsheets API
scopes: ["https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive"],
});
var body = {
'type': 'user',
'role': 'reader',
'emailAddress': 'xyz#gmail.com',
};
var drive=google.drive({version: 'v3',auth});
drive.permissions.create({
'fileId': spId, //sheetID returned from create sheet response
'resource': body,
}, function(err, response) {if (err) {
console.error('error-------', err);
return;
} else{
console.log(JSON.parse(JSON.stringify(response))) ;
}
});
I believe your goal is as follows.
You want to delete the permission of xyz#gmail.com from a file using googleapis for Node.js.
In this case, how about the following sample script?
Sample script:
const spId = "###"; // Please set the file ID.
const email = "xyz#gmail.com"; // Please set the email address you want to delete the permission.
drive.permissions.list(
{ fileId: spId, fields: "permissions(emailAddress,id)" },
function (err, response) {
if (err) {
console.error("error-------", err);
return;
} else {
var permission = response.data.permissions.find(({ emailAddress }) => emailAddress == email);
if (permission) {
drive.permissions.delete({ fileId: spId, permissionId: permission.id },
function (err, response) {
if (err) {
console.error("error-------", err);
return;
} else {
console.log(JSON.parse(JSON.stringify(response)));
}
});
}
}
});
When this script is run, the permission ID is searched using the email address. And, when the permission is found, the permission is deleted.
References:
Permissions: list
Permissions: delete

How to create a spreadsheet file with the google drive API, and set the default tab title to something other than "Sheet1"

I want to create a spreadsheet via the google drive(v3) API where:
I upload a CSV of data to populate the first tab
I am able to set
the name of the tab to something other than "Sheet1"
I spent all night crawling the google API for sheets(v4) and drive(v3), but still can't figure this one out!
If I can't do it this way, it seems like I will have to send an additional request to update the sheet properties to change the title after I do the initial upload. I'd like to avoid that if possible, but I realize it might be the only way.
Here's the API request I'm sending:
let fileMetadata = {
name: name,
title: 'rawData', //This does NOT get set! Tab appears as "Sheet1"
mimeType: 'application/vnd.google-apps.spreadsheet'
}
let media = {
mimeType: 'text/csv',
body: data // The body data is the raw parsed CSV
}
var Google = google.drive('v3')
Google.files.create({
auth: auth,
media: media,
resource: fileMetadata
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err)
return done(err)
} else {
console.log('success!')
console.log(response)
}
})
I try creating a new spreadsheet using Sheet API, then use Method: spreadsheets.create. Using the Specific API like Sheets API gives you more of specialize method like adding properties of Sheet Title (for more sheets specific methods).
Creates a spreadsheet, returning the newly created spreadsheet.
// BEFORE RUNNING:
// ---------------
// 1. If not already done, enable the Google Sheets API
// and check the quota for your project at
// https://console.developers.google.com/apis/api/sheets
// 2. Install the Node.js client library by running
// `npm install googleapis --save`
var google = require('googleapis');
var sheets = google.sheets('v4');
authorize(function(authClient) {
var request = {
resource: {
// TODO: Add desired properties to the request body.
},
auth: authClient
};
sheets.spreadsheets.create(request, function(err, response) {
if (err) {
console.log(err);
return;
}
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
});
});
function authorize(callback) {
// TODO: Change placeholder below to generate authentication credentials. See
// https://developers.google.com/sheets/quickstart/nodejs#step_3_set_up_the_sample
//
// Authorize using one of the following scopes:
// 'https://www.googleapis.com/auth/drive'
// 'https://www.googleapis.com/auth/spreadsheets'
var authClient = null;
if (authClient == null) {
console.log('authentication failed');
return;
}
callback(authClient);
}
I used the Try this API
Resulting to:
This will also be in your Google Drive.
Hope this helps.

users.list returns 403 Error: Not Authorized to access this resource/api

I am trying to retrieve a list of users using the node.js googleapis library and a service account.
I followed this guide to 'Perform Google Apps Domain-Wide Delegation of Authority'. There are examples for Java and Python, but unfortunately not for node.js, which seems to work rather differently.
I tried following the quickstart and completed the first two steps, but then it uses a manual OAuth flow instead of a service account.
So I tried to follow the example here to authorize using a service account. That all seems to work until I send the request, then I get an error: Error: Not Authorized to access this resource/api with code: 403.
Here's my code:
var google = require('googleapis'),
GoogleAuth = require('google-auth-library'),
authFactory = new GoogleAuth(),
admin = google.admin('directory_v1')
authFactory.getApplicationDefault(function (err, authClient) {
console.log('GOT APPLICATION DEFAULT', authClient)
if (err) {
console.log('Authentication failed because of ', err);
return;
}
if (authClient.createScopedRequired && authClient.createScopedRequired()) {
console.log('SCOPE REQUIRED')
var scopes = ['https://www.googleapis.com/auth/admin.directory.user'];
authClient = authClient.createScoped(scopes);
}
var request = {
auth: authClient,
domain: 'mydomain.com'
};
console.log('request:', request)
admin.users.list(request, function (err, result) {
if (err) {
console.log('admin.users.list error', err);
} else {
console.log(result);
}
});
});
What have I missed please?
After several hours of experimenting I came to the conclusion that this particular API cannot be accessed with a service account. Although it is not explicitly stated in the docs anywhere that I could find, the quickstart seems to overcome this limitation by using an OAuth process and then storing in a file the tokens required to authorize future requests. If I'm wrong please add a better answer!
My solution is to use the quickstart project to generate those tokens and then add the credentials and tokens from the quickstart to my project and use them whenever my server starts, something like:
let tokens = require('./credentials/tokens.json'),
credentials = require('./credentials/oauth_credentials.json'),
clientSecret = credentials.installed.client_secret,
clientId = credentials.installed.client_id,
redirectUrl = credentials.installed.redirect_uris[0],
google = require('googleapis'),
GoogleAuth = require('google-auth-library'),
authFactory = new GoogleAuth(),
admin = google.admin('directory_v1'),
oauth2Client = new authFactory.OAuth2(clientId, clientSecret, redirectUrl);
oauth2Client.credentials = tokens;
let request = {
auth: oauth2Client,
domain: 'coachaxis.com'
};
console.log('request:', request)
admin.users.list(request, function (err, result) {
if (err) {
console.log('admin.users.list error', err);
} else {
console.log(result);
}
});
It's not elegant but it works.

Node.js: Google Analytics API: How do I make authorized read-only Management API requests with a "service account"?

I'm creating an LED grid ticker run on a Raspberry Pi that will collect Google Analytics data via the Google Analytics API using Node.js. It will tally up counts from multiple websites then scrolling the stats across the grid.
I could use help listing account items via the Management API, specifically account id, property ids, and view ids to save on manual work. I'm getting errors in this area (see below).
This seemed like the job for a Service Account. After setting up a Google Cloud project here, then setting up a service account here, I added the email address contained within the downloaded authorization as a property user (read/analyze permissions) where applicable in Google Analytics admin settings. With this, I can successfully retrieve Analytics data for a known view ID (looked up manually in Google Analytics admin, ID shown on a view settings page), with this:
'use strict';
//////////////
// includes //
//////////////
var google = require('googleapis');
var fs = require('fs');
var Promise = require('promise');
//////////////
// settings //
//////////////
//the service account auth file downloaded from Google Cloud
var serviceAccountInfoPath = './secrets.json';
//view id with 'ga:' prefix
//I want this to soon be an array of ids looked up by an authenticated API requested
var viewID = 'ga:999999999';
////////
// go //
////////
// go get it all
var go = function() {
loadServiceAccountInfo()
.then(function(serviceAccountInfo) {
return getJwtClient(serviceAccountInfo);
})
.then(function(jwtClient) {
var client = jwtClient;
var analytics = google.analytics('v3');
return getThisYearViewAnalytics(client, analytics, viewID);
})
.then(function(result) {
console.log('total users: ' + result.totalsForAllResults["ga:users"]);
console.log('total sessions: ' + result.totalsForAllResults["ga:pageviews"]);
console.log('total pageviews: ' + result.totalsForAllResults["ga:sessions"]);
})
.catch(function(error) {
console.log("go promise chain failed", error);
});
};
// load the Google service account login details
var loadServiceAccountInfo = function() {
return new Promise(function(resolve, reject) {
fs.readFile(serviceAccountInfoPath, 'utf8', function(error, serviceAccountInfoData) {
if(error) {
console.log('loadServiceAccountInfo failed: ' + error);
reject(error);
} else {
var serviceAccountInfo = JSON.parse(serviceAccountInfoData);
resolve(serviceAccountInfo);
}
});
});
};
//return a an authenticated Google API client
var getJwtClient = function(serviceAccountInfo) {
return new Promise(function(resolve, reject) {
var jwtClient = new google.auth.JWT(serviceAccountInfo.client_email, null, serviceAccountInfo.private_key, ['https://www.googleapis.com/auth/analytics.readonly'], null);
jwtClient.authorize(function (error, tokens) {
if (error) {
console.log('getJwtClient failed: ' + error);
reject(error);
} else {
resolve(jwtClient);
}
});
});
};
//this is a test query to get the last year of data for a single view id
var getThisYearViewAnalytics = function(client, analytics, viewID) {
return new Promise(function(resolve, reject) {
analytics.data.ga.get({
'auth': client,
'ids': viewID,
'metrics': 'ga:sessions,ga:pageviews,ga:users',
'start-date': '365daysAgo',
'end-date': 'today',
},
function (error, response) {
if (error) {
console.log('getThisYearViewAnalytics failed: ' + error);
reject(error);
} else {
resolve(response);
}
});
});
};
//ready, set, go!
go();
This is good progress, but I start running into authentication errors when attempting to list accounts, with id(s) needed to list views (which also gives the same errors if you knew the account id).
I'm guessing that the answer is that I need to use OAuth, but if so, how? Is a service account no longer an option? I'm not sure how I would proceed with this when the service account and hardware device don't have usual OAuth back-and-forth user interaction to work with. Perhaps I'm just setting something wrong in the JWT Client being used above?
UDPATE:
Added info for those who read this in the future: After making sure read permissions were properly set, I was able to list Google Analytics accounts with this:
analytics.management.accounts.list({'auth': client}, function(error, response) {
console.log(error);
console.log(response);
});
Note: This does not follow the promise pattern I was using above, but gives the idea of how node.js googleapis module works.
When using a service account with the Google Analytics api. You need to be sure that you add the permission at the account level. I don't know why it needs to be the account level I just know that it doesn't work right any other way.

Resources