google-spreadsheet.js (npm) read only access to cell not working with API KEY - need OAuth? - node.js

From a node.js application (a discord bot)
I try to acess to a public googlesheet using the npm package google-spreadsheet
I followed each step carefully, but I would like to use only the API key authentification method instead of a more risky Oauth identification
(my discord bot is public, on heroku and I don't want to mess around with too much sensitive information even though i use environment variables)
On the documentation of google-spreadsheet.js it mentions that :
// OR use API key -- only for read-only access to public sheets
doc.useApiKey('YOUR-API-KEY');
I sucessfully could connect to the
spreadsheet
and read the title of it and get the titles of each sheet but when I call
await sheet.loadCells();
it gives me the following error
Google API error - [401]
Request is missing required authentication credential.
Expected OAuth 2 access token,
login cookie or other valid authentication credential.
See https://developers.google.com/identity/sign-in/web/devconsole-project.
What would be the right way or READING ONLY cells, if possible using only the API KEY authentification ?
here is my full code :
const sheetId = "1Bny-ZsCG_oUuS0nTbR-7tBBZu47_ncS9qGYaMpuprWU"
var loaded = {}
if (message) {
message.reply("je me connecte à Google Sheets...")
}
const doc = new GoogleSpreadsheet(sheetId);
doc.useApiKey(process.env.GOOGLE_API_KEY);
await doc.loadInfo();
loaded.docTitle = doc.title;
loaded.sheets = {};
if (message) {
message.reply("...connection réussie, je récupère les infos...")
}
// get the spreadsheets
for (let s = 0; s < doc.sheetCount; ++s ) {
const sheet = doc.sheetsByIndex[s];
loaded.sheets[sheet.title] = {sheetReference:sheet};
loaded.sheets[sheet.title].data = []
await sheet.loadCells(); // <---------it seems to block here
for (let row= 0; row < sheet.rowCount; ++row) {
loaded.sheets[sheet.title].data.push([])
for (let col = 0; col < sheet.columnCount; ++col) {
let cell = sheet.getCell(row, col).value;
loaded.sheets[sheet.title].data[row].push(cell)
}
}
Thank you very much !

You want to retrieve the values from Google Spreadsheet using the API key.
The Google Spreadsheet is publicly shared.
You want to achieve this using google-spreadsheet.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Issue and workaround:
When I saw the source script of google-spreadsheet, it seems that sheet.loadCells() requests with the POST method using the API key. Ref Unfortunately, the API key cannot use the POST method. So such error occurred. I think that the reason of this issue is due to this. For example, when the access token from OAuth2 and service account is used, I could confirm that sheet.loadCells() worked. From this situation, this might be a bug or the specification of the library.
Fortunately, the values can be retrieved from the publicly shared Google Spreadsheet with the API key. So as one of several workarounds, in this answer, googleapis for Node.js is used as a simple method. This is the official library.
Sample script:
At first, please install googleapis. And please set the variables of spreadsheetId and APIKey.
const { google } = require("googleapis");
const spreadsheetId = "1Bny-ZsCG_oUuS0nTbR-7tBBZu47_ncS9qGYaMpuprWU"; // This is from your script.
const APIKey = "### your API key ###";
const sheets = google.sheets({version: "v4", auth: APIKey});
sheets.spreadsheets.get({ spreadsheetId: spreadsheetId }, (err, res) => {
if (err) {
console.error(err);
return;
}
sheets.spreadsheets.values.batchGet(
{
spreadsheetId: spreadsheetId,
ranges: res.data.sheets.map(e => e.properties.title)
},
(err, res) => {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(res.data));
}
);
});
When you run the script, the all values from all sheets in the publicly shared Spreadsheet are retrieved.
In above sample script, there are 2 methods of spreadsheets.get and spreadsheets.values.batchGet were used.
References:
google-api-nodejs-client
Method: spreadsheets.get
Method: spreadsheets.values.batchGet
If I misunderstood your question and this was not the direction you want, I apologize.

Author of google-spreadsheet here.
I've just released an update that should fix this problem. It was a very subtle difference in google's API docs that I missed. The loadCells method now will default to the regular get endpoint if using an API key only. The interface for loadCells is the same, but will only support A1 ranges in this mode.
Cheers!

Related

XERO-NODE SDK => How to choose a specific email template

I am using the Xero-node SDK to automatically create client invoices which works well.
At the end of the process, I would like to automatically email the client the invoice.
In the documentation it has the following example:
const xeroTenantId = 'YOUR_XERO_TENANT_ID';
const invoiceID = '00000000-0000-0000-0000-000000000000';
const requestEmpty: RequestEmpty = { };
try {
const response = await xero.accountingApi.emailInvoice(xeroTenantId, invoiceID, requestEmpty);
console.log(response.body || response.response.statusCode)
} catch (err) {
const error = JSON.stringify(err.response.body, null, 2)
console.log(`Status Code: ${err.response.statusCode} => ${error}`);
}
I have 2 questions:
The requestEmpty method does not work in javascript. Does anyone know the correct structure of requestEmpty?
I have used requestEmpty = { } but this throws an error => even though the system does actually send an email (probably a bug)
AND....
Is there a way for me to specify the email template that I would like the invoice to use (if I have specific templates setup in the web version)? Currently it seems to use the default Xero email template.
If you don't get an answer to your first query here, please can you raise it on the SDK page in Github and the Xero SDK team will look into this for you.
With regards to point 2, it is not possible to choose the email template when sending through the API, a basic template is used.

how to delete anonymous users that last signed in more than certain time from Firebase Authentication in NodeJS Admin SDK?

from Firebase Authentication, we have table like this, the providers can be email, google, facebook or anonymous
I need to delete all anonymous accounts that last signed in was more than six months ago. I need to query those anonymous accounts and then delete them all.
but I really have no idea how to query all anonymous account that last signed in was more than six months ago using Node JS admin SDK. is it possible? how to do that?
because there is a limit from firebase (100 million anonymous account) from the documentation in here. I may not hit that limit, but I think it is better If I can clean unused anonymous accounts by creating a cron job using cloud scheduler in cloud function
I think I find the solution, I suggest you to read this Firebase official documentation first, to know how to get all users from authentication, there is an explanation there that you need to read. there is no something like query to get data we need, at least right now
I will use Typescript and async/await instead of then/catch and Javascript. but if you use javascript, then you just need to modify the function parameter a little bit
import * as moment from "moment";
const auth = admin.auth();
export const deleteUnusedAnonymousUsers = async function(nextPageToken?: string) {
// this is a recursive function
try {
// get accounts in batches, because the maximum number of users allowed to be listed at a time is 1000
const listUsersResult = await auth.listUsers(1000, nextPageToken);
const anonymousUsers = listUsersResult.users.filter((userRecord) => {
return userRecord.providerData.length == 0;
});
const sixMonthAgo = moment().subtract(6, "months").toDate();
const anonymousThatSignedInMoreThanSixMonthAgo = anonymousUsers.filter((userRecord) => {
const lastSignInDate = new Date(userRecord.metadata.lastSignInTime);
return moment(lastSignInDate).isBefore(sixMonthAgo);
});
const userUIDs = anonymousThatSignedInMoreThanSixMonthAgo.map((userRecord) => userRecord.uid);
await auth.deleteUsers(userUIDs);
if (listUsersResult.pageToken) {
// List next batch of users.
deleteUnusedAnonymousUsers(listUsersResult.pageToken);
}
} catch (error) {
console.log(error);
}
};
usage
deleteUnusedAnonymousUsers();

Azure keyvault, request for multiple secrets

Im making use of the following node library azure-keyvault to get retrieve stored secrets from azure keyvault. Ive only found the client.getSecret api exposed to retrieve a secret value. Im searching for a way to retrieve multiple secret values in one call. I hav'nt found one yet. Is there a way to do this that i'm missing or its simply not supported.
const { SecretClient } = require('#azure/keyvault-secrets')
const client = new SecretClient(
`https://${KEYVAULT_NAME}.vault.azure.net`,
new DefaultAzureCredential()
)
const [secret1, secret2] = await Promise.all([
client.getSecret(`secret1`),
client.getSecret(`secret2`)
])
Here is the complete code for getting the multiple client secret at once:
var credentials = new KeyVault.KeyVaultCredentials(authenticator);
var client = new KeyVault.KeyVaultClient(credentials);
client.setSecret(vaultUri, 'mysecret', 'my password', options, function (err, secretBundle) {
// List all secrets
var parsedId = KeyVault.parseSecretIdentifier(secretBundle.id);
client.getSecrets(parsedId.vault, parsedId.name, function (err, result) {
if (err) throw err;
var loop = function (nextLink) {
if (nextLink !== null && nextLink !== undefined) {
client.getSecretsNext(nextLink, function (err, res) {
console.log(res);
loop(res.nextLink);
});
}
};
console.log(result);
loop(result.nextLink);
});
});
You can find the complete reference for azure key vault using node js below:
http://azure.github.io/azure-sdk-for-node/azure-keyvault/latest/KeyVaultClient.html#getSecrets
http://azure.github.io/azure-sdk-for-node/azure-keyvault/latest/
Hope it helps.
You can use read-azure-secrets npm package which will return all secrets to you.
E.g.
const secretClient = require('read-azure-secrets');
async function loadKeyVaultValues() {
let applicationID = '';
let applicationSecret = '';
let vaultURL = 'https://<your-key-vault-name>.vault.azure.net/';
let secrets = await secretClient.getSecrets(applicationID, applicationSecret, vaultURL);
secrets.forEach(secret => {
console.log(secret);
});
}
loadKeyVaultValues();
You can try using client.getSecrets(..) method exposed by the REST Api.
Kindly go through the following useful blog, in which all methods have been implemented.
LINK: https://www.red-gate.com/simple-talk/cloud/platform-as-a-service/using-azure-keyvault-with-node-js/
You haven't specified what information about the secret you want to fetch so I am going to assume that you are looking for the secret's value. I am also going to assume you are looking to minimize network traffic for fetching multiple secrets (either for costs or for performance).
Looking at the Azure REST API documentation while there is a route to list multiple secrets it only provides the secret identifier and metadata about the secret (attributes, tags, etc). So if you want to get the secret's value (the actual secret) you will need to make individual calls although get-secrets route can be used to find all the secrets stored in the Key Vault.
As far as the client library, #azure/keyvault-secrets maps pretty closely to the REST API it supports so it will not provide a method that fetches multiple secrets. Even if it did, it would just be a facade over multiple network calls so it would not help reduce the number of network trips.
So to answer your question - it does not look possible today unless all you want is metadata about the secret and not the secret value itself.

Transfer data from Zapier authentication to trigger

I am working on a Zapier app and there is a tenant id (integer) that is retrieved during authentication that I need to use in a trigger. What is the correct way to do this?
I have tried using global, bundle.authData and storing the data in a module, but nothing seems to work consistently. The best has been when I stored the data in global, but it is inconsistent, out of six calls to the trigger the tenant id may only be valid twice, the other four times it will be returned as undefined.
In the case of global I am writing the data during authentication:
const test = (z, bundle) => {
return z.request({
url: URL_PATH + ':' + URL_PORT + '/v1/auth',
params: {
username: bundle.authData.username,
password: bundle.authData.password
}
}).then((response) => {
if (response.status === 401) {
throw new Error('The username and/or password you supplied is incorrect.');
} else {
global.GLOBAL_tenant = response.json.tenant;
// ...
}
}
And then attempting to read the data back in the trigger:
const processTransactions = (z, bundle) => {
let jsonAll = [];
let tenant = global.GLOBAL_tenant;
return new Promise( (resolve, reject) => {
(function loop() {
// ...
I also tried adding the dat to 'bundle.authData', this was the recommendation that Zapier made when I contacted them, but the tenant id that I added during the authentication:
bundle.authData.tenant = response.json.tenant
Is not available when I try to retrieve it in the trigger. Only the 'username' and 'password' are present.
I am new to Zapier and node.js so any help will be greatly appreciated.
Instead of returning fully qualified name like bundle.authData.tenant = response.json.tenant, please use something like tenant = response.json.tenant and this statement should be enclosed in a return statement preferably. The bundle.authData qualifier is automatically applied by Zapier.
global variables should be avoided. Hope this helps.
David here, from the Zapier Platform team.
global isn't going to work because your code runs in multiple lambda executions and state isn't stored between them. Plus, global implies it would be the same for all users, which probably isn't what you want.
Instead, I'd check out session auth, which will let you store extra fields during your test by creating a computed field and returning values for it from sessionConfig.perform. Then it'll be stored in the auth object, next to the username and password.
Separately, you may want to consider whatever code is in processTransactions. Either you can return them all and they'll deduped on our end, or you're doing a bunch of extra computation that is better dehydrated. That's just a guess on my part though, so feel free to ignore this part.

DocuSign Node SDK not returning loginInfo in Production

I've built out an integration using DocuSign's Node SDK. While testing using a DocuSign sandbox account, the authentication flow works just fine using the example in the docs.
I'm now trying to do the same within a live DocuSign production account using the Integrator Key that was promoted from the sandbox account. authApi.login() seems to work just fine, I get no error and the status code of the response is 200. However, the value of loginInfo comes back as exports {} with no account info included.
I've made sure to change the base path from https://demo.docusign.net/restapi to www.docusign.net/restapi and as far as I can tell from the docs, there doesn't seem to be anything else I need to make the switch to production. Here is the code I am using:
apiClient.setBasePath('www.docusign.net/restapi');
apiClient.addDefaultHeader('Authorization', 'Bearer ' + token);
docusign.Configuration.default.setDefaultApiClient(apiClient);
const authApi = new docusign.AuthenticationApi();
const loginOps = {
apiPassword: true,
includeAccountIdGuid: true
};
authApi.login(loginOps, function (err, loginInfo, response) {
if (err) {
console.log(err);
}
if (loginInfo) {
// loginInfo returns 'exports {}' so the variables below cannot be set.
const loginAccounts = loginInfo.loginAccounts;
const loginAccount = loginAccounts[0];
const baseUrl = loginAccount.baseUrl;
const accountDomain = baseUrl.split('/v2');
const accountId = loginAccount.accountId;
apiClient.setBasePath(accountDomain[0]);
docusign.Configuration.default.setDefaultApiClient(apiClient);
www.docusign.net endpoint will only work if your PROD account is in NA1, if your PROD Account is in NA2, then you need to use na2.docusign.net and if it is in NA3 then na3.docusign.net. This is the main reason you should use /oauth/userinfo call with OAUTH2 Access Token to know your base URL, and then call all APIs with this baseURL. You can find more details at https://docs.docusign.com/esign/guide/authentication/userinfo.html

Resources