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

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

Related

Firebase Admin SDK with Cloude Functions Confusion

So after I have written a good part of my app, I am now becoming maximally confused regarding the use of the admin sdk in cloud functions regarding firestore.
I only want to query, read and write data from the cloud function environment correctly. Which documentation do I have to use, how do I correctly initialize the "Admin SDK" and implement the corresponding methods and functions?
It seems like I have mixed up v9 and v10 and even by reading the docs I still can't find a red thread, on how to use it correctly.
I am currently importing and initializing like that.
const functions = require("firebase-functions");
const { initializeApp } = require("firebase-admin/app");
const admin = require("firebase-admin");
const app = initializeApp();
when I initialize like that, and would like to work with firestore. There are different options which I do not understand - for example.
const userRef = admin.firestore().collection("users").doc(data.userID);
enables me to access "collection"
However when using
const userRef = admin.firestore
I am only able to choose admin.firestore.CollectionGroup and admin.firestore.CollectionReference, which are classes? What is up with that?
Furthermore this approach seems to be outdated as this site of the docs (which I only recently came to know), says that I should use a modular approach, as I would on the client side, with.
import { getFirestore } from 'firebase-admin/firestore'
getFirestore();
So far so good. Now when I take a look at the docs, I am led to this page. The question I ask myself there are, what are External Api Re-Exports? By clicking on any of the referenced functions I get redirected to this, which contains the reference for the nodejs client and also subgroups called firestore admin client, as well as FirestoreAdmin. Neither of those subgroups contain anything which has something to do with querying a collection. There is a section about collections and querying which displays examples of asynchronous programming with .then, but from what I heard it is generally more beneficial to use async/await?
In addition to that the quickstart guide, recommends an initialization like this.
const {Firestore} = require('#google-cloud/firestore');
// Create a new client
const firestore = new Firestore();
Furthermore the firebase docs, seem to have a dedicated documentation on how to use the Admin SDK, with the realtime database here but not how to use it with firestore?
I am just so confused, as to which documentation to use, and I couldn't find any examples how to do standard operations. I guess I also lack fundamental understanding about the Admin SDK itself and its integration in firebase.
The code I have written now is working, however I think it is not "right" from a documentation point of view.
exports.createUserDoc = functions.auth.user().onCreate((user) => {
const userRef = admin.firestore().collection("users").doc(user.uid);
const userData = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
tokens: 0,
};
return userRef.set(userData);
});
exports.setWriteTimestamp = functions.https.onCall((data, context) => {
//in the if check below, retrieve the corresponding pool document and check whether the "open" field is true
const poolRef = admin.firestore().collection("pools").doc(data.slug);
const poolDoc = poolRef.get().then((doc) => {
if (doc.exists && doc.data().open) {
return poolRef.update({
writeTimestamp: admin.firestore.FieldValue.serverTimestamp(),
});
} else {
return null;
}
});
});
Thank you for your help.

How to delete a user and their Firestore document in a callable cloud function

I'm creating a cloud function in firebase and need some help,I'm trying to delete a user form firebase and delete his document from firestore in one cloud function.
How can I make a batch job / transaction for both auth and firestore, lets say the user tries to delete his account but for some reason the user.delete() function doesn't work (lets say it's down on firebases side for that moment). The user would get en error message that we couldn't delete his account but when he tries to login again he would also get an error because his document doesn't exist.
I looked at the firebase extension to delete user data but it doesn't delete the user account and it seems to have the same problem.
Do I need to handle such edge case in the app/cloud-functions, is it something firebase should take care of or am I just getting something wrong?
Here is my code, if it would help:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports.deleteUser = functions.https.onCall(async (data, context) => {
try {
const uid = context.auth.uid;
const db = admin.firestore();
const collection = db.collection("users");
await collection.doc(uid).delete();
await admin.auth.deleteUser(uid); // what if this line fails?
return "success";
} catch (err) {
console.error(err);
return "error";
}
});
This line isn't doing what you think it's doing:
const user = await admin.auth().currentUser;
user is going to be undefined, because admin.auth() doesn't have a property called currentUser (it's an Auth object). The concept of "current user" doesn't exist on backend SDKs. It's a frontend concept only. What you do have, however, is a string uid which is the UID of the authenticated user that invoked the function.
If you want to use the Firebase Admin SDK to delete the user identified by the uid string, then you just need to call deleteUser(uid):
await admin.auth().deleteUser(uid);
By the way, the Delete User Data extension doesn't have to delete the user, because it works by responding to the user deleting their own account using the client SDK. That should actually be enough to make this work.

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.

How to query firebase realtime database in cloud code

I am using Firebase cloud code and firebase realtime database.
My database structure is:
-users
-userid32
-userid4734
-flag=true
-userid722
-flag=false
-userid324
I want to query only the users who's field 'flag' is 'true' .
What I am doing currently is going over all the users and checking one by one. But this is not efficient, because we have a lot of users in the database and it takes more than 10 seconds for the function to run:
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.test1 = functions.https.onRequest((request, response) => {
// Read Users from database
//
admin.database().ref('/users').once('value').then((snapshot) => {
var values = snapshot.val(),
current,
numOfRelevantUsers,
res = {}; // Result string
numOfRelevantUsers = 0;
// Traverse through all users to check whether the user is eligible to get discount.
for (val in values)
{
current = values[val]; // Assign current user to avoid values[val] calls.
// Do something with the user
}
...
});
Is there a more efficient way to make this query and get only the relevant records? (and not getting all of them and checking one by one?)
You'd use a Firebase Database query for that:
admin.database().ref('/users')
.orderByChild('flag').equalTo(true)
.once('value').then((snapshot) => {
const numOfRelevantUsers = snapshot.numChildren();
When you need to loop over child nodes, don't treat the resulting snapshot as an ordinary JSON object please. While that may work here, it will give unexpected results when you order on a value with an actual range. Instead use the built-in Snapshot.forEach() method:
snapshot.forEach(function(userSnapshot) {
console.log(userSnapshot.key, userSnapshot.val());
}
Note that all of this is fairly standard Firebase Database usage, so I recommend spending some extra time in the documentation for both the Web SDK and the Admin SDK for that.

Resources