Check if User is Anonymous in Firestore Cloud Function - javascript

When a user is authenticated I trigger a cloud Firestore Cloud Function below;
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase)
exports.createUserAccount = functions.auth.user().onCreate((user) => {
const uid = user.uid
const firstName = user.firstName
const familyName = user.familyName
const displayName = user.displayName
const email = user.email
const photoURL = user.photoURL
const newUser = admin.firestore().doc(`/users/${uid}`)
return newUser.set({
firstName: firstName,
familyName: familyName,
displayName: displayName,
email: email,
photoURL: photoURL,
})
})
I run into an issue when a user is logged in anonymously. I have tried adding a const isAnonymous = user.isAnonymous and then returning it under;
return newUser.set({
isAnonymous: isAnonymous,
ERROR LOG IN CONSOLE
Error: Value for argument "data" is not a valid Firestore document. Cannot use "undefined" as a Firestore value (found in field isAnonymous).
My question is how do I check if the user is anonymously logged in or not through a Firebase Cloud Function?

The UserRecord object delivered to your auth function will never have an isAnonymous property on it. That's why it's always taking the value undefined. Check the link to the API documentation to see what's there. I'm not sure how you came to the conclusion that it should be there.
Each authentication provider that has verified an account is present in the providerData array property of the UserRecord object. You should look through that array to check if it's an anonymous account. Specifically, you should be checking the providerId field of each UserInfo object in the providerData array. I'm not 100% certain if this is the correct approach, but from what I'm observing, an anonymous account will have an empty providerData array, since it's not been verified by any auth providers.

As long as you are using email-based authentication (email+pass & OAuth) then you can simply check
if (user.email !== null){
// Run this code if the user is not anonymous
}
Docs

As far as I know there is no direct API for checking if a user is anonymous using the firebase admin sdk. By checking that a user has been active and don't have any connected login providers I believe it should work however.
const user = await admin.auth().getUser('Bp7DmEl5HKcDKrQr7QFVnQXS8FH1')
const hasBeenActive = user.metadata.lastSignInTime != null
const hasProviders = user.providerData.length > 0
const isAnonymous = hasBeenActive && !hasProviders
Note that the reason for the active check is to filter out users who are created manually using either the firebase console or with the admin sdk. Depending on your use case you might want to consider them anonymous as well.

Related

Is it secure to send the user UID to firebase cloud functions

I have a firestore collection and every user has one document. The document name is equal to the user UID.
I write to these documents with a firebase function. So I pass the user UID to this firebase cloud function so the function can write to the database (this is just a very simple write so I won't show it here).
Here is the function call in my js file:
const saveAllTimeData = firebase.functions().httpsCallable('saveAllTimeData');
saveAllTimeData({ data: firebase.auth().currentUser.uid })
But I am not very sure if this is safe.
Can't just someone change the firebase.auth().currentUser.uid part before the execution? And f.e. put in another uid to change documents he shouldn't be able to change?
The user's UID is safe to share as a UID acts like a fingerprint to differentiate and identify a user from another. these do not in any way give permission or power over that user, it is simply a random set of characters that is unique.
With the Admin SDK, you are also able to create these UID's per design, allowing you to have a custom UID that may represent their username if so desired.
Since you are also using a httpsCallable cloud function, this includes a value called context, which uses the JWT token from the auth module. the JWT token is decodable to the user and should not be shared. however, the method httpsCallable secures it through the HTTPS tunnel making it secure from most hijacking.
in your function, you should notice the context variable
exports.addMessage = functions.https.onCall((data, context) => {
// ...
});
For onCall, context contains a property called auth of which contains the decoded JWT values
// Message text passed from the client.
const text = data.text;
// Authentication / user information is automatically added to the request.
const uid = context.auth.uid;
const name = context.auth.token.name || null;
const picture = context.auth.token.picture || null;
const email = context.auth.token.email || null;
references
https://firebase.google.com/docs/reference/functions/providers_https_#oncall
https://firebase.google.com/docs/reference/functions/providers_https_.callablecontext
https://firebase.google.com/docs/auth/admin/verify-id-tokens

How to update or set property of firestore document after .onCreate cloud function trigger

I'm currently trying to integrate Stripe with my Firebase's Cloud Firestore db through using Cloud Functions for Firebase. The onCreate trigger is happening correctly but I also want it to update or set a specific field called "customer_id" into the right document in my Users collection. I think something is a little off about how I write my function since I'm not the most experienced with javascript.
I've also tried
return admin.firestore().ref(`Users/${user.uid}/customer_id`).set(customer.id);
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
//const logging = require('#google-cloud/logging')();
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate((user) => {
return stripe.customers.create({
email: user.email,
}).then((customer) => {
return admin.firestore().collection("Users").doc(user.uid).update({"customer_id": customer.id})
});
});
Customer gets created with Stripe no problem but the "customter_id" field is not getting updated on the Firestore db.
Print screen of the database:
Print screen of the error log:
As said in the comments, from the print screen of the error log, the code you have deployed does not correspond to the code you are referencing to in your question.
The code in your question looks correct.

Interacting with the current user object in firebase requires me the type user.user, why must I type user twice?

I am trying to add a registration page for users to register on a firebase database in a web page(it's wordpress).
For example when I create the user and then try a simple console log to check the created user's uid like so:
auth.createUserWithEmailAndPassword(email, password)
.then(function(user){
console.log(user.uid);
});
I get an "undefined". But when I attempt to do the same thing by typing
auth.createUserWithEmailAndPassword(email, password)
.then(function(user){
console.log(user.user.uid);
});
It works correctly.
Why is this?
I followed the documentation for initialising firebase and then set the reference to the auth as
var auth = firebase.auth();
Thanks for any help. I am new to both Javascript and Firebase.
From the documentation
createUserWithEmailAndPassword(email, password) returns firebase.Promise containing non-null firebase.auth.UserCredential
Clicking firebase.auth.UserCredential shows you it has the structure
{
user: nullable firebase.User,
credential: nullable firebase.auth.AuthCredential,
operationType: (nullable string or undefined),
additionalUserInfo: (nullable firebase.auth.AdditionalUserInfo or undefined)
}
So you are accessing the user property of the returned UserCredential object which you have named user.
If you wanted to, you could change user to userCredential to help clarify what it is and avoid future confusion.
auth.createUserWithEmailAndPassword(email, password)
.then(function(userCredential){
console.log(userCredential.user.uid);
});

Firebase : Anonymous authentication - How to set identifier?

const promise = firebase.auth().signInAnonymously();
This is the code i use to create anonymous authentication.
I get the name of the visitor and i have to store
const user_id = response.uid;
const userPromise = firebase.database().ref('users/' + user_id).set({
username: this.state.name
});
response.uid is received from promise.
Again, when the user visits the site again
firebase.auth().onAuthStateChanged(user => {
I have to grab uid first, i need to use the firebase api to fetch the username.
Is it possible to store username in identifier column?
A good place to store the user name is in the display name field of the Firebase Authentication profile. You can read this with firebase.auth().currentUser.displayName and set it through updateProfile.

How to create "credential" object needed by Firebase web user.reauthenticateWithCredential() method?

The (unclear) example in the new docs:
var user = firebase.auth().currentUser;
var credential;
// Prompt the user to re-provide their sign-in credentials
user.reauthenticateWithCredential(credential).then(function() {
How should I create this credential object?
I tried:
reauthenticateWithCredential(email, password) (like the login method)
reauthenticateWithCredential({ email, password }) (the docs mention one argument only)
No luck :(
PS: I don't count the hours wasted searching for relevant info in the new docs... I miss so much the fabulous firebase.com docs, but wanted to switch to v3 or superior for firebase.storage...
I managed to make it work, docs should be updated to include this for who does not want to spend too much time in the exhaustive-but-hard-to-read API reference.
Firebase 8.x
The credential object is created like so:
const user = firebase.auth().currentUser;
const credential = firebase.auth.EmailAuthProvider.credential(
user.email,
userProvidedPassword
);
// Now you can use that to reauthenticate
user.reauthenticateWithCredential(credential);
Firebase 9.x
(Thanks #Dako Junior for his answer that I'm adding here for exhaustivity)
import {
EmailAuthProvider,
getAuth,
reauthenticateWithCredential,
} from 'firebase/auth'
const auth = getAuth()
const credential = EmailAuthProvider.credential(
auth.currentUser.email,
userProvidedPassword
)
const result = await reauthenticateWithCredential(
auth.currentUser,
credential
)
// User successfully reauthenticated. New ID tokens should be valid.
Note
Some people asked about userProvidedPassword, if it was some sort of stored variable from the first login. It is not, you should open a new dialog/page with a password input, and the user will enter their password again.
I insist that you must not try to workaround it by storing user password in cleartext. This is a normal feature for an app. In GMail for example, sometimes your session expires, or there is a suspicion of hack, you change location, etc. GMail asks for your password again. This is reauthentication.
It won't happen often but an app using Firebase should support it or the user will be stuck at some point.
Complete answer - you can use the following:
var user = firebase.auth().currentUser;
var credentials = firebase.auth.EmailAuthProvider.credential(
user.email,
'yourpassword'
);
user.reauthenticateWithCredential(credentials);
Please note that reauthenticateWithCredential is the updated version of reauthenticate()
There are multiple methods to re-authenticat. See the refs: https://firebase.google.com/docs/reference/js/firebase.User
firebase
.auth()
.currentUser.reauthenticateWithPopup(new firebase.auth.GoogleAuthProvider())
.then((UserCredential) => {
console.log("re-outh", UserCredential);
});
In case your app allows multiple authentication methods you might want to first find out what privider was used. You can do this by looking at the firebase.auth().currentUser.providerData array.
With the new firebase version 9.*
import {
EmailAuthProvider,
getAuth,
reauthenticateWithCredential,
} from "firebase/auth";
const auth = getAuth();
let credential = EmailAuthProvider.credential(
auth.currentUser.email,
password
);
reauthenticateWithCredential(auth.currentUser, credential)
.then(result => {
// User successfully reauthenticated. New ID tokens should be valid.
})
I agree that the documentation is not pretty clear on this. But looking a little deeper on the API reference I found firebase.auth.AuthCredential and this and I guess you should be looking to pass it to reauthenticate().
I'm guessing here but I would start trying to log the firebase.auth() to see if there is any credential object there.
I suppose it will look something like the following:
user.reauthenticate(firebase.auth().credential).then(function() {
Now there's a small change in the method since both posted answers are deprecated,
val user = auth.currentUser
user?.let { _user ->
val credentials = EmailAuthProvider.getCredential(
_user.email!!,
"userPassword"
)
_user.reauthenticate(credentials).addOnCompleteListener { _reauthenticateTask ->
}
final FirebaseUser fireBaseUser = FirebaseAuth.getInstance().getCurrentUser();
AuthCredential credential = EmailAuthProvider.getCredential(fireBaseUser.getEmail(), storedPassword);
fireBaseUser.reauthenticate(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> reAuthenticateTask) {
if (!reAuthenticateTask.isSuccessful())
...
}
});

Categories