I want to dynamically create a Google Spreadsheet using the Sheets API with a Service account and JWT in Cloud Function.
The sheet is inaccessible by others except for the Service Email (which I cannot Login with).
Is there a workaround?
const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"];
const jwtClient = new google.auth.JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: SCOPES
});
const jwtAuthPromise = jwtClient.authorize();
const sheets = google.sheets({ version: "v4" });
exports.createNewSS = functions.https.onRequest(async (req, res) => {
const resource = {
properties: {
title: "Testing sheet " + Date.now()
}
};
await jwtAuthPromise;
await sheets.spreadsheets.create(
{
auth: jwtClient,
fields: "spreadsheetId",
resource
},
(err, ss) => {
if (err) {
return console.log("Error creating new sheet: " + err);
}
res.send(ss.data);
return console.log(ss.data);
}
);
});
Related
I'm trying to verify a user's email by calling the verifyAttribute method.
I am able to authenticate and get a JWT response - and with that I can do the following:
const { email, phone, accessToken } = verifyUserAttributesDto;
const getUser = await CognitoService.getUser(accessToken);
const attributes = getUser.UserAttributes.reduce(
(acc, { Name, Value }) => ({ ...acc, [Name]: Value }),
{},
);
console.log(attributes);
........
{
sub: '5f04a73b-...',
email_verified: 'false',
phone_number_verified: 'false',
phone_number: '+12222222222',
given_name: 'First',
family_name: 'Last',
email: 'example#email.com'
}
So I know I'm able to access the user.
Later I do:
const cognitoUser = new AmazonCognitoIdentity.CognitoUser({
Username: attributes.email,
Pool: this.userPool,
});
...
CognitoUser {
username: 'example#email.com',
pool: CognitoUserPool {
...
I believe I have an instance of a CognitoUser here, but maybe I don't; maybe that's the problem.
If I do:
return new Promise(function (resolve, reject) {
return cognitoUser.verifyAttribute(attribute, accessToken, {
onSuccess: (success) => {
console.log(success);
resolve(success);
},
onFailure: (err) => {
console.log(err);
reject(err);
},
});
});
The response I get back is:
ERROR [ExceptionsHandler] User is not authenticated
I have verified in the AWS console that the confirmation status for the user is confirmed and the account status is enabled.
If I've got a valid JWT, and am able to "get" the user, why am I getting that error?
It's painfully obvious I'm not sure what I'm doing. I got this working due to:
I wasn't providing the verification code
I was using the wrong identity provider (cognito vs. cognito user)
There are two methods needed to verify:
getUserAttributeVerificationCode
verifyUserAttribute
async getUserAttributeVerificationCode(
attribute: string,
accessTokenDto: AccessTokenDto,
) {
const { accessToken } = accessTokenDto;
const cognito = new AWS.CognitoIdentityServiceProvider();
try {
return await cognito
.getUserAttributeVerificationCode({
AccessToken: accessToken,
AttributeName: attribute,
})
.promise();
} catch (err) {
throw new BadRequestException(err.message);
}
}
async verifyUserAttribute(verifyUserAttributesDto: VerifyUserAttributesDto) {
const { email, phone, accessToken, verificationCode } =
verifyUserAttributesDto;
const cognito = new AWS.CognitoIdentityServiceProvider();
try {
if (email || phone) {
const attribute = email ? 'email' : 'phone';
return await cognito
.verifyUserAttribute({
AccessToken: accessToken,
AttributeName: attribute,
Code: verificationCode,
})
.promise();
}
} catch (err) {
throw new BadRequestException(err.message);
}
}
You'll first need to call getUserAttributeVerificationCode with a valid JWT. You'll then get sent a verification code (email/sms).
With that, you'll call verifyUserAttribute with all of the proper attributes.
After Creating a csv file or spreadsheet by google drive api v3 we need to give permission to make that file visible in google drive.
const google = require("googleapis").google;
const credentials = require('path to your service account file credentials.json');
const async = require("async");
async function filePermission(fileId, callback) {
const client = await google.auth.getClient({
credentials,
scopes: ["YOUR SCOPES"]
});
var permissions = [{ 'type': 'user', 'role': 'writer', 'emailAddress': 'test#example.com' }];
const drive = google.drive({ version: 'v3', auth: client });
async.eachSeries(permissions, function (permission) {
drive.permissions.create({
resource: permission,
fileId: fileId,
fields: 'id',
sendNotificationEmail: false,
}, function (err, resp) {
if (err) return console.log(err);
callback(resp);
});
}, function (err) {
if (err) {
console.error(err);
} else {
console.error("Permissions Granted");
}
});
}
I'm in real trouble with GMAIL API, here is my error:
(node:14592) UnhandledPromiseRejectionWarning: Error: Delegation denied for ADMIN_EMAIL
Here is my code, I want to change my signature with the gsuits admin of the domain at first. When i tried to just check and prompt my gmail information account, it's okay, but when I want to modify the signature, the error appeared.
I set scopes in Wide-delegation domain with the ID service account and download credentials.JSON
const {google} = require('googleapis');
const keys = require('./credentials.json');
const {JWT} = require('google-auth-library');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/gmail.settings.basic',
'https://www.googleapis.com/auth/gmail.settings.sharing',
'https://www.googleapis.com/auth/gmail.modify',
'https://mail.google.com',
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.readonly',
];
async function main() {
const client = new JWT(
{
email: keys.client_email,
key: keys.private_key,
subject: ADMIN_EMAIL,
scopes: SCOPES,
}
);
const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`;
listUsers(client);
}
main();
/**
* Lists all users in the domain and check if he's my email.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listUsers(auth) {
const service = google.admin({version: 'directory_v1', auth});
service.users.list({
customer: 'my_customer',
maxResults: 50,
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) => {
//credentials
//changer signature
if (user.primaryEmail && user.primaryEmail === MY_EMAIL) {
/*const client = new JWT(
{
email: keys.client_email,
key: keys.private_key,
subject: ADMIN_EMAIL,
scopes: SCOPES,
}
);*/
//client.subject = user.primaryEmail;
const gmail = google.gmail({version: 'v1', auth});
gmail.users.settings.delegates.list({userId:user.primaryEmail});
gmail.users.settings.sendAs.update({
userId: user.primaryEmail,
sendAsEmail: user.primaryEmail,
fields: 'signature',
resource: {
signature: SIGNATURE
}
});
} else {
console.log('Error: Not found');
}
});
} else {
console.log('No users found.');
}
});
}
Edit: I found my misstakes, like ziganotschka said, I need to create a new JWT client.
Here is the new function listUsers:
function listUsers(auth) {
const service = google.admin({version: 'directory_v1', auth});
service.users.list({
customer: 'my_customer',
maxResults: 50,
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) => {
//changer signature
if (user.primaryEmail && (user.primaryEmail === MY_SIGNATURE)) {
const client = new JWT(
{
email: keys.client_email,
key: keys.private_key,
subject: user.primaryEmail,
scopes: SCOPES,
}
);
client.subject = user.primaryEmail;
const gmail = google.gmail({version: 'v1', auth: client});
gmail.users.settings.delegates.list({userId:user.primaryEmail});
gmail.users.settings.sendAs.update({
userId: user.primaryEmail,
sendAsEmail: user.primaryEmail,
fields: 'signature',
resource: {
signature: SIGNATURE
}
});
} else {
console.log('Error: Not found');
}
});
} else {
console.log('No users found.');
}
});
}
To change a user's signature you have to impersonate the user
As you are already doing correctly, for creating / updating user signatures you have to use the service account with domain-wide delegation
However, if the service acocunt impersonates the admin, you will receive the error Delegation denied for ADMIN_EMAIL
This is because the admin is not authorized to change any siganture other than his own
So instead you need to create within your loop (users.forEach((user)) a new JWT client - with the respective user email as subject - for each user
I am trying to change my google spreadsheet's accessibility permission using google API, I have created it using my google service account using google API.
Also can I give it's ownership to some other email address and keep editing access to myself?
Code for generating(I am using node js) :-
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/spreadsheets']
);
client.authorize((err, tokens) => {
if (err) {
console.log('sheet auth err', err);
return;
}
console.log('success auth');
});
app.post('/generateSheet', async (req, res, next) => {
const { userId, sheetName } = req.body;
console.log(userId, sheetName);
// return res.redirect('/');
const integrationName = 'Google_Sheets';
const data = req.body;
try {
// const integrationInfoSnapshot = await db
// .doc(`integrationParameters/${integrationName}`)
// .get();
try {
const gsapi = google.sheets({
version: 'v4',
auth: client,
});
request_ = {
resource: {
properties: {
title: sheetName
}
}
}
const response = await gsapi.spreadsheets.create(request_);
// GIVE SPREADSHEET OWNERSHIP TO SOME OTHER EMAIL ADDRESS AND KEEP EDITING ACCESS TO MY SELF
console.log(response);
} catch (e) {
console.log(`Error while creating google sheet for user: ${userId}: `, e);
}
} catch (e) {
console.log(`Error while activating integration for user: "${userId}": `, e);
res.status(500).send({ message: e.message });
}
});
It would be a great help if someone can answer. Please Help!!!
In order to give the permissions as the writer using email, you can achieve this using the method of Permissions: create in Drive API. Unfortunately, in the current stage, Sheets API cannot do this.
From your script, for example, when the permissions are given to the created Spreadsheet, the script is as follows.
Modified script:
From:
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/spreadsheets']
);
To:
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive']
);
And, also please modify as follows.
From:
const response = await gsapi.spreadsheets.create(request_);
To:
const response = await gsapi.spreadsheets.create(request_);
// I added below script.
const fileId = response.data.spreadsheetId;
drive = google.drive({ version: "v3", auth: client });
const res = await drive.permissions.create({
resource: {
type: "user",
role: "writer",
emailAddress: "###", // Please set the email address you want to give the permission.
},
fileId: fileId,
fields: "id",
});
Reference
Permissions: create
I want to let users to connect to my node.js server with their gmail. I have created a project in the Google Developers Console. Then use the following code:
const express = require('express');
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const credentials = require('../link/of/some/file.json'); // google credential
let clientSecret = credentials.web.client_secret;
let clientId = credentials.web.client_id;
let redirectUrl = credentials.web.redirect_uris[0];
const SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.readonly'
];
let auth = new googleAuth();
let oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
app.get('/connect', (req, res) => {
let authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
res.redirect(authUrl);
});
when /connect is called, it will redirect to google page to verify user. After user give access of his/her account, google will automatically call the following api with a code query parameter:
app.get('/auth/callback', (req, res) => {
// 'req.query.code' is provided by google
return oauth2Client.getToken(req.query.code, (err, token) => {
// token value:
/*
{
access_token: 'ya29.Gls2Ba_rXEA5EoBaFH5bPdDDzgaSWOtb0GSJcnaTP47Jh7HwHdF2ZJZOlQaCJBC5wjpq-sOLBVlIM9L8BMslVyHw22nveU0MwQ4iJPTq5vkjXDeitqtoYH8JO83w',
refresh_token: '1/S0C3w-8vnqcrGE4Z2mSW9ctYkaitVuZquBSJ0WJJHUs',
token_type: 'Bearer',
expiry_date: 1514897380473
}
*/
});
});
Now, My problem is, in the above token value, I cannot understand how can I get gmail address from that token. What am I missing???
Any Suggestion? Thanks in advance.
the first steps I would suggest is to store your token somewhere so you don't request it all the time.
Then when you recieve the token you put it in oauth2Client.credentials
oauth2Client.getToken(req.query.code, (err, token) => {
if (err) {
console.log(err);
return;
}
//TODO: Save token somewhere
oauth2Client.credentials = token;
});
Then you use the oauth2Client to do API calls
var gmail = google.gmail('v1');
gmail.users.labels.list({
auth: oauth2Client,
userId: 'me',
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var labels = response.labels;
if (labels.length == 0) {
console.log('No labels found.');
} else {
console.log('Labels:');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
console.log('- %s', label.name);
}
}
});
Solved:
I have solve this problem, just calling getProfile, where userId is me. My code is given below:
const SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.compose'
];
app.get('/auth/callback', (req, res) => {
return oauth2Client.getToken(req.query.code, (err, token) => {
oauth2Client.credentials = token;
var gmail = google.gmail('v1');
gmail.users.getProfile({
auth: oauth2Client,
userId: "me",
}, function(error, response) {
console.log("getProfile : ", {error, response});
/*
output:
{
emailAddress: 'some.address#gmail.com',
messagesTotal: xxx,
threadsTotal: xxx,
historyId: 'xxxxx'
}
*/
});
});
});
Thanks to all of you.