How to trigger a cloud function in Firebase on password change? - firebase

I am trying to trigger a Firebase cloud function when a user changes their password, be it by
changing the password (firebase.auth().currentUser. updatePassword(newPassword) ) or by reseting it (firebase.auth(). sendPasswordResetEmail(email) ). Since I am not storing password anywhere I can not use .onUpdate trigger (or any other of the triggers).
I found a similar question, but it only asked about a trigger on password change and there is no info about a workaround: Firebase cloud function listener for password change
Edit: with this trigger I want to send an email to user that their password has been changed.
Does anyone have any ideas? Is it possible at all?

What you want is totally possible, but you'll have to implement it yourself. As the link you found states, there is no built-in on-password-update account trigger... only onCreate and onDelete account triggers. Which means we have to handle it manually. I would handle it the same way you are heading - using a cloud function to send the user an email.
I would build a cloud function named something like notifyUserOfPasswordChange() and call that cloud function from your app immediately after the line of code where you call .updatePassword() or .confirmPasswordReset() (which is the finishing step after .sendPasswordResetEmail()). If I understand the point of your question - this is the real answer here. You will need to call a cloud function manually whenever you execute password update code. There's no automated trigger.
The email can be as simple or customized as you code it. If you're unsure, then start simple and have the cloud function get the target email address from the data parameter - and then use a generic message for the email body. If you feel more adventurous, consider passing the user's UID and using the Admin SDK to look up that user's registered email address & display name and then building a prettier & personalized HTML email.
As far as how to send an email from Firebase cloud functions, there are plenty of examples out there - a good sample is on Firebase's GitHub page.

Workaround
If you're using Firestore, you can use Cloud Firestore triggers as a workaround.
Step 1
Create a Cloud Function called sendEmail.
// This cloud function will get triggered when a new document is added to `emails` collection.
exports.sendEmail = functions.firestore
.document('emails/{documentId}')
.onCreate(async (snapshot, context) => {
const data = snapshot.data()
const user = data.user
if (data.changedPassword == true) {
// send email to the user saying password was changed
} else if (data.changedEmail == true) {
// send email to the user saying the email address was changed
}
})
Step 2
Write a document to your emails collection anytime the user updates their password.
// After you call .updatePassword() or .confirmPasswordReset()
// then you do this
const db = firebase.firestore();
db.collection("emails").add({
user: {INSERT USER ID HERE}",
changedPassword: true, //changedEmail: true,
})
.then((docRef) => {
// handle success
})
.catch((error) => {
// handle error
});
(Optional) Step 3
Write a Firebase Rule to protect the emails collection. The rule says: "Only allow documents to be written to the emails collection if the user field matches the authenticated user."
Summary
On the client, you'll write a new document to the emails collection anytime you want to send an email. The document will include fields for the user id and the type of email (e.g. changedPassword, changedEmail). Your cloud function will automatically send emails when documents are added to the emails collection. Firestore Rules will ensure emails are sent only to the intended user.
This system is reusable for any type of email you want to send the user. In my app, I send emails to the user when their password is changed and when their email is changed.

Related

Fake users in firebase with #gmail.com accounts

I have a firebase project.
The next sign-in methods auth are enabled:
Google
Facebook
Apple
Anonymous
A mobile app interacts with the firebase.
Each day I get some weird new users sign-ups with fake accounts with the pattern: [name][numbers]#gmail.com. They don't do anything except sign up via google oauth once.
Is it possible to prevent it? Maybe I missed something with the google oauth configuration?
Updated:
Also, I noticed that these sign-ups started to occur when I had sent out the mobile app to google/apple verification. May these two events are correlated?
New accounts created coz of Play market Pre Launch Report
You can change Pre Launch Report settings to change it's behaviour (e.g. specify test account to use in auth)
If you are sure those fake users have a specific pattern from their email address, I would make a trigger function on Cloud Functions for Firebase.
You can use functions.auth.user().onCreate() event handler like below.
exports.checkFakeUser = functions.auth.user().onCreate((user) => {
// You can check if the user has suspicious email patterns and delete them here.
});
Or you can also make a Schedule function on Cloud Functions for Firebase and daily check if there are fake users and automatically delete them.
Plus, it would be a good step if you figure out that fake users still joining even you didn't expose your mobile app anywhere if you want to find out the reason how they are joining.
Add the following Cloud Function will help you on check the email and delete the fake user
exports.checkFakeUser = functions.auth.user().onCreate((user) => {
const list = user.email.split(".")[1].split("#")
const isFake = list[0].length === 5 && list[1] === 'gmail'
if(isFake){
admin.auth().deleteUser(user.uid)
.catch(function(error) {
console.log('Error deleting user:', error);
});
}
});
You can't stop specific accounts from being created, as the underlying Google Auth APIs are accessible to anyone with an account. You could manually delete them, or write a program to delete them (bearing in mind that you could also be deleting actual user accounts).
Or if you suspect abusive behavior, you can contact Firebase support to report that.
Check, these e-mail addresses will be re-logged when they upload a new version to google play. The most likely reason for this is that google keeps your application to a number of tests with its automation infrastructure.

Search and update document on Firebase Cloud function

I'm trying to integrate Stripe with Firebase as a backend.
I need to create stripe customer when new user signs up to my app.
for that I wrote 1 cloud function which will execute when new user created by Firebase Auth.
but in that function I'm getting Firebase auth's user.
from that I need to update my collection's document which is created for that user. (I'm storing Auth user's uid in my collection's document).
but somehow Firebase cloud function is not updating it.
Here is my Cloud function for same.
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
const customer = await stripe.customers.create({email: user.email});
return admin.firestore().collection('fl_users').doc(user.uid).set({customer_id: customer.id});
});
I don't know what need to update how. Can you please help me to solve my problem?

How to add metadata to Firebase authentication

I need to pass a custom value (device_id) during google signin with firebase authentication. This value is later obtained from cloud functions by listening for authentication event triggers and then the value is added to Firestore
I understand that you can pass values as query parameters for http triggers. However I only need to pass and get the value during and after authentication in my case. Hence is there some sort of auth.addMetaData(metadata) function in firebase authentication?
I need to be able to retrieve the custom data after an auth trigger just like we can do user.email. I need something like user.custom_data
Although Doug mentions Firebase Custom Claims, I think it’s worth extra documentation because it does allow you to add simple metadata to a Firebase User object.
Important notes
Big caveat: Custom claims are only refreshed when the user logs in. So an isAdministrator claim would require the user to logout/login before it is activated.
Firebase recommends “Use custom claims to store data for controlling user access only. All other data should be stored separately via the real-time database or other server side storage.”
Set metadata (server only)
Here’s an example on how to set device_id on a Firebase User object (on the server using firebase-admin):
await admin.auth().setCustomUserClaims(uid, { deviceId })
Note: You can not set custom claims on the client.
Get metadata (server and client)
Then to retrieve the the device_id from the User on the server:
const userRecord = await admin.auth().getUser(uid)
console.log(userRecord.customClaims.deviceId)
…and on the client:
const idTokenResult = await firebase.auth().currentUser.getIdTokenResult()
console.log(idTokenResult.claims.deviceId)
Use metadata in Firebase Security Rules
The neat thing is that custom claims are also available in Firebase Security Rules. This (slightly unrealistic) example only allows users with deviceId === 123 to see the data:
{
"rules": {
"secureContent": {
".read": "auth.token.deviceId === 123"
}
}
}
More information
Official docs: https://firebase.google.com/docs/auth/admin/custom-claims
Deep dive: https://medium.com/google-developers/controlling-data-access-using-firebase-auth-custom-claims-88b3c2c9352a
A clever pattern of synching custom claims with a Firebase database collection: https://medium.com/firebase-developers/patterns-for-security-with-firebase-supercharged-custom-claims-with-firestore-and-cloud-functions-bb8f46b24e11
Firebase Authentication doesn't support any sort of extra data provided by the client. The closest thing to metadata that gets stored per user by Firebase would be custom claims, however, the JSON blob stored there can only be set by privileged server-side applications.
If you need to store data per user, written by client apps, you should probably be using a database for that (Cloud Firestore or Realtime Database), protected by Firebase security rules, so that only the end user can read and write their own data. You could also use an HTTP type Cloud Function to pass data into your function to be recorded in a database.

Firebase - How to send email verification from onCreate cloud function listener? [duplicate]

This question already has answers here:
How to send email verification after user creation with Firebase Cloud functions?
(5 answers)
Closed 4 years ago.
I'm trying to create a cloud function that will listen for when users are created, and then send an email verification to the user that was created.
I have this code:
export const verifyEmail = functions.auth.user().onCreate((user) => {
});
The problem I have here is that user gives me no way of accessing sendEmailVerification. The only way of accessing that function is via the currentUser, but since this happens in a cloud function reaction to onCreate there is no current user.
I need to somehow get a firebase.User instance instead of a firebase.UserRecord, as the onCreate gives me.
How can I solve this?
There is no way to get the client-side user from the server, nor is there a way to send verification emails through the Admin SDK.
You will either have to trigger the email from the app itself, or send your own verification email. In the latter case, you can still use the existing verification service, by generating the correct email verification link.
Also see How to send email verification after user creation with Firebase Cloud functions?, which I'll actually mark your question as a duplicate of.

Adding users to Firebase Database

I'm learning Firebase and creating my first project with it. I'm using FirebaseUI to simplify the authentication. I'm now working on the database and I need to start by adding my authenticated users to it. I've read all the documentation and I'm able to do this, but I'm wondering if I'm doing it the best way.
Since I'm using FirebaseUI and I'm not actually calling the signIn() or createUser() methods of Firebase Authentication myself, I thought the best way for me to add users would be to do it onAuthStateChanged()
usersRef = rootRef.child('users')
firebase.auth().onAuthStateChanged(user => {
if (user) {
let userRef = usersRef.child(user.uid)
userRef.set({
name: user.displayName,
email: user.email,
photoURL: user.photoURL,
emailVerified: user.emailVerified,
})
}
}
This works fine but I'm concerned about two things:
1) This sets the user data every time the user is authenticated, even if the user already exists and nothing has changed. A simple page reload will rewrite the user to the database.
2) In order for this to work, the userRef location needs to be writable by the user. This would mean that the emailVerified in the userRef location isn't reliable because the user could potentially modify it himself. I could just rely on the emailVerified returned from onAuthStateChanged which the user could never modify, but I'm wondering if I'm just doing this wrong or if there's a better way.
A possible solution is described in a video found at https://www.youtube.com/watch?v=QEd2lEoXpp40. He creates two sections in the database: users and loginQueue. A user in users is only readable by the authorized user and loginQueue is only writable by an authorized user. When a user is authenticated, his data gets written to the loginQueue. He then uses the on() method to check for a child added to the loginQueue that matches their user.uid and somehow uses the update method to write to the user data to users. This works but I don't understand how. How can is the client able to send an update() to users\uid if it's only readable? You can see his code at 7:00 in the video. This has me stumped. This works but why.
I just implemented the technique as shown in the video and although it worked for him, I encountered a PERMISSION_DENIED error when trying to update the only readable user data. This is what I thought SHOULD happen but in his video he clearly shows this was not happening. Unless I'm missing something which I don't think I am, his method doesn't work anymore. Maybe it was a bug that was later fixed?
UPDATE: Thanks to Doug Stevenson for pointing me to Firebase Cloud Functions. I was able to completely solve my problem by creating a cloud function that responds when new users are authenticated. Here is my code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.addUserToDB = functions.auth.user().onCreate(event => {
admin.database().ref('/users/' + event.data.uid).set({
name: event.data.displayName,
email: event.data.email
});
});
This is a common strategy.
Authentication shouldn't happen too often. Also, if nothing changed in user record since the last write, nothing should actually happen. You don't pay for the bandwidth for client writes to the database.
Split your user data up into two sections, one that's writable by the current UID, and the other that's not. That will prevent problems with users modifying data that you'd not like them to.
Alternately to this, set aside another location in your database where your clients can push commands to update data elsewhere, and use a Cloud Functions for Firebase trigger to read these commands, act on them with elevated privilege, checking for correctness, and delete them.

Resources