How to edit event in google calendar created by service account - node.js

I am emailing an HTML link of a Google calendar event generated by Google API to users but they are unable to edit the event, they can only view it when they click the link. I am creating this event with a service account and sharing with other users.
How can I ensure these events are editable in the user's calendar?
This is a link to the code I am using:
const { google } = require('googleapis');
// Provide the required configuration
const CREDENTIALS = JSON.parse(process.env.CREDENTIALS);
const calendarId = process.env.CALENDAR_ID;
// Google calendar API settings
const SCOPES = ['https://www.googleapis.com/auth/calendar.events'];
const calendar = google.calendar({version : "v3"});
const auth = new google.auth.JWT(
CREDENTIALS.client_email,
null,
CREDENTIALS.private_key,
SCOPES,
'email_used_to_configure_the_service_account#gmail.com'
);
auth.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("Successfully connected!");
}
});
//fetching the even object from the db to get evnt.name and co
const saveEvent = {
summary: event.name,
location: event.room.host.location,
description: event.extra,
colorId: 3,
start: {
dateTime: event.startDate,
timeZone: 'Africa/Lagos',
},
end: {
dateTime: event.endDate,
timeZone: 'Africa/Lagos',
},
organizer: {
email: 'email_used_to_configure_the_service_account#gmail.com',
displayName: 'display name',
self: true
},
attendees: [{ email: 'email of recepient of event' }]
//visibility: 'public'
}
async function generateLink(){
try{
const val = await calendar.events.insert({ auth: auth, calendarId: calendarId, resource: saveEvent, sendNotifications: true });
if(val.status === 200 && val.statusText === 'OK'){
console.log('CREATED', val);
return val.data.htmlLink;
}
return console.log('NOT CREATED')
} catch(error){
console.log(`Error ${error}`);
return;
}
}
const link = await generateLink();
let mailData = {
name: user.name ? user.name : user.firstname,
token: `${config.get('platform.url')}/event-accepted/${invite.token}`,
coverImage: event.gallery.link,
eventName: event.name,
hostName: host.name? host.name : host.firstname,
venue: event.venue,
date: event.startDate,
time: event.startDate,
attendees: '',
ticketNo: '',
cost: event.amount,
action: link // htmlLink that takes you to the calendar where user can edit event.
}
mail.sendTemplate({
template: 'acceptEventEmail',
to: u.email.value,
context: mailData
});
Current Behaviour
Expected behaviour
P.S: Code has been added to the question

YOu created the event using a Service account, think of a service account as a dummy user. When it created the event it became the owner / organizer of the event and there for only the service account can make changes to it.
You either need the service account to update it and set someone else as organizer
"organizer": {
"email": "me#gmail.com",
"displayName": "L P",
"self": true
},
Service accounts cannot invite attendees without Domain-Wide Delegation of Authority
In enterprise applications you may want to programmatically access users data without any manual authorization on their part. In Google Workspace domains, the domain administrator can grant to third party applications domain-wide access to its users' data—this is referred as domain-wide delegation of authority. To delegate authority this way, domain administrators can use service accounts with OAuth 2.0.

Related

disable verification code being sent on email verification on cognito

userPool.signUp(
userData.email,
userData.password,
attributeList,
[],
async (err, result) => {
if (err) {
failure(new HttpException(500, err.message));
}
let myCredentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: process.env.USER_POOL_ID!,
});
new AWS.Config({
credentials: myCredentials,
region: process.env.AWS_REGION,
});
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
cognitoIdentityServiceProvider.adminConfirmSignUp(
{
UserPoolId: process.env.USER_POOL_ID!,
Username: userData.email,
},
function (err, _data) {
if (err) {
failure(err);
}
cognitoIdentityServiceProvider.adminUpdateUserAttributes(
{
UserPoolId: process.env.USER_POOL_ID!,
Username: userData.email,
UserAttributes: [
{
Name: 'email_verified',
Value: 'true',
},
],
},
() => {
console.log('done');
}
);
}
);
if (result) {
const cognitoUser = result.user;
success({ user: cognitoUser, sub: result.userSub });
} else {
failure(
new HttpException(
500,
'Authentication server error, please try again later'
)
);
}
}
);
This is the code by which I am signing up a user and auto verify that user on cognito.
Problem being I have 2 different set of user roles, and one user i am auto confirming but for the other i want them to manually confirm, using the code that is sent by cognito.
Now for the one type of user for which the auto_confirmation is done the email and cofirm user is working perfectly, with one caveat.
The code for verification is being sent even if it's auto verified by admincognito.
How can i disable this on this particualr set of code, so that the other user role can confirm with the code that is being sent via email
Cognito isn't really configurable in this regard. Your escape hatch in this case is the lightly documented custom email sender lambda trigger. Perform whatever checks you want before sending the email through SES (or not).
I solved this issue by creating a pre-signup lambda trigger. And passing in the role to it as a user attribute and then auto verifying email and user based on the role.

iOS/Firebase - Stripe Connect Account Verification

I've created Connected Accounts using Stripe Connect. The account is created, however, it is restricted due to:
INFORMATION NEEDED
Identity document
Owner's additional document
After messing around, I realised if I just go back and mess around with the settings I am then prompted to verify the account. Is there a way in which I can always demand verification when users sign up? I've looked at the documents, but they have not been much help to me.
This is my code:
exports.createConnectAccount = functions.https.onRequest((req, res) => {
var data = req.body
var email = data.email
var response = {}
stripe.accounts.create(
{
object: 'account',
type: 'express',
country: 'GB',
business_type: 'individual',
email: email,
capabilities: {
card_payments: {
requested: true,
},
transfers: {
requested: true,
},
},
},
function(err, account) {
if (err) {
console.log("Couldn't create stripe account: " + err)
return res.send(err)
}
console.log("ACCOUNT: " + account.id)
response.body = {success: account.id}
return res.send(response)
return admin.firestore().collection('vendors').doc(user.uid).set({account_id: account.id});
}
);
});
Your code specifically creates Express accounts with the card_payments and transfers capabilities. In order for those capabilities to be active you'd need your user to provide additional information.
You can use the Account Links API to redirect your users to a Stripe hosted onboarding form which will collect all of this for you.

Google Calendar API with Service User - Reminder overrides are not set

Problem:
When using the service user auth and inserting / updating a calendar event, the reminders are not overridden
The event is inserted / updated correct APART from the reminders are always at the default (email > 10m, popup > 30m).
Context:
Node.js using standard libraries below
Valid service account with downloaded credentials.json
Service account (service-user-account#myapp.iam.gserviceaccount.com) has write access to myuser#gmail.com calendar
Code:
const {google} = require('googleapis')
const {auth} = require('google-auth-library')
const credentials = require('./credentials.json')
const addEvent = async (auth) => {
const calendar = google.calendar({version: 'v3', auth})
const insertRes = await calendar.events.insert({
calendarId: 'myuser#gmail.com',
resource: {
summary: 'Test API',
start: {
dateTime: '2020-06-02T12:55:00.000',
timeZone: 'Europe/London'
},
end: {
dateTime: '2020-06-02T12:56:00.000',
timeZone: 'Europe/London'
},
reminders: {
useDefault: false,
overrides: [
{method: 'popup', 'minutes': 5}
]
}
}
})
console.log('insertRes', insertRes.data)
}
const getAuth = async () => {
let client = auth.fromJSON(credentials)
client.scopes = ['https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events']
return client
}
const init = async () => {
const auth = await getAuth()
await addEvent(auth)
}
init()
Response: from console.log(insertRes)
{ kind: 'calendar#event',
etag: '"3182200547452000"',
id: '6063phndufgppo8rfev1XXXXXX',
status: 'confirmed',
htmlLink:
'https://www.google.com/calendar/event?eid=NjA2M3BobmR1ZmdwcG84cmZldjFjdWh2YzQgZGFuZ2FyZmllbGR1a0Bnb29nbGVtYWlsXXXXXX',
created: '2020-06-02T12:17:53.000Z',
updated: '2020-06-02T12:17:53.768Z',
summary: 'Test API',
creator:
{ email: 'service-user-account#myapp.iam.gserviceaccount.com' },
organizer: { email: 'myuser#googlemail.com', self: true },
start:
{ dateTime: '2020-06-02T12:55:00+01:00',
timeZone: 'Europe/London' },
end:
{ dateTime: '2020-06-02T12:56:00+01:00',
timeZone: 'Europe/London' },
iCalUID: '6063phndufgppo8rfev1XXXXXX#google.com',
sequence: 0,
reminders: { useDefault: false, overrides: [{"method":"popup","minutes":5}] }
}
Hopefully someone can shed a light on the issue for me.
Thanks
It seems to be a bug, already reported on Google's Public Issue Tracker
Give it a "star" to show that more people are affected and to receive updates on the issue.
The service account user is a user more or less just like you. One can only modify reminders for the current user, not for other users.
The reminder is set, but for the "wrong" user. Try to get the event through api with the service account user, you will see the reminder there. Add a reminder with your own user through UI and do the api request again, you will not be able to see the new reminder.
If you want to set reminders on events for yourself, be sure to use your own account to modify the event, for example with OAuth 2.0.

Google Calendar API Node JS, can't add attendees with service account

I'm currently trying to develop a simple Node JS program deployed on a GCP Cloud Function, to use Google Calendar API with a Service Account.
The program is simple, i just want to create an event and add attendees.
My code works well, however i can't add attendees with the service account i have this error :
There was an error contacting the Calendar service: Error: Service accounts cannot invite attendees without Domain-Wide Delegation of Authority.
The API are activated and i allowed the calendar scope on the GSuite Admin Security, my service account have the delegation authority checked on GCP.
I search a lot a solution but nothing solve my problem..
This is the code of my module :
const { google } = require('googleapis');
const sheets = google.sheets('v4');
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets','https://www.googleapis.com/auth/calendar'];
let privatekey = require("../creds.json");
async function getAuthToken() {
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
SCOPES);
//authenticate request
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("Successfully connected!");
}
});
return jwtClient;
}
async function insertEvents(auth,items) {
const calendar = google.calendar({ version: 'v3', auth });
var startDate = new Date();
var endDate = new Date();
startDate.setHours(14, 0, 0, 0);
endDate.setHours(15, 0, 0, 0);
var attendees = [];
items.forEach(function(item){
attendees.push({email: item[2]});
});
var event = {
summary: 'Stack Roulette : It\'s Coffee Time =)',
location: 'Partout, mais avec un café',
description: "",
start: {
dateTime: startDate.toISOString(),
timeZone: 'Europe/London'
},
end: {
dateTime: endDate.toISOString(),
timeZone: 'Europe/London'
},
attendees: attendees,
reminders: {
useDefault: false,
overrides: [
{ method: 'email', minutes: 24 * 60 },
{ method: 'popup', minutes: 10 }
]
},
conferenceData: {
createRequest: {requestId: Math.random().toString(36).substring(2, 10),
conferenceSolution: {
key: {
type: "hangoutsMeet"
}
},}
}
};
calendar.events.insert(
{
auth: auth,
calendarId: 'primary',
resource: event,
conferenceDataVersion: 1
},
function(err, event) {
if (err) {
console.log(
'There was an error contacting the Calendar service: ' + err
);
return;
}
console.log('Event created: %s', event.data.htmlLink);
}
);
}
module.exports = {
getAuthToken,
getSpreadSheetValues,
insertEvents
}
I precise, there is no Frontend for my application, the code run with a cloud Function like an API Endpoint.
PS : it's not a Firebase Cloud Function, but GCP Cloud Function
If i remove attendees from event creation, it's work well but can't see the event.
Thx for your help
To set up Domain-Wide Delegation of Authority
Visit your cloud console
Go to IAM & Admin -> Service Accounts
Chose the service account you using in your script
Check Enable G Suite Domain-wide Delegation
If you are sure that you already enabled the domain-wide delegation for the correct service account and you encounter issues only when creating events with attendees:
Your issue is likely related to this issue reported on Google's Issue Tracker:
Currently there are restrictions for creation of events with attendees with a service account, especially when the attendees are outside of your domain.
When you create jwtClient, I think you have to put user primary email to JWT method.
Like:
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
SCOPES,
"yourUserPrimaryEmail"); // here

Not able to watch Admin Users Directory using `google-admin-sdk`

I am trying to connect to the G-Suite's User directory using the google-admin-sdk. I am using an API Key for authorization and I am not able to reach a successful execution.
Here is the code snippet that I'm using:
import { google } from 'googleapis';
import uuid from 'uuid/v4';
const API_KEY = 'my api key goes here';
google.admin({
version: 'directory_v1',
auth: API_KEY
}).users.list({
customer: 'my_customer',
maxResults: 10,
orderBy: 'email',
}, (err, res: any) => {
if (err) { return console.error('The API returned an error:', err.message); }
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user: any) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
Output:
Login Required
Can someone tell me what I am doing wrong here?
Also, how do I proceed further for listening to the events emitted by the Google API?
---UPDATE---
Here is the snippet that works for me now:
import { JWT } from 'google-auth-library';
import { google } from 'googleapis';
// Importing the serivce account credentials
import { credentials } from './credentials';
const scopes = ['https://www.googleapis.com/auth/admin.directory.user'];
const adminEmail = 'admin_account_email_address_goes_here';
const myDomain = 'domain_name_goes_here';
async function main () {
const client = new JWT(
credentials.client_email,
undefined,
credentials.private_key,
scopes,
adminEmail
);
await client.authorize();
const service = google.admin('directory_v1');
const res = await service.users.list({
domain: myDomain,
auth: client
});
console.log(res);
}
main().catch(console.error);
--- Bonus Tip ---
If you face any Parse Errors while using other methods of the directory, remember to JSON.stringify the request body. For example, on the admin.users.watch method:
// Watch Request
const channelID = 'channel_id_goes_here';
const address = 'https://your-domain.goes/here/notifications';
const ttl = 3600; // Or any other TTL that you can think of
const domain = 'https://your-domain.goes';
const body = {
id: channelID,
type: 'web_hook',
address,
params: {
ttl,
},
};
// Remember to put this in an async function
const res = await service.users.watch({
domain,
customer: 'my_customer',
auth: client, // get the auth-client from above
event: 'add'
}, {
headers: {
'Content-Type': 'application/json'
},
// This is the important part
body: JSON.stringify(body),
});
As you can see in the official documentation, every request sent "to the Directory API must include an authorization token". In order to authorize your request, you have to use OAuth 2.0.
You are providing an API key instead, which is not appropriate for this process. API keys are usually used for accessing public data, not users' private data as in your current situation.
You should follow the steps provided in the Node.js Quickstart instead:
First, obtain client credentials from the Google API Console.
Second, authorize the client: obtain an access token after setting the user credentials and the appropriate scopes (a process accomplish in functions authorize and getNewToken in the Quickstart).
Finally, once the client is authorized, call the API (function listUsers).
Update:
If you want to use a Service Account for this, you will have to follow these steps:
Grant domain-wide delegation to the Service Account by following the steps specified here.
In the Cloud console, create a private key for the Service Account and download the corresponding JSON file. Copy it to your directory.
Use the Service Account to impersonate a user who has access to this resource (an Admin account). This is achieved by indicating the user's email address when creating the JWT auth client, as indicated in the sample below.
The code could be something along the following lines:
const {google} = require('googleapis');
const key = require('./credentials.json'); // The name of the JSON you downloaded
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/admin.directory.user'],
"admin#domain" // Please change this accordingly
);
// Create the Directory service.
const service = google.admin({version: 'directory_v1', auth: jwtClient});
service.users.list({
customer: 'my_customer',
maxResults: 10,
orderBy: 'email',
}, (err, res) => {
if (err) return console.error('The API returned an error:', err.message);
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
Reference:
Directory API: Authorize Requests
Directory API: Node.js Quickstart
Delegate domain-wide authority to your service account
Google Auth Library for Node.js
I hope this is of any help.

Resources