Google Drive API Service Account inside domain - node.js

I have to download/upload/delete files from a folder on Drive with a Node.js server. That folder is inside the G Suite of the company, and only a few people in the company has access.
I have to use a Service Account to do this, the question is: is it possible? How can I do that?
I already read https://developers.google.com/drive/v2/web/delegation and https://developers.google.com/identity/protocols/OAuth2ServiceAccount
but I don't know if it is possible give permissions to a service account to access a folder inside the domain of the company, because the service account is #developer.gserviceaccount.com and the domain of the company is other, so gives me an error when I try to add that service account to the folder.
If you could guide me on this, I'll be very greatful.
Thanks!

You can use an oAuth token with the rights scope(s):
const path = require('path');
module.exports = (app) => {
const factory = {};
factory.connect = (done) => {
const fs = require('fs');
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const SCOPES = [
'https://www.googleapis.com/auth/drive.metadata.readonly'
];
const TOKEN_DIR = path.resolve(app.root, 'server','config');
const TOKEN_PATH = path.resolve(TOKEN_DIR,'token.json');
const creds = require(path.resolve(app.root, 'server', 'config', 'google_oauth.json'));
authorize(creds, (ret) => {
done(null, ret);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const clientSecret = credentials.installed.client_secret;
const clientId = credentials.installed.client_id;
const redirectUrl = credentials.installed.redirect_uris[0];
const auth = new googleAuth();
const oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
console.error('[ERROR] Unable to read token', err)
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function (code) {
rl.close();
oauth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
};
return factory
};
and then factory.connect(done) will give done an auth to use googleapis:
const google = require('googleapis');
const service = google.drive('v3');
service.files.list({
auth,
pageSize: 10,
fields: 'nextPageToken, files(id, name)'
}, step);

Related

How to get new access token using the refresh token while working with google sheets API OAuth?

I am trying to use this google's API documentation function but it returns me the same credentials which is passed to it. In authorize() it gives me the accesstoken and refreshtoken in last return client but with the first return it returns the same refresh token which was created initially.
const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const {authenticate} = require('#google-cloud/local-auth');
const {google} = require('googleapis');
/**
* Reads previously authorized credentials from the save file.
*
* #return {Promise<OAuth2Client|null>}
*/
async function loadSavedCredentialsIfExist() {
try {
const content = await fs.readFile(TOKEN_PATH);
const credentials = JSON.parse(content);
return google.auth.fromJSON(credentials);
} catch (err) {
return null;
}
}
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;
}
authorize();
In TOKEN_PATH file the below content is stored:
{
"type": "authorized_user",
"client_id": "43242424.apps.googleusercontent.com",
"client_secret": "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kQ",
"refresh_token": "ejjkdsfw8uw29e32h092hf20"
}
source: https://developers.google.com/sheets/api/quickstart/nodejs
Overall, I am trying to understand more what does this line of code do excatly:
return google.auth.fromJSON(credentials);

Verify email content using mocha

I am using Node JS, mocha and googleapi writing tests to verify email content
When i run the googleapi as a standalone node js file i am able to get mails but when i integrate it with mocha tests i am not seeing any result, please help
test spec file (verify.js)
var checkEmail = require('../shared/checkEmail');
const { google } = require('googleapis');
var expect = require('chai').expect;
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
const { isNullOrUndefined } = require('util');
chai.use(chaiAsPromised);
chai.should();
var emailRecords = new Array();
var content;
var auth;
describe('GMAIL Testing', function () {
before('Connect to Gmail server', function () {
content = checkEmail.getAuthentication();
auth = checkEmail.authorize(content);
// Random test data
var a = [{ "Id": "123", "MsgId": "34677", "Type": "aaa", "Subject": "subxxxx", "ToAddress": "abc#gmail.com", "ToName": "ABC", "DateCreated": "2020-07-09T18:25:38.047Z" }];
emailRecords.push(a);
var b = [{ "Id": "456", "MsgId": "34655", "Type": "bbb", "Subject": "subject", "ToAddress": "abc#gmail.com", "ToName": "ABC", "DateCreated": "2020-06-09T18:25:38.047Z" }];
emailRecords.push(b);
});
it('Gmail Verification', function () {
emailRecords.forEach(element => {
const gmail = google.gmail({ version: 'v1', auth });
var query = "from:noreply#somedomain.com " + element[0].MsgId;
console.log('getting mail '+ element[0].MsgId);
gmail.users.messages.list({
userId: 'me',
q: query
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
var mails = res.data.messages;
console.log('mail(s) found');
expect(mails.length).to.be.at.least(1);
});
console.log('completed search');
});
});
});
Utility File checkEmail.js Ref -> Gmail API
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
var base64 = require('js-base64').Base64;
const cheerio = require('cheerio');
var open = require('open');
const { isNullOrUndefined } = require('util');
var Mailparser = require('mailparser').MailParser;
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
module.exports = new class checkEmail {
getAuthentication() {
// Load client secrets from a local file.
console.log('getting auth');
this.content = JSON.parse(fs.readFileSync('shared/config/credentials.json', 'utf8'));
return this.content;
}
authorize(credentials) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
var token = fs.readFileSync(TOKEN_PATH, 'utf-8');
if (token == isNullOrUndefined) {
token = getNewToken(oAuth2Client);
}
oAuth2Client.setCredentials(JSON.parse(token));
return oAuth2Client;
}
getNewToken(oAuth2Client) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
var newToken;
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
newToken = token;
});
});
return newToken;
}
}
I have tried adding debug messages but nothing is printed nor any error is thrown, Please let me know if i am missing something
output after running tests
> .\node_modules.bin\mocha .\test\verify.js
Tests are appearing to be passed but console.log('mail(s) found');
didnt show up in the output
Your test is finishing before your network request finishes.
See this section in the mocha docs.
https://mochajs.org/#asynchronous-code
You need to either use the done callback or return a promise.
If you can use async/await I find this to be the easiest because an async function always returns a promise: https://mochajs.org/#using-async-await

UnhandledPromiseRejectionWarning: ReferenceError: message is not defined Discord.js

I'm in the process of creating a Discord bot that will read from a specific Google Sheets spreadsheet and this error keeps coming up as I am trying to integrate the Google Sheets functions. See Github Repo and please know I'm extremely new at Node.js.
For this function, I have created index.js to get everything running for both discord.js and the Google API:
require("dotenv").config();
const fs = require("fs");
const Discord = require("discord.js");
const client = new Discord.Client();
const readline = require('readline');
const { google } = require('googleapis');
const OAuth2Client = google.auth.OAuth2;
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
const TOKEN_PATH = 'token.json';
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
const authorize = function (credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
const getNewToken = function (oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return callback(err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
//pulls all commands and events
fs.readdir("./events/", (err, files) => {
files.forEach(file => {
const eventHandler = require(`./events/${file}`);
const eventName = file.split(".")[0];
client.on(eventName, (...args) => eventHandler(client, ...args));
});
});
client.login(process.env.BOT_TOKEN);
module.exports = {
authorize,
google
}
Sheets.js contains the readAll function for Sheets I want to use so when someone types in assignments!all the function should run:
const {authorize, google} = require('./index');
const fs = require("fs");
const readline = require('readline');
const Discord = require("discord.js");
const client = new Discord.Client();
const spreadsheetId = "1asvhCVI1sC6Q2cCuqdUrRqFHkH2VCr5FM7kWSm8VBE8";
//read entire spreadsheet
const readAll = (range) => {
fs.readFile('client_secret.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content), (auth) => {
const sheets = google.sheets({ version: 'v4', auth });
sheets.spreadsheets.values.get({
spreadsheetId, range
}, (err, res) => {
if (err) return console.log('The API returned an error:' + err);
const rows = res.data.values;
if (rows.length) {
// Print columns B through F.
rows.map((row) => {
message.reply(`Details: ${row[0]}, Link: ${row[1]}, Additional Link: ${row[2]}, Assigned To: ${row[3]}, Assigned On: ${row[4]}, Ready for QC on: ${row[5]}, QC Link: ${row[6]}`);
});
} else {
console.log('No data found.');
}
});
});
});
}
module.exports = {
readAll
};
As I plan to have the bot do more functions in the future, I have created a message.js file in an events folder to call commands from a commands folder:
const assignments = require('../commands/assignments');
const assign = require('../commands/assign');
const qc = require('../commands/qc');
const qcList = require('../commands/qcList');
const sheets = require('../sheets');
module.exports = (client, message) => {
if (message.content.startsWith('assignments!')) {
return assignments(message);
} else if (message.content.startsWith('assign!')) {
return assign(message);
} else if (message.content.startsWith('qc!')) {
return qc(message);
} else if (message.content.startsWith('qclist!')) {
return qcList(message);
}
};
Assignments.js actually tells the bot that when the word "all" is used after the ! in "assignments!", that is when it is suppose to run the ReadAll function on the specific tab in the Sheets spreadsheet:
// gets all assignments or assignments based on name given after !
module.exports = (message, google, authorize, readAll) => {
const sheets = require('../sheets');
//ex message: assignments!Brody
//gets name after ! and stores as member variable
var messageContent = message.content;
var member = messageContent.split('!')[1];
var member = member.toLowerCase();
//test reading spreadsheet
if(member === 'all') {
sheets.readAll("Active News Assignments!B2:F");
}
//error messages
if (!member) {
message.reply("You must add a name or 'all' after the command.");
}
};
Again, I am very new to Node.js and following these tutorials to create this bot. Please feel free point out anything obvious I'm missing or ways I can simplify my code once it's working as I know I will be adding more functions in the future.
https://levelup.gitconnected.com/making-a-custom-discord-bot-in-discord-js-1e17f2090919
https://www.ishaanrawat.com/integrate-google-sheets-api-with-node-js/

Google Api, Node.js - invalid_grant Malformed auth code

I'm trying to use the google api on node.js, but I always have the following error:
invalid_grant
Malformed auth code
By searching on the web I saw that the client id should be an email address, not the client id on the console. I changed that to the email address of my google account, it was even worse because I wasn't able to connect using google
Here's the code, adapted from https://developers.google.com/youtube/v3/docs/videos/list
var fs = require('fs');
var readline = require('readline');
var { google } = require('googleapis');
// var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/google-apis-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'google-apis-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the YouTube API.
//See full code sample for authorize() function code.
authorize(JSON.parse(content), {'params': {'id': 'Ks-_Mh1QhMc',
'part': 'snippet,contentDetails,statistics'}}, videosListById);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, requestData, callback) {
// Regarder response.config.data
var clientSecret = credentials.client_secret;
var clientId = credentials.client_id;
var redirectUrl = credentials.redirect_uri;
var auth = google.auth.OAuth2;
var oauth2Client = new google.auth.OAuth2(
clientId,
clientSecret,
redirectUrl
);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, requestData, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client, requestData);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, requestData, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token');
console.log(err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client, requestData);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Remove parameters that do not have values.
*
* #param {Object} params A list of key-value pairs representing request
* parameters and their values.
* #return {Object} The params object minus parameters with no values set.
*/
function removeEmptyParameters(params) {
for (var p in params) {
if (!params[p] || params[p] == 'undefined') {
delete params[p];
}
}
return params;
}
/**
* Create a JSON object, representing an API resource, from a list of
* properties and their values.
*
* #param {Object} properties A list of key-value pairs representing resource
* properties and their values.
* #return {Object} A JSON object. The function nests properties based on
* periods (.) in property names.
*/
function createResource(properties) {
var resource = {};
var normalizedProps = properties;
for (var p in properties) {
var value = properties[p];
if (p && p.substr(-2, 2) == '[]') {
var adjustedName = p.replace('[]', '');
if (value) {
normalizedProps[adjustedName] = value.split(',');
}
delete normalizedProps[p];
}
}
for (var p in normalizedProps) {
// Leave properties that don't have values out of inserted resource.
if (normalizedProps.hasOwnProperty(p) && normalizedProps[p]) {
var propArray = p.split('.');
var ref = resource;
for (var pa = 0; pa < propArray.length; pa++) {
var key = propArray[pa];
if (pa == propArray.length - 1) {
ref[key] = normalizedProps[p];
} else {
ref = ref[key] = ref[key] || {};
}
}
};
}
return resource;
}
function videosListById(auth, requestData) {
var service = google.youtube('v3');
var parameters = removeEmptyParameters(requestData['params']);
parameters['auth'] = auth;
service.videos.list(parameters, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
console.log(response);
});
I literally copy-paste the code in the console, but still have the same error
Here is my case:
The authorization code in the url hash fragment is being encoded by encodeURIComponent API, so if you pass this code to request the access token. It will throw an error:
{ "error": "invalid_grant", "error_description": "Malformed auth code." }
So I use decodeURIComponent to decode the authorization code.
decodeURIComponent('4%2F_QCXwy-PG5Ub_JTiL7ULaCVb6K-Jsv45c7TPqPsG2-sCPYMTseEtqHWcU_ynqWQJB3Vuw5Ad1etoWqNPBaGvGHY')
After decode, the authorization code is:
"4/_QCXwy-PG5Ub_JTiL7ULaCVb6K-Jsv45c7TPqPsG2-sCPYMTseEtqHWcU_ynqWQJB3Vuw5Ad1etoWqNPBaGvGHY"
Generally the URL is Encoded, So decode the URL and try again
Try URL Encode/Decode Tool click here
In python it can be done as below:
import requests
from urllib.parse import unquote
# Decode the url
query_str = unquote(request.META.get('QUERY_STRING'))
# Or just decode CODE
code = unquote(code)
data_dict = {
"code": code, "redirect_uri":"", "grant_type": "authorization_code",
"client_id": "","client_secret": ""
}
resp = requests.post('https://oauth2.googleapis.com/token', data_dict)

How to get user specific data on a Google Analytics request?

I'm trying to create a web app where a user can grant access to her Google Analytics account via OAuth2. After positive response I would like to make a request to that user's GA data (in the real application the request would be made "offline"). But when calling:
google.analytics('v3').data.ga.get(params, callback);
params should contain ids, which should be a list of "table ID"s from the user. How do I get hold of these IDs? Is it necessary to get this information through another profile-scoped-request?
Code:
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var clientId = '123-123.apps.googleusercontent.com';
var clientSecret = 'abc';
var redirectUrl = 'http://localhost:8080/redirect';
var authRequest = function(req, res) {
var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
var scopes = [ 'https://www.googleapis.com/auth/analytics.readonly' ],
params = { state: 'some-data', access_type: 'offline', scope: scopes };
var url = oauth2Client.generateAuthUrl(params);
res.redirect(301, url); // will return to "authResult()"
};
var _sampleAnalytics = function(req, res, oauthClient) {
var params = {
auth: oauthClient,
metrics: 'ga:visitors,ga:visits,ga:pageviews',
'start-date': '2015-06-01',
'end-date': '2015-06-30',
ids: ['ga:123456789'] // <== How to get this parameter?
};
google.analytics('v3').data.ga.get(params, function(err, response) {
// todo
});
};
var authResult = function (req, res) {
if (req.query.error) {
return handleError(res, req.query.error);
}
var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(err) {
return handleError(res, err);
} else {
oauth2Client.setCredentials(tokens);
_sampleAnalytics(req, res, oauth2Client);
}
});
};
Ok, that was simple. I just need to make another call to:
google.analytics('v3').management.accountSummaries.list(params, function(err, result) {
// ...
});
result will contain all the required information.

Resources