Google `drive.files.list` doesn't list files - node.js

I'm having issues with the Google drive API. I am passing in the folder ID and it's not showing any files. How do I traverse within the sub folders to check for folders and files there?
const path = require('path')
const { JWT } = require('google-auth-library')
const { google } = require('googleapis')
const axios = require('axios')
// Get auth token
const getAuth = ({ email, key }) => {
const scopes = ['https://www.googleapis.com/auth/drive']
return new JWT({
email,
key,
scopes,
})
}
async function loadDrive(options) {
const { folderId, key, service_email } = options
const auth = await getAuth({ email: service_email, key })
const drive = google.drive({
version: 'v3',
auth: auth,
})
try {
const res = await drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
q: `'${folderId}' in parents`,
spaces: 'drive',
})
const files = res.data.files
if (files.length === 0) {
console.log('No files found.')
} else {
console.log('Files:')
for (const file of files) {
console.log(`${file.name} (${file.id})`)
}
}
} catch (e) {
console.log(e)
}
}

[Update 01/04/22]: I shared my service account email under https://console.cloud.google.com/iam-admin/serviceaccounts with the folder in Google drive and was able access the Drive data.

Related

createClickwrap returns 404 Not Found

When I call createClickwrap method, I got 404 error Not Found. If I run this method through Quickstart generated demo project, I don't get this error. However, if I run it through my project I get the error. If I debug the demo app and my app, the functions parameters are the same.
This is the code in my app:
docusign controller:
const docuSignService = require('./docusign_esign_service');
const demoDocumentsPath = path.resolve(__dirname, '../demo_documents');
const { createClickwrap } = require('./createClickWrap');
async getDocusignRecieptService() {
const authResponse = await docuSignService.authenticate();
if (authResponse) {
const docTermsPdf = 'Term_Of_Service.pdf';
const docFile = path.resolve(demoDocumentsPath, docTermsPdf);
const { basePath, accessToken, apiAccountId } = authResponse;
const { clickwrapId } = await createClickwrap({ docFile, basePath, accessToken, accountId: apiAccountId });
const res = await activateClickwrap({ clickwrapId, basePath, accessToken, accountId: apiAccountId });
console.log({ res });
}
}
docuSignService.js
const SCOPES = ['signature', 'impersonation', 'openid', 'click.manage', 'click.send'];
const fs = require('fs');
const docusign = require('docusign-esign');
class DocusingService {
async authenticate() {
const jwtLifeSec = 10 * 60, // requested lifetime for the JWT is 10 min
dsApi = new docusign.ApiClient();
dsApi.setOAuthBasePath(process.env.dsOauthServer.replace('https://', '')); // it should be domain only.
let rsaKey = fs.readFileSync(process.env.privateKeyLocation);
try {
const results = await dsApi.requestJWTUserToken(
process.env.dsJWTClientId,
process.env.impersonatedUserGuid,
SCOPES,
rsaKey,
jwtLifeSec
);
const accessToken = results.body.access_token;
// get user info
const userInfoResults = await dsApi.getUserInfo(accessToken);
// use the default account
let userInfo = userInfoResults.accounts.find((account) => account.isDefault === 'true');
return {
accessToken: results.body.access_token,
apiAccountId: userInfo.accountId,
basePath: `${userInfo.baseUri}/restapi`
};
} catch (e) {
let body = e.response && e.response.body;
// Determine the source of the error
if (body) {
// The user needs to grant consent
if (body.error && body.error === 'consent_required') {
if (this.getConsent()) {
return this.authenticate();
}
} else {
// Consent has been granted. Show status code for DocuSign API error
this._debug_log(`\nAPI problem: Status code ${e.response.status}, message body:
${JSON.stringify(body, null, 4)}\n\n`);
}
}
}
}
getConsent() {
var urlScopes = SCOPES.join('+');
// Construct consent URL
var redirectUri = 'https://developers.docusign.com/platform/auth/consent';
var consentUrl =
`${process.env.dsOauthServer}/oauth/auth?response_type=code&` +
`scope=${urlScopes}&client_id=${process.env.dsJWTClientId}&` +
`redirect_uri=${redirectUri}`;
throw new Error(`Open the following URL in your browser to grant consent to the application: ${consentUrl}`);
}
getArgs(apiAccountId, accessToken, basePath, signerEmail, signerName, id, agreementData, redirect_uri) {
const envelopeArgs = {
signerEmail: signerEmail,
signerName: signerName,
status: 'sent',
signerClientId: id,
dsReturnUrl: redirect_uri,
agreement: agreementData
};
const args = {
accessToken: accessToken,
basePath: basePath,
accountId: apiAccountId,
envelopeArgs: envelopeArgs
};
return args;
}
}
module.exports = new DocusingService();
createClickWrap.js
const createClickwrap = async ({ docFile, clickwrapName = 'clickwrapName', basePath, accessToken, accountId }) => {
// Step 3. Construct the request Body
// Create display settings model
const displaySettings = docusignClick.DisplaySettings.constructFromObject({
consentButtonText: 'I Agree',
displayName: 'Terms of Service',
downloadable: true,
format: 'modal',
hasAccept: true,
mustRead: true,
requireAccept: true,
documentDisplay: 'document'
});
// Create document model
// Read and encode file. Put encoded value to Document entity.
// The reads could raise an exception if the file is not available!
const documentPdfExample = fs.readFileSync(docFile);
const encodedExampleDocument = Buffer.from(documentPdfExample).toString('base64');
const document = docusignClick.Document.constructFromObject({
documentBase64: encodedExampleDocument,
documentName: 'Terms of Service',
fileExtension: 'pdf',
order: 0
});
// Create clickwrapRequest model
const clickwrapRequest = docusignClick.ClickwrapRequest.constructFromObject({
displaySettings,
documents: [document],
name: clickwrapName,
requireReacceptance: true
});
// Step 4. Call the Click API
const dsApiClient = new docusignClick.ApiClient();
dsApiClient.setBasePath(basePath);
dsApiClient.addDefaultHeader('Authorization', 'Bearer ' + accessToken);
const accountApi = new docusignClick.AccountsApi(dsApiClient);
// Create a clickwrap
let result = null;
try {
result = await accountApi.createClickwrap(accountId, {
clickwrapRequest
});
} catch (e) {
debugger;
console.log(e);
}
debugger;
console.log(`Clickwrap was created. ClickwrapId ${result.clickwrapId}`);
return result;
};
module.exports = { createClickwrap };
Parameters look like this in the demo app and it works:
and these are the parameters in my app:
The first parameter accountId is the same. Why I am getting this issue in my app if function gets the same parameters?
"Error: Not Found
at Request.callback (/Users/and/test/node_modules/docusign-click/node_modules/superagent/lib/node/index.js:696:15)
at IncomingMessage.<anonymous> (/Users/and/test/node_modules/docusign-click/node_modules/superagent/lib/node/index.js:906:18)
at IncomingMessage.emit (node:events:539:35)
at IncomingMessage.emit (node:domain:475:12)
at endReadableNT (node:internal/streams/readable:1345:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)"
Thanks to pointing out in the comments, when I changed basePath ${userInfo.baseUri}/restapi to ${userInfo.baseUri}/clickapi, it works now.

How to test cloud functions locally with same authorizations as remote

I've been trying out cloud functions for a little while now. Recently, I found out about functions-framework. It basically allows you to run your functions locally. This helps/should help in reducing the time it takes to test your code.
I am running into an issue where calling the functions locally - curl localhost:8080 outputs Insufficient Permission: Request had insufficient authentication scopes while testing them via the console produces the expected result.
I am trying to move data from google drive to google cloud storage
const { google } = require("googleapis");
const { google } = require("googleapis");
const SCOPES = [
"profile",
"email",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.metadata.readonly",
"https://www.googleapis.com/auth/drive.appdata",
"https://www.googleapis.com/auth/drive.metadata",
];
async function addDocument(authClient, bucket, data) {
const version = "v1";
const storage = google.storage({ version, auth: authClient });
const response = await storage.objects.insert({
bucket,
requestBody: {
name: data.name,
},
media: {
body: data.content,
mimeType: data.mimeType,
},
});
return response.data;
}
async function getDocumentContent(authClient, fileId) {
const version = "v3";
const drive = google.drive({ version, client: authClient });
const json = await drive.files.get({
alt: "media",
fileId: fileId,
auth: authClient,
});
const content = await drive.files.get({
alt: "media",
fileId: fileId,
auth: authClient,
});
const data = {
content: content.data,
mimeType: json.data.mimeType,
name: json.data.name,
};
return data;
}
async function getDocuments(authClient, folderId) {
const query = `parents= '${folderId}'`;
const fields = "files(id, name)";
const version = "v3";
const drive = google.drive({ version, client: authClient });
const response = await drive.files.list({
q: query,
fields,
auth: authClient,
});
const { files } = response.data;
return files;
}
exports.copy = async (req, res) => {
const auth = new google.auth.GoogleAuth({
scopes: SCOPES,
});
const client = await auth.getClient();
const containingFolder = "folder_id";
const bucketName = "some_bucket";
try {
const files = await getDocuments(client, containingFolder);
res.send(files);
} catch (err) {
res.send(err.message);
}
};
My question here is, how do I get local calls to have the same authentication as my remote calls?
I wrote an article on that. At the time where I wrote the article, only Java and Go (because I contributed to implement the feature in the client library) were compliant.
Have a try with NodeJS, it might be implemented now.

Not authorized when accessing google directory api

I am using node to list all users from domain. I had created service account with domain wide delegation.
The domain admin gave access to the service account to required scopes.
Code:
const { JWT } = require('google-auth-library');
const {google, chat_v1} = require('googleapis');
const keys = require('./keys.json')
async function main() {
const client = new JWT(keys.client_email, keys, keys.private_key,
['https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.user.readonly',
'https://www.googleapis.com/auth/cloud-platform'],
"admin#domain.com"
);
await client.authorize();
const service = google.admin("directory_v1");
service.users.list({
domain: "domain.com",
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.');
}
});
}
main();
but only thing I recive is:
The API returned an error: Login Required.
also enabled admin sdk api for this service account
any idea why this is happening?
I'm not sure if JWT is the correct way, but this is how I do it.
const google = require("googleapis").google;
const SRVC_ACCOUNT_CREDS = require('./keys.json');
const getClient = async (scopes: string[], user: string)=>{
const auth = new google.auth.GoogleAuth({
credentials: SRVC_ACCOUNT_CREDS,
scopes: scopes
});
const client = await auth.getClient();
client.subject = user;
return client;
};
const listUsers = async (query = "", limit = 500, pageToken = null, user, fields, getAll = false)=>{
const scopes = ["https://www.googleapis.com/auth/admin.directory.user"];
const client = await getClient(scopes, user);
const service = google.admin({version: "directory_v1", auth: client});
const result = {
users: [],
nextPageToken: ""
};
if(!fields) {
fields = "users(name.fullName,primaryEmail,organizations(department,primary,title),thumbnailPhotoUrl),nextPageToken";
}
do{
const request = await service.users.list({
customer: "my_customer",
fields: fields,
orderBy: "givenName",
maxResults: limit,
pageToken: pageToken,
query: query,
viewType: "admin_view"
});
pageToken = getAll ? request.data.nextPageToken : null;
const users = request.data.users;
if(users && users.length){
result.users.push(...users);
result.nextPageToken = request.data.nextPageToken;
}
} while(pageToken);
return result;
};

Using the Google Auth Library to authenticate Google Drive

I am using the node Google Auth Library to get an authorized client. I know this step is successful from the line console.log('Project ID', await auth.getProjectId()) which prints the correct result. I am getting an error when I try to use this authorized client to authenticate the Google Drive api:
Error: invalid_scope: Invalid OAuth scope or ID token audience provided.
Code:
import { google } from 'googleapis'
const { GoogleAuth } = require('google-auth-library')
import { config } from 'dotenv'
import { resolve } from 'path'
/**
* GCLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS are set
* in the top-level .env file, GoogleAuth requires these to be set
*/
config({ path: resolve(__dirname, '../.env')})
const main = async () => {
const csv = ''
const auth = await getAuthClient()
await uploadToGoogleDrive({ csv, auth })
}
const getAuthClient = async () => {
const scopes = ['https://www.googleapis.com/auth.drive']
const auth = new GoogleAuth({ scopes })
console.log('Project ID', await auth.getProjectId())
return await auth.getClient()
}
const uploadToGoogleDrive = async ({ csv, auth }) => {
const drive = google.drive({
version: 'v3',
auth,
})
await drive.files.create({
requestBody: {
name: 'test.csv',
mimeType: 'text/plain',
parents: ['** Parent Folder Name **']
},
media: {
mimeType: 'text/csv',
body: csv
}
})
}
main().catch(console.error)

Domain-wide delegation using default credentials in Google Cloud Run

I'm using a custom service account (using --service-account parameter in the deploy command). That service account has domain-wide delegation enabled and it's installed in the G Apps Admin panel.
I tried this code:
app.get('/test', async (req, res) => {
const auth = new google.auth.GoogleAuth()
const gmailClient = google.gmail({ version: 'v1' })
const { data } = await gmailClient.users.labels.list({ auth, userId: 'user#domain.com' })
return res.json(data).end()
})
It works if I run it on my machine (having the GOOGLE_APPLICATION_CREDENTIALS env var setted to the path of the same service account that is assigned to the Cloud Run service) but when it's running in Cloud Run, I get this response:
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Bad Request",
"reason" : "failedPrecondition"
} ],
"message" : "Bad Request"
}
I saw this solution for this same issue, but it's for Python and I don't know how to replicate that behaviour with the Node library.
After some days of research, I finally got a working solution (porting the Python implementation):
async function getGoogleCredentials(subject: string, scopes: string[]): Promise<JWT | OAuth2Client> {
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
})
const authClient = await auth.getClient()
if (authClient instanceof JWT) {
return (await new google.auth.GoogleAuth({ scopes, clientOptions: { subject } }).getClient()) as JWT
} else if (authClient instanceof Compute) {
const serviceAccountEmail = (await auth.getCredentials()).client_email
const unpaddedB64encode = (input: string) =>
Buffer.from(input)
.toString('base64')
.replace(/=*$/, '')
const now = Math.floor(new Date().getTime() / 1000)
const expiry = now + 3600
const payload = JSON.stringify({
aud: 'https://accounts.google.com/o/oauth2/token',
exp: expiry,
iat: now,
iss: serviceAccountEmail,
scope: scopes.join(' '),
sub: subject,
})
const header = JSON.stringify({
alg: 'RS256',
typ: 'JWT',
})
const iamPayload = `${unpaddedB64encode(header)}.${unpaddedB64encode(payload)}`
const iam = google.iam('v1')
const { data } = await iam.projects.serviceAccounts.signBlob({
auth: authClient,
name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
requestBody: {
bytesToSign: unpaddedB64encode(iamPayload),
},
})
const assertion = `${iamPayload}.${data.signature!.replace(/=*$/, '')}`
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
const body = querystring.encode({ assertion, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' })
const response = await fetch('https://accounts.google.com/o/oauth2/token', { method: 'POST', headers, body }).then(r => r.json())
const newCredentials = new OAuth2Client()
newCredentials.setCredentials({ access_token: response.access_token })
return newCredentials
} else {
throw new Error('Unexpected authentication type')
}
}
What you can do here is define ENV variables in your yaml file as described in this documentation to set the GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON key.
Then use a code such as the one mentioned here.
const authCloudExplicit = async ({projectId, keyFilename}) => {
// [START auth_cloud_explicit]
// Imports the Google Cloud client library.
const {Storage} = require('#google-cloud/storage');
// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// const projectId = 'project-id'
// const keyFilename = '/path/to/keyfile.json'
const storage = new Storage({projectId, keyFilename});
// Makes an authenticated API request.
try {
const [buckets] = await storage.getBuckets();
console.log('Buckets:');
buckets.forEach(bucket => {
console.log(bucket.name);
});
} catch (err) {
console.error('ERROR:', err);
}
// [END auth_cloud_explicit]
};
Or follow an approach similar to the one mentioned here.
'use strict';
const {auth, Compute} = require('google-auth-library');
async function main() {
const client = new Compute({
serviceAccountEmail: 'some-service-account#example.com',
});
const projectId = await auth.getProjectId();
const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
const res = await client.request({url});
console.log(res.data);
}
main().catch(console.error);

Resources