get user displayName in firebase cloud function using context - node.js

In my firebase cloud function, I can get the email of each user that makes a call on the client side using:
context.auth.token.email
Is there a way to obtain the user's display name? I tried context.auth.token.name but it still return an undefined property.

The context.auth.token is an object of type DecodedToken which does not contain user display name. You'll have to use Admin SDK and get user by UID/email.
export const functionName = functions.https.onCall(async (data, ctx) => {
const { uid } = ctx.auth?.token
const user = await getAuth().getUser(uid)
console.log(user.displayName)
// ...
})

Related

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.

Executing compound Firestore query in Cloud Functions

I am writing a few triggers who work on a firestore databse of movies and users.
in this trigger' I am trying to display a new user the top rated movies in a list of genres he chose. for that I have an array of his favourite genres, and I want to query the database inside a foreach loop:
import * as functions from 'firebase-functions';
import { __values } from "tslib";
import * as admin from 'firebase-admin';
admin.initializeApp();
const films = admin.firestore().collection("Films");
export const new_user_recommend = functions.firestore
.document('/Users/{email}')
.onCreate((snap, context) => {
const new_user = snap.data();
if(new_user){
const new_email = new_user.email;
const favourite_genres = new_user.favourite_genres;
let film_id_arr: string[] = new Array();
favourite_genres.forEach((genre: string) => {
films.where('genres', 'array-contains', genre).orderBy('avg_rating', 'desc').limit(5).get()
.then((list_book: any)=>{
list_book.forEach((element: any) => {
const film_element = JSON.parse(element.data());
film_id_arr.push(film_element.data().id);
});
return film_id_arr;
})
.catch((error: any)=>{
console.log(error);
console.log("Error loading films");
});
});
}
return 0;
});
the problem is I always get the error message in the catch clause (so for each genre in the array I get the error message). I double checked to make sure the strings in the array are the same as the strings who might be written in 'genres' field for each film document.
This lead me to believe my query is somehow incorrect. Is that the case?
I edit to add some important details. the exact error I get is "Could not load the default credentials", So would that indicate a problem with user authentication rather than the query itself?
After quick search it seems that your problem is that you're using firebase-admin without providing valid credentials website.
Atleast that's how it seems judging from the error code.
The credentials for deployed functions are necessary only for firebase-admin but not for firebase-functions.

How to properly get Twitter screenName from firebase auth?

Following these links https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile and https://firebase.google.com/docs/auth/web/manage-users#get_a_users_provider-specific_profile_information
I was able to authenticate user and log in with their twitter account. However, I want to get the screenName of an user. How will I do that?
I've checked some network request and I see the screenName attribute. I've checked onAuthStateChanged and it returns user attribute but without the screenName.
I needed to do this from Node, however, Google/Firebase Auth does not store a user's Twitter handle (at least it's not accessible through firebase-admin).
However, they do make the Twitter uid accessible as the question points out. With that, you can subsequently call Twitter's API to get a user by their uid and the result will return the handle a.k.a username:
import { TwitterApi } from 'twitter-api-v2';
import { auth } from 'firebase-admin';
const BEARER = process.env['TWITTER_BEARER_TOKEN'] as string;
const logTwitterHandleFromUid = async (googleUid: string): Promise<void> => {
// get Google Auth information associated with the target user
const { providerData } = await auth().getUser(googleUid);
// pull out the Twitter information
const twitter = providerData.find((p) => p.providerId.includes('twitter'));
if (!twitter) {
console.error('User does not have a linked Twitter account')
process.exit(1)
}
const details = await new TwitterApi(BEARER).v2.user(twitter.uid);
// pull out the Twitter handle
const twitter_handle = details.data.username;
return console.log(twitter_handle);
};
See Get a user's profile documentation. The .displayName property should have it.
var user = firebase.auth().currentUser;
var name, email, photoUrl, uid, emailVerified;
if (user != null) {
name = user.displayName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid; // The user's ID, unique to the Firebase project. Do NOT use
// this value to authenticate with your backend server, if
// you have one. Use User.getToken() instead.
}

NodeJS: Google Sign-In for server-side apps

Following this documentation I succeed to perform Google Sign-In for server-side apps and have access to user's GoogleCalendar using Python on server side. I fail to do that with NodeJS.
Just in a nutshell - with Python I used the auth_code I've sent from the browser and got the credentials just like that:
from oauth2client import client
credentials = client.credentials_from_clientsecrets_and_code(
CLIENT_SECRET_FILE,
['https://www.googleapis.com/auth/drive.appdata', 'profile', 'email'],
auth_code)
Then I could store in DB the value of:
gc_credentials_json = credentials.to_json()
And generate credentials (yes it uses the refresh_token alone when it's needed):
client.Credentials.new_from_json(gc_credentials_json)
So I want to do the same using NodeJS:
easily generate credentials using just: CLIENT_SECRET_FILE, scopes and auth_code (just like I did with Python)
receive credentials using previous credentials value without analysing if the access token is expired - I prefer a ready (well tested by the community) solution
Thank you in advance!
I've implemented it using google-auth-library package.
Here is the function to retrieve the gcClient:
const performAuth = async () => {
const tokens = await parseTokenFromDB();
const auth = new OAuth2Client(
downloadedCredentialsJson.web.client_id,
downloadedCredentialsJson.web.client_secret
);
auth.on('tokens', async (newTokens) => {
updateDBWithNewTokens(newTokens);
});
await auth.setCredentials(tokens);
const gcClient = google.calendar({version: 'v3', auth});
return gcClient;
};
Here is the template for parseTokenFromCurrentDB function just to give the idea of its output:
const parseTokenFromCurrentDB = async () => {
// Put here your code to retrieve from DB the values below
return {
access_token,
token_type,
refresh_token,
expiry_date,
};
};
So using this implementation one can get gcClient:
const gcClient = await gc.getGcClient(org);
and use its methods, e.g.:
const gcInfo = await gc.getInfo(gcClient);
const events = await gc.getEvents(gcClient, calcPeriodInGcFormat());

Get a auth user by its uid in Firestore cloud function

I have cloud function triggered when a new object is added to a collection. It looks like this:
exports.emailAdmin = functions.firestore
.document('user-books/{userId}/books/{ASIN}')
.onWrite(event => {
That event.data.data() is an object added to the sub-collection ("books"). The userId comes from the Firebase authentication system. I.e. a user signed in, and added an object to the collection "user-books" with his/her "uid".
I tried:
firestore
.collection('users')
.doc(uid)
.get()
But that, "of course", fails because I don't have a collection called "users". How do I get to the "authentication database"?
The purpose is to convert the "uid" to that person's "email".
Import firebase-admin:
import * as admin from 'firebase-admin';
Then fetch the auth user:
const uid = ...;
const authUser = await admin.auth().getUser(uid); // this returns a promise, so use await or .then()
console.log(authUser.email);
You have no access to the user collection from firebase. What I did was creating a separate User's Table where I store all the users Data. I add the new user to my collection on the onCreate Hook like this:
exports.addNewUserToUserTable = functions.auth.user()
.onCreate(event => {
let user = event.data;
let database = admin.firestore();
return database.collection('user').doc(user.uid).set({
email: user.email
});
Another problem is, that at the moment it is not possible to get the logged in user's ID on Firestore Database Triggers (It works for the real time db). I'm still waiting for this feature to be released...
It seems like your question might be incomplete- you reference an event.data.data() line that didn't appear in your code snippet. Could you review it and add it if it's missing?
Also, just to clarify your purpose- you want to be able to access the email address of a user who has been authenticated, and tag it to the book that has been added by that user?
Thanks.
Since context.auth.uid is not available for firestore. You can assign userId to document and read it in the rules.
match /collectionA/{docId} {
allow update: if request.resource.data.userId == request.auth.uid;
}
exports.createProfile = functions.firestore
.document('collectionA/{docId}')
.onCreate((snap, context) => { // or onUpdate
const newValue = snap.data();
const {userId} = newValue;
});

Resources