Transfer data from Zapier authentication to trigger - node.js

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.

Related

NestJS: Authorization based on instances property best practice

I need authorization in NestJS based on instances property.
Ex. user can update only his own articles.
Is there another way despite defining the logic in each services? ( I know it is possible using CASL )
Not having a global guard will facility errors, and everything is authorized by default unless add logic on the service.
What about creating a function that takes the request, the model and the name of the proprety and use it wherever you want ?
const verifAuthorization = (
req: Request,
propName: string,
model: any
): void => {
const sender: User = req.user;
if (!sender) {
throw new BadRequestException("there is no user in the token");
}
if (!sender._id.equals(model[propName])) {
throw new UnauthorizedException();
}
};
Yes ! you will call it in every service you want to check the authorization in, but it will save you a lot of time and code

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();

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

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!

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.

Convert NodeJS asynchronous code to Spring Project Reactor

I have below NodeJS code:
// req and resp are http request, response objects
var uri = req.getURI()
var pageView = new PageView(uri)
var token = req.token
if (token) {
UserRepository.findByToken(token, function(notFound, user){
if(notFound) { // means user not found by specified token
var newUser = new User('John Doe')
user.foo = 'some value'
processUser(newUser, pageView)
} else { // user found by token
user.foo = 'some value'
processUser(user, pageView)
}
})
} else { // token does not exist
token = new Token('some value')
resp.setToken(token)
var newUser = new User('John Doe')
user.foo = 'some value'
processUser(newUser, pageView)
}
processUser(user, pageView) {
PageViewRepositiry.save(pageView, function(error, savedPageView){
if(error) {
throw 'error'
}
user.pageViews.push(savedPageView)
// save the modified savedUser
UserRepository.save(user , function(error, savedUser){
})
})
}
It uses Repository pattern as abstraction over database layer (same as the Repository pattern in Spring applications).
Basically it finds user by incoming token (from http req object). If user is found then updates user entity and adds the saved pageView entity and saves the modified user. If user is not found by token then it creates a new User, updates the user with saved pageView, saves the user.
How the same code will be written in Spring Project Reactor (Flux) ?
Is it possible to solve this problem without using block()? Ideally I would like a solution that does not use block().
First of all, you have some logic to generate a token if a token isn't present. For example:
private Mono<String> getToken(String token) {
return Mono
.just(token)
.switchIfEmpty(Mono.just("some token"));
}
In this case, it's a bit overkill to use switchIfEmpty for this, but I assume your process to generate a token is a bit more complex, otherwise you could have worked with Optional<String> in stead (eg. token.orElse("some token")).
Additionally, we also have some logic to either find the user by its token, or create a new user if there is no user by the given token:
private Mono<User> findUserByToken(String token) {
return userRepository
.findByToken(token)
.switchIfEmpty(userRepository.save(new User("John Doe", token)));
}
Now that we have these methods, we can create a PageView and use these methods along the way. The reason I start with creating a PageView is because that's the first "constant" in the entire token, regardless of whether there is a token/user found:
return Mono
.just(new PageView(uri))
.flatMap(pageViewRepository::save)
.flatMap(pageView -> getToken(token)
.flatMap(this::findUserByToken)
.doOnNext(user -> user.setFoo("foo"))
.doOnNext(user -> user.getPageView().add(pageView)))
.flatMap(userRepository::save)
.map(User::getToken);
Now, since you need the token to add to the response, and I figured out that the token is part of the User object somehow (otherwise UserRepository.findByToken() wouldn't work?), it would be easier to just use User::getToken at the end to retrieve the token to pass to the response.
Be aware though, the repository pattern does work properly with Spring, but there is only reactive support for MongoDB, Cassandra, Couchbase and Redis. Other than that there's also reactive support for PostgreSQL through rdbc, but I don't think Spring data has support for that.

Resources