Can't authenticate google service account using Node.js - node.js

My goal is to allow people to add events to a calendar through my website (which uses Firebase currently.) I'm attempting to allow them to add the events to a service account; however, I'm having issues getting the service account authenticated. When I attempt to deploy my code, I get an 'unexpected token google' error at the await google.auth.getClient part. I have google API tools installed globally.
//ADD CALENDAR EVENT
const {google} = require('googleapis');
const calendar = google.calendar('v3');
//Authenticate Service Acounnt
function getCredentials() {
const filePath = path.join(__dirname, 'credentials.json';
if (fs.existsSync(filePath)) {
return require(filePath)
}
if (process.env.CREDENTIALS) {
return JSON.parse(process.env.CREDENTIALS)
}
throw new Error('Unable to load credentials')
}
//Create Calendar Event
function addEvent(event) {
return new Promise(function(resolve, reject) {
calendar.events.insert({
calendarId: 'primary',
resource: {
'summary': event.eventName,
'description': event.description,
'start': {
'dateTime': event.startTime,
'timeZone': TIME_ZONE,
},
'end': {
'dateTime': event.endTime,
'timeZone': TIME_ZONE,
},
},
}, (err, res) => {
if (err) {
console.log('Rejecting because of error');
reject(err);
}
console.log('Request successful');
resolve(res.data);
});
});
}
//Add Event To Service Acount
exports.addEventToCalendar = functions.https.onRequest((request, response) => {
const eventData = {
eventName: request.body.eventName,
description: request.body.description,
startTime: request.body.startTime,
endTime: request.body.endTime
};
const credentials = getCredentials();
const client = await google.auth.getClient({
credentials,
scopes: 'https://www.googleapis.com/auth/calendar',
})
addEvent(eventData, client).then(data => {
response.status(200).send(data);
return;
}).catch(err => {
console.error('Error adding event: ' + err.message);
response.status(500).send(ERROR_RESPONSE);
return;
});
});
I've been following a combination of these two tutorials:
https://medium.com/zero-equals-false/integrating-firebase-cloud-functions-with-google-calendar-api-9a5ac042e869
https://dev.to/mornir/create-a-service-account-to-authenticate-with-google-5b1k
I've spent a lot of time googling what could be wrong, but to be honest, this google authentication stuff confuses the heck out of me. I'd appreciate whatever help I can get, thanks :)

You want to create new event to the Google Calendar using the service account.
credentials.json is the credential file of the service account.
You want to achieve this using googleapis with Node.js.
You have already been able to use Calendar API.
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
Modification points:
When the service account is used, you can use google.auth.JWT instead of google.auth.getClient.
In your script, client is given to addEvent(eventData, client). But at function addEvent(event) {}, client is not used.
About path.join(__dirname, 'credentials.json';, ) is required to be added. And I think that const path = require("path"); is also required to added.
Modified script:
//ADD CALENDAR EVENT
const { google } = require("googleapis");
const path = require("path"); // Added
// --- I added below script.
const key = require(path.join(__dirname, 'credentials.json'));
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
["https://www.googleapis.com/auth/calendar"],
null
);
const calendar = google.calendar({ version: "v3", auth: jwtClient });
// ---
//Create Calendar Event
function addEvent(event) {
return new Promise(function(resolve, reject) {
calendar.events.insert(
{
calendarId: "primary",
resource: {
summary: event.eventName,
description: event.description,
start: {
dateTime: event.startTime,
timeZone: TIME_ZONE
},
end: {
dateTime: event.endTime,
timeZone: TIME_ZONE
}
}
},
(err, res) => {
if (err) {
console.log("Rejecting because of error");
reject(err);
return;
}
console.log("Request successful");
resolve(res.data);
}
);
});
}
//Add Event To Service Acount
exports.addEventToCalendar = functions.https.onRequest((request, response) => {
const eventData = {
eventName: request.body.eventName,
description: request.body.description,
startTime: request.body.startTime,
endTime: request.body.endTime
};
addEvent(eventData) // Modified
.then(data => {
response.status(200).send(data);
return;
})
.catch(err => {
console.error("Error adding event: " + err.message);
response.status(500).send(ERROR_RESPONSE);
return;
});
});
Note:
This modified script supposes that the object of request.body can be used for resource of calendar.events.insert().
Is TIME_ZONE declared elsewhere? Please be careful this.
When the email of service account is not shared with the Google Spreadsheet, the event cannot be created. So please be careful this.
References:
google-api-nodejs-client
Events: insert
If I misunderstood your situation and this was not the direction you want, I apologize.

Related

How to request data from google analytics multi-channel funnels

I can successfully request data from the google analytics core reporting api but am struggling to pull from the multi-channel funnels api. I am pretty sure my problem is with permission/access but I've gone through the documentation and can't figure out exactly what the problem is. I am using node and the googleapis library. Here is my code for pulling the google analytics information (This works)
var {google} = require('googleapis');
var key = require('./auth.json');
var viewID = 'XXXXXXX';
var analytics = google.analyticsreporting('v4');
var jwtClient = new google.auth.JWT(key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/analytics.readonly'],
null);
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log('Yup, we got authorized!');
console.log(tokens)
}
});
var req = {
reportRequests: [{
viewId: viewID,
dateRanges: [{
startDate: '2020-11-01',
endDate: '2020-11-01'}],
metrics: [{expression: 'ga:pageViews'}],
dimensions: [{name: 'ga:city'}],
pageSize: 10
}],
};
analytics.reports.batchGet({
auth: jwtClient,
resource: req
},
function (err, response) {
if (err) {
console.log('Failed to get Report');
console.log(err);
return;
}
console.log('Success');
console.log(response.data)
}
);
This produces the desired output just fine. I tried to change the metrics and dimensions so I can pull some multi-channel funnel information that I want but am getting an error
var {google} = require('googleapis');
var key = require('./auth.json');
var viewID = 'XXXXXXX';
var analytics = google.analyticsreporting('v4');
var jwtClient = new google.auth.JWT(key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/analytics.readonly'],
null);
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log('Yup, we got authorized!');
console.log(tokens)
}
});
var req = {
reportRequests: [{
viewId: viewID,
dateRanges: [{
startDate: '2020-11-01',
endDate: '2020-11-01'}],
metrics: [{expression: 'mcf:sourceMedium'}],
dimensions: [{name: 'mcf:lastInteractionConversions'}],
pageSize: 10
}],
};
analytics.reports.batchGet({
auth: jwtClient,
resource: req
},
function (err, response) {
if (err) {
console.log('Failed to get Report');
console.log(err);
return;
}
console.log('Success - got something back from the Googlez');
console.log(response.data)
}
);
It is producing this
code: 400,
errors: [
{
message: 'Unknown dimensions(s): mcf:lastInteractionConversions, unknown metric(s): mcf:sourceMedium\n' +
'For details see https://developers.google.com/analytics/devguides/reporting/core/dimsmets.',
domain: 'global',
reason: 'badRequest'
}
]
I am pretty sure my problem is either I am requesting from the wrong source or my permissions are only available for ga and not mcf, but I don't fully understand the reason for the error or how to fix it, and the documentation hasn't provided much help. Any help would be appreciated, thanks.
You are using Reporting API V4 instead of Multi-Channel Funnels API V3: https://developers.google.com/analytics/devguides/reporting/mcf/v3/reference
The API you need provides a single method to request data:
analytics.data.mcf.get()

Google Drive Api v3 "File not found"

During my work with the Google Drive Api v3, I am facing an issue:
If I make a call to retrieve the file list, in the response I can see, among others:
{ id: '1XYlwukNmzUrHRCh05pb9OeD1nnZdDjJU', name: 'file5.zip' },
so now I am using the fileId in the response above to try to delete:
const deleteFileById = (fileId) => {
console.log(`File id is ${fileId}`);
const drive = google.drive({ version: 'v3', authorization });
return new Promise((resolve, reject) => {
try {
drive.files.delete({
auth: authorization,
fileId: fileId,
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
resolve("File has been deleted");
});
} catch (error) {
console.log(error);
}
});
}
And getting as a response:
The API returned an error: Error: File not found: 1XYlwukNmzUrHRCh05pb9OeD1nnZdDjJU.
So, at this point I would say that is weird.....
This is the code i used. It works but runs a little fast sometimes. You dont need to add authorization to the call to the api its already part of the service.
async function deleteFiles(auth) {
const driveService = google.drive({version: 'v3', auth});
let response = await driveService.files.list({
q: "parents in '10krlloIS2i_2u_ewkdv3_1NqcpmWSL1w'",
fields: 'files(id)'
})
const files = response.data.files;
if (files.length) {
console.log('Files:');
files.map((file) => {
console.log(`${file.id}`);
driveService.files.delete({
"fileId": file.id
})
.then(() => console.log('FileDeleted'))
.catch((err) => console.error('failed' + err));
});
} else {
console.log('No files found.');
}
}

How to make sure a route is called only after completing payment on Stripe?

I have configured stripe on my website successfully. But as soon as they make the payment, I have an HTTP route that updates their 'pro' status in my MongoDB database. But the problem I am facing is that I call the 'fetch' call in the client is called as soon as they click pay now and the user is updated in the database without finishing the payment.
Another problem of me updating the user like this is that anyone will be able to share this HTTP route and upgrade their account to pro. Here are the code snippets:
Server Route:
app.patch('/new-pro', async(req,res)=>{
try{
const email = req.query.email
console.log(email)
const user = await User.findOneAndUpdate({email:email}, { $set: {pro: 'true'}}, {new:true})
res.send(user)
}catch(e){
}
})
Client:
var checkoutButton = document.getElementById('checkout-button');
var inputEmail = document.getElementById('accountEmail')
var paymentForm = document.getElementById('paymentForm')
paymentForm.addEventListener('submit', function (event) {
event.preventDefault()
const email = inputEmail.value
console.log(email)
fetch("/charge").then((response) => {
response.json().then((data) => {
console.log(data.id)
// stripe.redirectToCheckout({
// sessionId:
// }).then(function (result) {
// // If `redirectToCheckout` fails due to a browser or network
// // error, display the localized error message to your customer
// // using `result.error.message`.
// });
stripe.redirectToCheckout({
sessionId: data.id
}).then((result) => {
})
fetch('/new-pro?email=' + encodeURIComponent(email), { method: 'PATCH' }).then((a) => {
console.log('here')
})
})
}).catch((err) => {
console.log(err)
console.log('yo')
})
Any way I can address these 2 issues.

I get error when trying to subscribe to the getStream Websocket

Currently, I am integrating websockets for the feeds using GetStream JS library.
But I can't subscribe to the websocket by following the instruction
I have created Flat Feeds and it's working fine. I can do all the actions with activities. But I need to integrate my app with websocket so that it can update the feed live.
I've followed the steps that described in the feed document
async initializeStream(profile, followings = []) {
try {
const { peerID } = profile;
const response = await getStreamToken(peerID);
const { token } = response;
this.token = token;
this.client = stream.connect(STREAM_API_KEY, token, STREAM_APP_ID);
await this.createUser(profile);
this.feed = this.client.feed('user', peerID, token);
this.globalFeed = this.client.feed('user', 'all', token);
return true;
} catch (err) {
return false;
}
}
This is stream initialization part and it works fine without any flaw.
And below is subscription function:
subscribe (subscribingFunction) {
return this.feed.subscribe(subscribingFunction);
}
And this one is how I am using subscription function to integrate websocket:
StreamClient.subscribe((data) => {
console.log('stream - update received');
console.log(data);
// return emitter({ type: 'STREM/UPDATE_RECEIVED', payload: data });
}).then(response => {
console.log('success', response)
}).catch(response => {
console.log('failure', response)
});
Expected Result:
Websocket subscription is success and I can get the live updates through it.
Result:
I am getting this error when trying to subscribe to websocket:
klass {code: null, params: Array(0), message: "Failed to authenticate. Please check your API Token, App ID and Feed ID."}
Can you point me out what went wrong with this configuration?
Here's code for the getStreamToken function:
export const getStreamToken = (userId) => {
const apiURL = `${tokenServerAPI}/getToken?user_id=${userId}`;
const headers = {
method: 'GET',
headers: {
authorization: `Basic ${base64.encode('ob_stream_user:ob_stream_pwd')}`,
},
};
return fetch(
apiURL,
headers,
).then(response => response.json()).catch(err => {
console.log(err);
});
};

Using domain-level delegation with Google Classroom API

I'm trying to access teacher's class info using a node script. I can do it if I authenticate as the teacher themselves but I need a server connection to build a database. I'm not sure if this is a google classroom problem or if this is a bug on my end. I could be just thinking about authentication in the wrong way but I'm not sure. I am using this to authenticate:
module.exports.auth = function auth(type, callback) {
switch(type){
case "jwt":
useJwt(process.env.LXMUSER, callback)
break;
case "default":
useDefaultCred(callback);
break;
case "user":
useUser(callback);
break;
}
}
function useJwt(user, callback){
var google = require('googleapis');
var cfg = require('./config.json');
var key = require(process.env.GOOGLE_APPLICATION_CREDENTIALS);
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
cfg.scopes,
user
);
jwtClient.authorize(function (err, tokens) {
if (err) {
console.error('Unable to authenticate with google:', err);
return
}
callback(err, jwtClient)
})
}
I added these scopes to the Oauth client manager:
classroom.courses,
classroom.coursework.students,
drive.readonly,
drive.file,
drive.metadata.readonly,
drive.appdata
I can successfully use the API call through the google drive API but not the google classroom api. Here is my calling environment:
auth("jwt", function (err, authClient) {
// Make an authorized request to list Drive files.
gclass.courses.list({
auth: authClient,
}, function (err, res) {
if (err) {
utl.errorHandle(res, err);
return;
}
if (res.courses === undefined) {
console.log(res, "You have no courses");
}

Resources