use of r1.question when using google sheets api - node.js

I am using the documentation found at https://developers.google.com/sheets/api/quickstart/nodejs in order to use the google sheets api in my node.js application. For reference, the code I'm using from the page is as below:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.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';
// Load client secrets from a local file.
fs.readFile('credentials.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), listMajors);
});
/**
* 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 {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
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.
*/
function getNewToken(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 console.error('Error while trying to retrieve 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);
});
callback(oAuth2Client);
});
});
}
/**
* 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.
*/
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
At the line where it says rl.question('Enter the code from that page here: ', (code) => { I get the feeling that I'm supposed to input something, but I never see any obvious prompt to enter any kind of code, and thus I do not enter anything and my code goes to return console.error('Error while trying to retrieve access token', err); and I apparently never get an access token. How exactly do I enter this code, and where do I get this code from?
EDIT: Detailed flow for replicating bug.
I have the following requires at the top of my code:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
Below is the route which I first access in the browser in order to initiate the process.
app.get("/test", function (req,res) {
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.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';
// Load client secrets from a local file.
fs.readFile('credentials.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), listMajors);
});
/**
* 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 {client_secret, client_id, redirect_uris} = credentials.installed;
var client_secret = credentials.web.client_secret;
var client_id = credentials.web.client_id;
var redirect_uris = credentials.web.redirect_uris;
const oAuth2Client = new google.auth.OAuth2(
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.
*/
function getNewToken(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 console.error('Error while trying to retrieve 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);
});
callback(oAuth2Client);
});
});
}
/**
* 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.
*/
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
})
When I access this route, the browser tries to load to something, but never gets there. In the debug console I get a prompt saying "Authorize this app by visiting this url: ..." and I go to the given url in the browser. I am already signed into google, so I am given a list of accounts to choose from where the browser says "Choose an account to continue to quickstart." I choose my google account, and then I get the "this app isn't verified screen", so I click "Advanced" towards the bottom of the screen, after which a "Go to Quickstart (unsafe)" link appears. I click this link, and then I seemingly go back to the previous oauth screen where I was given a list of accounts, except a prompt now appears asking to Grant Quickstart permission to view your google spreadsheets. I click "Allow", and then I am redirected to a page which looks similar to the previous Oath screen, except it's asking me to confirm my choices, make sure you trust quickstart etc., so I click Allow once again and I am finally redirected to the page I specified that I would go to by my redirect url. However, in my working directory I can never see token.json, and I never see the "Enter the code from that page here: " in the terminal (I am using VSCode and the terminal is set as windows powershell). When I try to enter the code I get from the url parameters (from the redirect page at the end) I get the error I mentioned before.

Related

connect the user to his google drive by email and password using nodejs

I'm trying to develop web app file management for that I want each user to be able to connect his google drive account I know that there is Oauth20_authorize but the user will need to provide client_id client_secret
what I need is to connect it only by email and password and have all permission read and write.
is it possible to implement
I think you are a little confused as to how to access google APIs.
I know that there is Oauth20_authorize but the user will need to provide client_id client_secret
The user does not provide the client id and client secret. You must register your application on google cloud console and create web application credentials. Your application owns the client id and client secrete.
Then your application will be able to request consent of the user to access their google drive account.
what I need is to connect it only by email and password and have all permission read and write.
You can not connect to a users private data using their email and password. this is called client login and was shut down by google in 2015.
You must use Oauth2 to access a users data.
May i suggest for starters that you follow the node.js quickstart
It will show you how to create a single user app. After that you can alter it for multi user.
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.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';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* 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 {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(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.
*/
function getAccessToken(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 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);
});
callback(oAuth2Client);
});
});
}
/**
* Lists the names and IDs of up to 10 files.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listFiles(auth) {
const drive = google.drive({version: 'v3', auth});
drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const files = res.data.files;
if (files.length) {
console.log('Files:');
files.map((file) => {
console.log(`${file.name} (${file.id})`);
});
} else {
console.log('No files found.');
}
});
}

Unauthorized Error when Fetching Google My Business accounts under an Authenticated Google Account

I am trying to list the businesses under an authenticated Google Account. I have done the Oauth2 flow and have gotten the object containing the access_token, id_token,refresh_token and expiry_date
Now when I tried listing the businesses under that account I keep getting Unauthorized Error.
Below is my flow:
initOAuth2Client() {
return new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URL
);
}
//Authorization Code
async authenticateAgainstGoogleMyBusiness() {
let oauth2Client = module.exports.initOAuth2Client();
const scopes = [
'https://www.googleapis.com/auth/business.manage',
'https://www.googleapis.com/auth/userinfo.profile'
];
const state = ....
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
state: state,
prompt: 'consent'
});
}
and then for my Oauth Callback Handler I have this
async googleOAuthCallbackHandler(req, res) {
let oauth2Client = module.exports.initOAuth2Client();
let { code, state } = req.query;
const oauth2Client = module.exports.initOAuth2Client();
const { tokens } = await oauth2Client.getToken(code);
//this is where I saved the tokens exactly as received.
}
async fetchGoogleMyBusinessAccounts() {
let {access_token} = fetchTokenFromDatabase(); //This is how I retrieved the access tokens saved by the previous function.
console.log(`Fetching GMB Accounts`);
let accountsUrls = `https://mybusinessaccountmanagement.googleapis.com/v1/accounts`;
try {
let headers = {
'Authorization': `Bearer ${access_token}`
}
let response = axios.get(accountsUrls, {
headers: headers
});
let data = response.data;
console.log(`GMB Accounts response = ${JSON.stringify(data, null, 2)}`);
} catch (e) {
console.log('Error retrieving GMB Accounts:Full error below:');
console.log(e); //I keep getting Unauthorized even though I authorized the process on the oauth consent screen
}
}
How can I fix this?
I think you should have a look at the sample for Google drive I dont know why Google doesnt release samples for all the APIs but they dont. I put this together by altering the sample for google drive a bit. I dont have node installed right now but this should be close
Let me know if you have any issues and i can try and help you debug them.
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/business.manage'];
// 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';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* 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 {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(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.
*/
function getAccessToken(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 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);
});
callback(oAuth2Client);
});
});
}
/**
* Lists the names and IDs of up to 10 files.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listFiles(auth) {
const service = google.mybusinessaccountmanagement({version: 'v1', auth});
service.accounts.get({
pageSize: 10,
fields: '*',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const accounts= res.accounts;
if (accounts.length) {
console.log('accounts:');
accounts.map((account) => {
console.log(`${account.name} (${account.id})`);
});
} else {
console.log('No files found.');
}
});
}
from comments
Lets have a look at this error message from the comments.
code: 429, errors: [ { message: "Quota exceeded for quota metric 'Requests' and limit 'Requests per minute' of service 'mybusinessaccountmanagement.googleapis.com' for consumer 'project_number:xxxxx'.", domain: 'global', reason: 'rateLimitExceeded' } ] }
When you created your project on google cloud platform you had to enable the api you were going to use.
If you go back and check the it by (tip manage button) on the left there is a note about Quota
Each api has a default quota for when you enable it. The default quota for this api is
Quota is what controls how many requests you can make to the api. Google limits us because they want us all to be able to use the api. They dont want just one app flooding the system.
As you can see the default quota for this api is 0 Which is why you are exceeding the limit already without making any requests.
At the top of this page there is a note that says
Request more quota limits or view quotas for your other services on the Quotas page, found in IAM & admin.
After a lot of digging i managed to find a link to the form for requesting additional quota Usage limits at the very bottom of the page there is a line that says
If you need higher limits, you can submit a Standard quota request.
Submit the form. I have no idea how long its going to take you to get a quota extension.

TypeError: Cannot read property 'redirect_uris' of undefined Youtube Data API authentication NodeJS

I have a web application through which I'd like to be able create playlists, add videos to a playlist, delete a video etc, to my youtube channel. I have created a service account and downloaded the service account credentials key file and set up my OAuth 2.0 Client IDs in the Google Developer Console.
To authenticate my app, I followed the instructions in the README.md for the google-api-nodejs-client here https://github.com/googleapis/google-api-nodejs-client - under service account credentials
Here is my controller file...
I should note that the project uses ES Modules and thus "type": "module" is set in package.json. This is why you'l notice for example that I am importing __dirname as a utility since ES Modules do not support the regular __dirname.
import googleapi from "googleapis";
const { google } = googleapi;
import Auth from "#google-cloud/local-auth";
const { authenticate } = Auth;
import path from "path";
import __dirname from "../utils/dirname.js";
async function initialize() {
try {
const auth = await authenticate({
keyfilePath: path.join(__dirname, "../service_account_credentials.json"),
scopes: ["https://www.googleapis.com/auth/youtube"],
});
console.log("Auth details");
console.log(auth);
google.options({ auth });
} catch (e) {
console.log(e);
}
}
initialize();
const oauth2Client = new google.auth.OAuth2(
"YOUR_CLIENT_ID",
"YOUR_CLIENT_SECRET",
"http://localhost:5000/oauth2callback"
);
// initialize the Youtube API library
const youtube = google.youtube({ version: "v3", auth: oauth2Client });
class YoutubeController {
static async createPlaylist(req, res) {
const { name } = req.body;
const playlist = await youtube.playlists.insert({
part: "snippet,status",
resource: {
snippet: {
title: name,
description: `${name} videos.`,
},
status: {
privacyStatus: "private",
},
},
});
res.json(playlist);
}
}
The initialize function, is the one that throws the error and I can't quite figure it out. I think because of that, when I make a POST request to the route that calls the method createPlaylist inside the class, I get back No access, refresh token or API key is set.
I've been going through the docs trying to understand how everything flows but I'm a little stuck.
A similar question was asked here - TypeError: Cannot read property 'redirect_uris' of undefined but there are no answers and the suggested workflow does not work for my case so I'd really appreciate your help on this.
service accounts
The YouTube API does not support service account authentication you need to use OAuth2.
OAuth2 Authorization
You might want to consider following the YouTube API quick start for nodejs.
The issue is that you are using service account authentication with the YouTube API which it does not support.
var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
var OAuth2 = google.auth.OAuth2;
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'youtube-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.
authorize(JSON.parse(content), getChannel);
});
/**
* 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) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (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) {
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', 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), (err) => {
if (err) throw err;
console.log('Token stored to ' + TOKEN_PATH);
});
}
/**
* Lists the names and IDs of up to 10 files.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function getChannel(auth) {
var service = google.youtube('v3');
service.channels.list({
auth: auth,
part: 'snippet,contentDetails,statistics',
forUsername: 'GoogleDevelopers'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var channels = response.data.items;
if (channels.length == 0) {
console.log('No channel found.');
} else {
console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
'it has %s views.',
channels[0].id,
channels[0].snippet.title,
channels[0].statistics.viewCount);
}
});
}
Code shamelessly ripped from YouTube API quick start for nodejs.
backend access
Due to the fact that YouTube API does not support service accounts. Accessing the data from a backend service can be tricky but it is not imposible.
Run your application locally once.
Aurhtoirze it to access your account data.
In your code find the credentials that are stored they should contain a refresh token.
Save this refresh token as part of your application.
set up your code to read this refresh token when loading.
Unfortunately i am not a node.js developer so i cant help you with the code required to do that. The library should be storing things into a credentials object if you can find that and how thats loaded then you should be able to do what i have suggested.
I would start by digging around into what ever storeToken(token); is doing.

How to use a Google service account in the place of oAuth2Client to use Google Apps API?

I've successfully used the oAuth2Client with Node using Google's Quickstart tutorial:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.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';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* 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 {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(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.
*/
function getAccessToken(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 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);
});
callback(oAuth2Client);
});
});
}
/**
* Lists the names and IDs of up to 10 files.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listFiles(auth) {
const drive = google.drive({version: 'v3', auth});
drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const files = res.data.files;
if (files.length) {
console.log('Files:');
files.map((file) => {
console.log(`${file.name} (${file.id})`);
});
} else {
console.log('No files found.');
}
});
}
However, I want to avoid the consent screen and do a service to service authentication.
I've found Node GToken to get a token, but where do I place it in the code, and how do I use it?
const key = '-----BEGIN RSA PRIVATE KEY-----\nXXXXXXXXXXX...';
const { GoogleToken } = require('gtoken');
const gtoken = new GoogleToken({
email: 'my_service_account_email#developer.gserviceaccount.com',
scope: ['https://scope1', 'https://scope2'], // or space-delimited string of scopes
key: key
});
I've also found the following code on how to implement Service to Service Authentication, but it seems to be implemented for Google's compute service. The example states:
For example, a JWT auth client will be created when your code is running on your local developer machine, and a Compute client will be created when the same code is running on a configured instance of Google Compute Engine.
const {google} = require('googleapis');
const compute = google.compute('v1');
async function main () {
// This method looks for the GCLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS
// environment variables.
const auth = await google.auth.getClient({
// Scopes can be specified either as an array or as a single, space-delimited string.
scopes: ['https://www.googleapis.com/auth/compute']
});
// obtain the current project Id
const project = await google.auth.getProjectId();
// Fetch the list of GCE zones within a project.
const res = await compute.zones.list({ project, auth });
console.log(res.data);
}
main().catch(console.error);
I've found code on other sites that uses googleapis.auth.JWT, but I can't find any documentation on that for service to service accounts used with Google Apps.
Here it says that a service account isn't what's needed. Unfortunately, I don't understand what the question is to understand if the answer applies to my situation, too. What I want to do is to authorize my node.js app to use my drive without the need for me to allow it to access every time.
I don't know how to translate the Quickstart code to accommodate a service account.
I don't have access to a "Google Drive for Business" account, so I'm posting what I could do with my personal Google Drive ("My Drive").
I also read on some pages on Internet that you can't share a "Team Drive", but maybe you could add the service account as a team member.
This code lists the files in a folder on "My Drive" after I shared the folder with a service account.
// load library
const { google } = require('googleapis');
// configure a JWT auth client
const privatekey = require("./privatekey.json");
const jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
[
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/drive.metadata.readonly'
]
);
jwtClient.authorize()
.then(function (tokens) {
// console.log(tokens);
console.log("Authentication successfull.\n");
// list the files once authenticated
listFiles(jwtClient);
})
.catch(function (error) {
throw (error);
});
function listFiles(auth) {
const drive = google.drive({ version: 'v3', auth });
drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
// q: "name contains 'foo'"
})
.then(res => {
console.log(res.data);
})
.catch(err => console.log(err));
}
privatekey.json is the private key of the service account (downloaded and renamed) from "Google Cloud Platform", where of course "Google Drive API" is enabled.
Inside privatekey.json you can find the email address for the service account which you could then try to add it to the "Team Drive".

can't get refresh token for google api. Node js

I've got an Apps Script script that I am running remotely through Google's API. When I go to the link it gives me for the code to retrieve the access token, it says that the file requested does not exist. However, the code is in the url anyway, this gives me the access token, but not the refresh token.
I have tried adding 'access_type: offline' and 'approval_prompt: force', but those didn't change anything. Here's the code:
var { google } = require('googleapis');
var fs = require('fs');
var async = require('async');
const readline = require('readline');
// If modifying these scopes, delete token.json.
const SCOPES = [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/spreadsheets"
];
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('./credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Apps Script API.
authorize(JSON.parse(content), callScriptFunction);
});
/**
* 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 {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2("294862899955-bb0929ato2qem8cqllggrpuqpqit191v.apps.googleusercontent.com", "Ds4-q0G3QZog4UamQrc3HFrW", "https://script.google.com/oauthcallback");
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(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.
*/
function getAccessToken(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 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) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function callScriptFunction(auth) {
var scriptId = "MwnuiFwldt-0ZLyLnoi0Q5kfoO49Cn6ao";
var script = google.script('v1');
script.scripts.run({
auth: auth,
resource: {
function: 'automateSheet',
parameters: [
process.argv[2],
process.argv[3],
process.argv[4],
process.argv[5],
process.argv[6],
process.argv[7],
process.argv[8],
]
},
scriptId: scriptId,
devMode: true
}, function (err, resp) {
if (err) {
console.log(err);
}
else {
var r = resp.data;
if ("error" in r) {
console.log("Error: %o", r.error);
} else {
console.log("Result: %o", r.response.result);
}
}
});
}
Here's the page Google gives me when I agree to allow the app access to my account:
i found the answer here:
Not receiving Google OAuth refresh token
where the guy said to go to https://myaccount.google.com/u/2/permissions?pli=1&pageId=none
then revoke your apps permission. it worked for me. because every time after the first time, the refresh token does not appear
I think there are two issues going on. One, it looks like your Authorized redirect URI isn't set correctly. It needs to be set to a page on your server that listens for the code URL parameter after the user authorizes. You can set it in the Google Cloud console under API > Credentials > 0Auth Client Settings > Authorized redirect URIs.
Second, I had an issue where sometimes I wouldn't get a refresh token. Turns out I had to add prompt: 'consent' to the generateAuthUrl request to force it to always return a refresh token, by always taking the user through the full authorization flow (instead of skipping steps if they recently authenticated). So your function would be:
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
prompt: 'consent',
});
Hope that helps. For reference: https://github.com/googleapis/google-api-nodejs-client/issues/750#issuecomment-368873635

Resources