How can I not confirm the action from Google api every time? - node.js

I have a Desktop App for Google Drive that create and set perms that i need. This app should work only for one account with google drive.
My problem is that when I launch an action, I always have to confirm this action in explorer. Can I somehow automatically send my data and confirmation to the server? I read about the access token, but it seems to be suitable only for web applications. I am based on the documentation from the Google API site.
And in future this should work from console.
My code right now:
const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const {authenticate} = require('#google-cloud/local-auth');
const {google} = require('googleapis');
const { AuthClient } = require('google-auth-library');
const SCOPES = ['https://www.googleapis.com/auth/drive'];
const TOKEN_PATH = path.join(process.cwd(), 'token.json');
const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json');
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 saveCredentails(client) {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content);
const key = keys.installed || keys.web;
const payload = JSON.stringify({
type: 'authorized_user',
access_type: 'offline',
client_id: key.client_id,
client_secred: key.client_secret,
refresh_token: client.credentials.refresh_token,
});
await fs.writeFile(TOKEN_PATH, payload);
}
async function authorize() {
let client = await loadSavedCredentialsIfExist();
if (client) {
return client;
}
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) {
await saveCredentails(client);
}
return client;
}
async function createFolder(authClient) {
const service = google.drive({version: 'v3', auth: authClient});
const fileMetadata = {
name: 'testmeows',
mimeType: 'application/vnd.google-apps.folder',
};
try {
const file = await service.files.create({
resource: fileMetadata,
fields: 'id',
});
console.log('Folder Id:', file.data.id);
const body = {"role": "writer", "type": "anyone"}
const result = await service.permissions.create({
resource: body,
fileId: file.data.id,
//fields: 'id',
});
const align = `https://drive.google.com/drive/folders/${file.data.id}?usp=sharing`;
console.log(align);
} catch (err) {
throw err;
}
}
//module.exports = test;ф
authorize().then(createFolder).catch(console.error);
Well, how to better get refresh token without user invention and opening explorer on Desktop App Google Api?

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);

Reduce Auth Requests

I am making a few node.js scripts using google-api-nodejs-client.
Here is the basic auth request to interact with the api:
const { google } = require("googleapis");
const auth = new google.auth.GoogleAuth({
keyFile: "credentials.json",
scopes: "https://www.googleapis.com/auth/spreadsheets",
});
const getAuthClient = async () => {
try {
return await auth.getClient();
} catch (error) {
console.error(error);
}
};
const sheetsClient = async () => {
const client = await getAuthClient();
return await google.sheets({ version: "v4", auth: client });
};
module.exports = { sheetsClient };
Now, whenever I create a function that needs to use the sheetsClient I need to set it up like this (the examples below are generic examples, I will have other calls to the api where I'll need to get the sheets client. In some cases I'll need to read (get the client) and the write (get the client again) in different functions called one after the other:
const { google } = require("googleapis");
const { sheetsClient } = require("./googleAuth");
const createSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.create({
resource: {
properties: {
title,
},
},
});
};
const updateSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.update({
resource: {
properties: {
title,
},
},
});
};
const deleteSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.delete({
resource: {
properties: {
title,
},
},
});
};
Is there a better way to get access to the client without having to call it everytime within a function?
there are many possibilities.
the easiest may be to call this only once, outside of all functions.
const { google } = require("googleapis");
const { sheetsClient } = require("./googleAuth");
// globally defined
const client = ( async () => await sheetsClient())();
// rest of code
const createSheet = async (name) => {
// deleted : const client = await sheetsClient();
const sheet = await client.spreadsheets.create({
resource: {
properties: {
title,
},
},
});
};
this will create a global client variable in this js file.
then you can remove its declaration from every function.
the code will still run smoothly but there will be only one authentication.
Another way to deal with your problem is to assure that the auth function really is executed only once by using a flag. (this solution is related to memoization)
var client = null;
const getAuthClient = async () => {
if (client) return client;
try {
client = await auth.getClient();
return client;
} catch (error) {
console.error(error);
}
};

how to refresh token on google oauth2 using firebase functions?

I developed an integration using Google Oauth2 inside firebase functions to access Google Sheets API. The integration works correctly but I'm having problems to make sure the refresh token is running correctly. The function stops working after the first token expires.
when this happens the following error occur:
Function execution started
Error: No refresh token is set.
at OAuth2Client.refreshTokenNoCache (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:161:19)
at OAuth2Client.refreshToken (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:142:25)
at OAuth2Client.getRequestMetadataAsync (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:256:28)
at OAuth2Client.requestAsync (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:329:34)
at OAuth2Client.request (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:323:25)
at createAPIRequestAsync (/workspace/node_modules/googleapis-common/build/src/apirequest.js:292:27)
at Object.createAPIRequest (/workspace/node_modules/googleapis-common/build/src/apirequest.js:43:9)
at Resource$Spreadsheets$Values.update (/workspace/node_modules/googleapis/build/src/apis/sheets/v4.js:601:37)
at exports.loadStripeData.functions.runWith.https.onRequest (/workspace/index.js:176:32)
at process._tickCallback (internal/process/next_tick.js:68:7)
I want to make sure the token refresh correctly and get stored on Firestore.
What am I doing wrong?
index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {google} = require('googleapis');
const sheets = google.sheets('v4');
admin.initializeApp();
const CLIENT_ID = 'CLIENT_ID';
const CLIENT_SECRET = 'CLIENT_SECRETT';
const REDIRECT_URL = 'https://us-central1-MY-PROJECT.cloudfunctions.net/oauth2callback';
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
oauth2Client.on('tokens', (tokens) => {
if (tokens.refresh_token) {
try {
admin.firestore()
.collection('oauth2')
.doc('google')
.set({
tokens: tokens.refresh_token,
});
} catch (error) {
console.error(JSON.stringify(error));
}
}
});
/*asks user permission to access his spreadsheets*/
exports.authenticate = functions.https.onRequest((req, res) => {
const authorizeUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES.join(','),
});
res.send(`<html>click here: ${authorizeUrl}</html>`)
});
/*callback function for when the user finishes authenticating*/
exports.oauth2callback = functions.https.onRequest(async(req, res) => {
const code = req.query.code.toString() || '';
try {
await admin.firestore()
.collection('oauth2')
.doc('google')
.set({
code: decodeURIComponent(code)
});
} catch(error) {
res.send(JSON.stringify(error))
}
res.send('auth successfully. You can close this tab');
});
/* get token from Firestone to execute function*/
async function oauth2Auth() {
const doc = await admin.firestore()
.collection('oauth2')
.doc('google')
.get();
const credentials = doc.data();
if (credentials.code !== undefined) {
const response = await oauth2Client.getToken(credentials.code);
credentials.tokens = response.tokens;
delete credentials.code;
try {
await admin.firestore()
.collection('oauth2')
.doc('google')
.set({
tokens: credentials.tokens,
})
} catch (error) {
console.error(error);
}
}
oauth2Client.setCredentials(credentials.tokens);
}
/*function that requires google sheets api*/
exports.mainFunction = functions.https.onRequest(async(req, res) => {
oauth2Auth();
//do main function
});
Finally discovered the problem!
You only get the refreshing token in the first time you ask for authorization. So if you're don't save it correctly you have to ask permission again.
To solve it:
when redirecting the user to the authorization URL add the following parameters to have sure you get the refreshing token:
access_type=offline&prompt=consent
to save the refreshing token:
oauth2Client.on('tokens', async(tokens:any) => {
if (tokens.refresh_token) {
try {
const authorization = await oauth2Client.getToken(tokens.refresh_token);
await admin.firestore()
.collection('collectionName')
.doc(docId)
.update({
token: authorization.tokens
})
} catch (error) {
console.error(JSON.stringify(error));
}
}
});

Issue with Google oAuth2 callback using Firebase functions

I would like use Firebase Functions to use the Google Developer API. Authentification is required to use this API.
I follow the doc: https://github.com/googleapis/google-api-nodejs-client
I have some troubles to get the authorization code in the callback url.
var {google} = require('googleapis');
google.options({ auth: oauth2Client });
var oauth2Client = new google.auth.OAuth2(
'XXXX.apps.googleusercontent.com',
'XXXX',
'https://us-central1-XXXX.cloudfunctions.net/oauth2callback'
);
function generateAuthenticationUrl() {
return oauth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: 'https://www.googleapis.com/auth/androidpublisher'
});
}
exports.oauth2Callback = functions.https.onRequest((req, res) => {
console.log(req.query.code);
const code = req.query.code;
//do something
return null;
});
exports.hello = functions.https.onRequest((req, res) => {
var url = generateAuthenticationUrl();
console.log(url);
//-> url print in the console is : https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fandroidpublisher&response_type=code&client_id=XXXXX-XXX.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fus-central1-XXX.cloudfunctions.net%2Foauth2callback
res.redirect(url);
});
Redirect url is set in the Google Console Developer:
When I call the url https://us-central1-XXX.cloudfunctions.net/hello, I got "Error: could not handle the request" and "finished with status: 'timeout'" in the Firebase logs.
What's wrong?
I found a solution.
Full code using JWT to authenticate, then get the list of app's reviews:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
var {google} = require('googleapis');
const serviceAccount = require('./client_secret.json');
const { JWT } = require('google-auth-library');
const getAuthorizedClient = () => new JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: ['https://www.googleapis.com/auth/androidpublisher']
});
const getAndroidpublisher = () => google.androidpublisher({
version: 'v3',
auth: getAuthorizedClient()
});
const requestProductValidation = () => new Promise((resolve, reject) => {
getAndroidpublisher().reviews.list({
packageName: "com.my.packagename"
}, (err, response) => {
if (err) {
console.log(`The API returned an error: ${err}`);
resolve({status: "Error"});
} else {
return resolve(response);
}
});
});
exports.hello = functions.https.onRequest((req, res) => {
return requestProductValidation();
});

Cloud Functions for Firebase Notification

I am trying to fire a notification using Cloud Functions for Firebase. I can get it to console log stating a message has been fired, but can't actually get the notification to work in the browser. Can anyone see a flaw?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.newMessageAlert = functions.database.ref('/messages/{message}').onWrite((event) => {
const message = event.data.val();
const getTokens = admin.database().ref('users').once('value').then((snapshot) => {
const tokens = [];
snapshot.forEach((user) => {
const token = user.child('token').val();
if (token) tokens.push(token);
});
return tokens;
});
const getAuthor = admin.auth().getUser(message.uid);
Promise.all([getTokens, getAuthor]).then(([tokens, author]) => {
const payload = {
notification: {
title: `Hot Take from ${author.displayName}`,
body: message.content,
icon: author.photoURL
}
};
admin.messaging().sendToDevice(tokens, payload).then((resp) =>{
console.log("IT WORKED", resp);
});
});
});

Resources