Firebase user.updateProfile({...}) not working in React App - javascript

So, I have this ReactJS app, there is a user database,
The function for creating the user is this
import { ref, firebaseAuth } from './../Components/config'
export function auth (email, pw) {
return firebaseAuth().createUserWithEmailAndPassword(email, pw)
.then(saveUser)
}
export function saveUser (user) {
return ref.child(`users/${user.uid}/info`)
.set({
email: user.email,
uid: user.uid,
number: "" //custom
})
.then(() => user)
}
as you see the user is made of 3 properties, email, uid, and a custom number property which initially is "",
I have a
changeNumberToNew = (n) =>{
var user = firebase.auth().currentUser;
if (user != null) {
user.updateProfile({
number: n
}).then(() => {
console.log("Number changer");
}).catch((error) => {
console.log(error);
});
} else {
console.log("No user")
}
};
and a button to call the function
<button onClick={this.changeNumberToNew(4)}>Click to change number</button>
When i click the button the promise is resolver leading to the execution of
console.log("Number changer")
but when I go and look at the firebase database object .. nothing changes, even if a reload and wait still nothing changes

I think the problem here is that you are confusing the user object in your database with the user in your authentication module. They are not the same.
You save a 'copy' of your user to the database when you say the following in the first chunk.
ref.child(`users/${user.uid}/info`)
.set({
email: user.email,
uid: user.uid,
number: ""
})
Then in the second chunk of code you try and update the current user in your authentication module. Not good. You should be updating your database, not your authentication module.
var user = firebase.**auth()**.currentUser
if (user != null) {
user.updateProfile({...})
}
I don't think you can create a custom field on the current User in the authentication module. The updateProfile() is used to update the fields you get by default from the provider, such as email, display name, photoURL etc. You can't create new ones.
You should update the copy of the user in your database and then reference that when you need the value of 'number'.
You change function should probably be more like...
changeNumberToNew = (n) => {
var user = firebase.auth().currentUser;
if (user) {
ref.child(`users/${user.uid}/info`).update({number: n})
.then(() => console.log("Number changer"))
.catch(error => console.log(error))
} else {
console.log("No user")
}
}

Firebase Auth updateProfile only supports displayName and photoURL. It does not support client custom attributes. For admin custom attributes, you would need to use the admin SDK: https://firebase.google.com/docs/auth/admin/custom-claims#set_and_validate_custom_user_claims_via_the_admin_sdk
You are probably better off in this case saving these arbitrary custom fields in the database only (provided they do not require admin privileges).

Related

Firebase Auth & CreateUserWithEmailAndPassword [duplicate]

So I have this issue where every time I add a new user account, it kicks out the current user that is already signed in. I read the firebase api and it said that "If the new account was created, the user is signed in automatically" But they never said anything else about avoiding that.
//ADD EMPLOYEES
addEmployees: function(formData){
firebase.auth().createUserWithEmailAndPassword(formData.email, formData.password).then(function(data){
console.log(data);
});
},
I'm the admin and I'm adding accounts into my site. I would like it if I can add an account without being signed out and signed into the new account. Any way i can avoid this?
Update 20161110 - original answer below
Also, check out this answer for a different approach.
Original answer
This is actually possible.
But not directly, the way to do it is to create a second auth reference and use that to create users:
var config = {apiKey: "apiKey",
authDomain: "projectId.firebaseapp.com",
databaseURL: "https://databaseName.firebaseio.com"};
var secondaryApp = firebase.initializeApp(config, "Secondary");
secondaryApp.auth().createUserWithEmailAndPassword(em, pwd).then(function(firebaseUser) {
console.log("User " + firebaseUser.uid + " created successfully!");
//I don't know if the next statement is necessary
secondaryApp.auth().signOut();
});
If you don't specify which firebase connection you use for an operation it will use the first one by default.
Source for multiple app references.
EDIT
For the actual creation of a new user, it doesn't matter that there is nobody or someone else than the admin, authenticated on the second auth reference because for creating an account all you need is the auth reference itself.
The following hasn't been tested but it is something to think about
The thing you do have to think about is writing data to firebase. Common practice is that users can edit/update their own user info so when you use the second auth reference for writing this should work. But if you have something like roles or permissions for that user make sure you write that with the auth reference that has the right permissions. In this case, the main auth is the admin and the second auth is the newly created user.
Update 20161108 - original answer below
Firebase just released its firebase-admin SDK, which allows server-side code for this and other common administrative use-cases. Read the installation instructions and then dive into the documentation on creating users.
original answer
This is currently not possible. Creating an Email+Password user automatically signs that new user in.
I just created a Firebase Function that triggers when a Firestore document is Created (with rules write-only to admin user). Then use admin.auth().createUser() to create the new user properly.
export const createUser = functions.firestore
.document('newUsers/{userId}')
.onCreate(async (snap, context) => {
const userId = context.params.userId;
const newUser = await admin.auth().createUser({
disabled: false,
displayName: snap.get('displayName'),
email: snap.get('email'),
password: snap.get('password'),
phoneNumber: snap.get('phoneNumber')
});
// You can also store the new user in another collection with extra fields
await admin.firestore().collection('users').doc(newUser.uid).set({
uid: newUser.uid,
email: newUser.email,
name: newUser.displayName,
phoneNumber: newUser.phoneNumber,
otherfield: snap.get('otherfield'),
anotherfield: snap.get('anotherfield')
});
// Delete the temp document
return admin.firestore().collection('newUsers').doc(userId).delete();
});
You can Algo use functions.https.onCall()
exports.createUser= functions.https.onCall((data, context) => {
const uid = context.auth.uid; // Authorize as you want
// ... do the same logic as above
});
calling it.
const createUser = firebase.functions().httpsCallable('createUser');
createUser({userData: data}).then(result => {
// success or error handling
});
Swift 5: Simple Solution
First store the current user in a variable called originalUser
let originalUser = Auth.auth().currentUser
Then, in the completion handler of creating a new user, use the updateCurrentUser method to restore the original user
Auth.auth().updateCurrentUser(originalUser, completion: nil)
Here is a simple solution using web SDKs.
Create a cloud function (https://firebase.google.com/docs/functions)
import admin from 'firebase-admin';
import * as functions from 'firebase-functions';
const createUser = functions.https.onCall((data) => {
return admin.auth().createUser(data)
.catch((error) => {
throw new functions.https.HttpsError('internal', error.message)
});
});
export default createUser;
Call this function from your app
import firebase from 'firebase/app';
const createUser = firebase.functions().httpsCallable('createUser');
createUser({ email, password })
.then(console.log)
.catch(console.error);
Optionally, you can set user document information using the returned uid.
createUser({ email, password })
.then(({ data: user }) => {
return database
.collection('users')
.doc(user.uid)
.set({
firstname,
lastname,
created: new Date(),
});
})
.then(console.log)
.catch(console.error);
I got André's very clever workaround working in Objective-C using the Firebase iOS SDK:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info" ofType:#"plist"];
FIROptions *secondaryAppOptions = [[FIROptions alloc] initWithContentsOfFile:plistPath];
[FIRApp configureWithName:#"Secondary" options:secondaryAppOptions];
FIRApp *secondaryApp = [FIRApp appNamed:#"Secondary"];
FIRAuth *secondaryAppAuth = [FIRAuth authWithApp:secondaryApp];
[secondaryAppAuth createUserWithEmail:user.email
password:user.password
completion:^(FIRUser * _Nullable user, NSError * _Nullable error) {
[secondaryAppAuth signOut:nil];
}];
Update for Swift 4
I have tried a few different options to create multiple users from a single account, but this is by far the best and easiest solution.
Original answer by Nico
First Configure firebase in your AppDelegate.swift file
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
FirebaseApp.configure(name: "CreatingUsersApp", options: FirebaseApp.app()!.options)
return true
}
Add the following code to action where you are creating the accounts.
if let secondaryApp = FirebaseApp.app(name: "CreatingUsersApp") {
let secondaryAppAuth = Auth.auth(app: secondaryApp)
// Create user in secondary app.
secondaryAppAuth.createUser(withEmail: email, password: password) { (user, error) in
if error != nil {
print(error!)
} else {
//Print created users email.
print(user!.email!)
//Print current logged in users email.
print(Auth.auth().currentUser?.email ?? "default")
try! secondaryAppAuth.signOut()
}
}
}
}
You can use firebase function for add users.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const cors = require('cors')({
origin: true,
});
exports.AddUser = functions.https.onRequest(( req, res ) => {
// Grab the text parameter.
cors( req, res, () => {
let email = req.body.email;
let passwd = req.body.passwd;
let role = req.body.role;
const token = req.get('Authorization').split('Bearer ')[1];
admin.auth().verifyIdToken(token)
.then(
(decoded) => {
// return res.status(200).send( decoded )
return creatUser(decoded);
})
.catch((err) => {
return res.status(401).send(err)
});
function creatUser(user){
admin.auth().createUser({
email: email,
emailVerified: false,
password: passwd,
disabled: false
})
.then((result) => {
console.log('result',result);
return res.status(200).send(result);
}).catch((error) => {
console.log(error.message);
return res.status(400).send(error.message);
})
}
});
});
CreateUser(){
//console.log('Create User')
this.submitted = true;
if (this.myGroup.invalid) {
return;
}
let Email = this.myGroup.value.Email;
let Passwd = this.myGroup.value.Passwd;
let Role = 'myrole';
let TechNum = this.myGroup.value.TechNum;
let user = JSON.parse(localStorage.getItem('user'));
let role = user.role;
let AdminUid = user.uid;
let authToken = user.stsTokenManager.accessToken;
let httpHeaders = new HttpHeaders().set('Authorization', 'Bearer ' + authToken);
let options = { headers: httpHeaders };
let params = { email:Email,passwd:Passwd,role:Role };
this.httpClient.post('https://us-central1-myproject.cloudfunctions.net/AddUser', params, options)
.subscribe( val => {
//console.log('Response from cloud function', val );
let createdUser:any = val;
//console.log(createdUser.uid);
const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${createdUser.uid}`);
const userUpdate = {
uid: createdUser.uid,
email: createdUser.email,
displayName: null,
photoURL: null,
emailVerified: createdUser.emailVerified,
role: Role,
TechNum:TechNum,
AccountAccess:this.AccountAccess,
UserStatus:'open',
OwnerUid:AdminUid,
OwnerUidRole:role,
RootAccountAccess:this.RootAccountAccess
}
userRef.set(userUpdate, {
merge: false
});
this.toastr.success('Success, user add','Success');
this.myGroup.reset();
this.submitted = false;
},
err => {
console.log('HTTP Error', err.error)
this.toastr.error(err.error,'Error')
},
() => console.log('HTTP request completed.')
);
}
On the web, this is due to unexpected behavior when you call createUserWithEmailAndPassword out of the registration context; e.g. inviting a new user to your app by creating a new user account.
Seems like, createUserWithEmailAndPassword method triggers a new refresh token and user cookies are updated too. (This side-effect is not documented)
Here is a workaround for Web SDK:
After creating the new user;
firebase.auth().updateCurrentUser (loggedInUser.current)
provided that you initiate loggedInUser with the original user beforehand.
Hey i had similar problem ,trying to create users through admin , as it is not possible to signUp user without signIn ,I created a work around ,adding it below with steps
Instead of signup create a node in firebase realtime db with email as key (firebase do not allow email as key so I have created a function to generate key from email and vice versa, I will attach the functions below)
Save a initial password field while saving user (can even hash it with bcrypt or something, if you prefer though it will be used one time only)
Now Once user try to login check if any node with that email (generate key from email) exist in the db and if so then match the password provided.
If the password matched delete the node and do authSignUpWithEmailandPassword with provided credentials.
User is registered successfully
//Sign In
firebaseDB.child("users").once("value", (snapshot) => {
const users = snapshot.val();
const userKey = emailToKey(data.email);
if (Object.keys(users).find((key) => key === userKey)) {
setError("user already exist");
setTimeout(() => {
setError(false);
}, 2000);
setLoading(false);
} else {
firebaseDB
.child(`users`)
.child(userKey)
.set({ email: data.email, initPassword: data.password })
.then(() => setLoading(false))
.catch(() => {
setLoading(false);
setError("Error in creating user please try again");
setTimeout(() => {
setError(false);
}, 2000);
});
}
});
//Sign Up
signUp = (data, setLoading, setError) => {
auth
.createUserWithEmailAndPassword(data.email, data.password)
.then((res) => {
const userDetails = {
email: res.user.email,
id: res.user.uid,
};
const key = emailToKey(data.email);
app
.database()
.ref(`users/${key}`)
.remove()
.then(() => {
firebaseDB.child("users").child(res.user.uid).set(userDetails);
setLoading(false);
})
.catch(() => {
setLoading(false);
setError("error while registering try again");
setTimeout(() => setError(false), 4000);
});
})
.catch((err) => {
setLoading(false);
setError(err.message);
setTimeout(() => setError(false), 4000);
});
};
//Function to create a valid firebase key from email and vice versa
const emailToKey = (email) => {
//firebase do not allow ".", "#", "$", "[", or "]"
let key = email;
key = key.replace(".", ",0,");
key = key.replace("#", ",1,");
key = key.replace("$", ",2,");
key = key.replace("[", ",3,");
key = key.replace("]", ",4,");
return key;
};
const keyToEmail = (key) => {
let email = key;
email = email.replace(",0,", ".");
email = email.replace(",1,", "#");
email = email.replace(",2,", "$");
email = email.replace(",3,", "[");
email = email.replace(",4,", "]");
return email;
};
If you want to do it in your front end create a second auth reference use it to create other users and sign out and delete that reference. If you do it this way you won't be signed out when creating a new user and you won't get the error that the default firebase app already exists.
const createOtherUser =()=>{
var config = {
//your firebase config
};
let secondaryApp = firebase.initializeApp(config, "secondary");
secondaryApp.auth().createUserWithEmailAndPassword(email, password).then((userCredential) => {
console.log(userCredential.user.uid);
}).then(secondaryApp.auth().signOut()
)
.then(secondaryApp.delete()
)
}
Update 19.05.2022 - using #angular/fire (latest available = v.7.3.0)
If you are not using firebase directly in your app, but use e.g. #angular/fire for auth purposes only, you can use the same approach as suggested earlier as follows with the #angular/fire library:
import { Auth, getAuth, createUserWithEmailAndPassword } from '#angular/fire/auth';
import { deleteApp, initializeApp } from '#angular/fire/app';
import { firebaseConfiguration } from '../config/app.config'; // <-- Your project's configuration here.
const tempApp = initializeApp(firebaseConfiguration, "tempApp");
const tempAppAuth = getAuth(tempApp);
await createUserWithEmailAndPassword(tempAppAuth, email, password)
.then(async (newUser) => {
resolve( () ==> {
// Do something, e.g. add user info to database
});
})
.catch(error => reject(error))
.finally( () => {
tempAppAuth.signOut()
.then( () => deleteApp(tempApp));
});
The Swift version:
FIRApp.configure()
// Creating a second app to create user without logging in
FIRApp.configure(withName: "CreatingUsersApp", options: FIRApp.defaultApp()!.options)
if let secondaryApp = FIRApp(named: "CreatingUsersApp") {
let secondaryAppAuth = FIRAuth(app: secondaryApp)
secondaryAppAuth?.createUser(...)
}
Here is a Swift 3 adaptaion of Jcabrera's answer :
let bundle = Bundle.main
let path = bundle.path(forResource: "GoogleService-Info", ofType: "plist")!
let options = FIROptions.init(contentsOfFile: path)
FIRApp.configure(withName: "Secondary", options: options!)
let secondary_app = FIRApp.init(named: "Secondary")
let second_auth = FIRAuth(app : secondary_app!)
second_auth?.createUser(withEmail: self.username.text!, password: self.password.text!)
{
(user,error) in
print(user!.email!)
print(FIRAuth.auth()?.currentUser?.email ?? "default")
}
If you are using Polymer and Firebase (polymerfire) see this answer: https://stackoverflow.com/a/46698801/1821603
Essentially you create a secondary <firebase-app> to handle the new user registration without affecting the current user.
Android solution (Kotlin):
1.You need FirebaseOptions BUILDER(!) for setting api key, db url, etc., and don't forget to call build() at the end
2.Make a secondary auth variable by calling FirebaseApp.initializeApp()
3.Get instance of FirebaseAuth by passing your newly created secondary auth, and do whatever you want (e.g. createUser)
// 1. you can find these in your project settings under general tab
val firebaseOptionsBuilder = FirebaseOptions.Builder()
firebaseOptionsBuilder.setApiKey("YOUR_API_KEY")
firebaseOptionsBuilder.setDatabaseUrl("YOUR_DATABASE_URL")
firebaseOptionsBuilder.setProjectId("YOUR_PROJECT_ID")
firebaseOptionsBuilder.setApplicationId("YOUR_APPLICATION_ID") //not sure if this one is needed
val firebaseOptions = firebaseOptionsBuilder.build()
// indeterminate progress dialog *ANKO*
val progressDialog = indeterminateProgressDialog(resources.getString(R.string.progressDialog_message_registering))
progressDialog.show()
// 2. second auth created by passing the context, firebase options and a string for secondary db name
val newAuth = FirebaseApp.initializeApp(this#ListActivity, firebaseOptions, Constants.secondary_db_auth)
// 3. calling the create method on our newly created auth, passed in getInstance
FirebaseAuth.getInstance(newAuth).createUserWithEmailAndPassword(email!!, password!!)
.addOnCompleteListener { it ->
if (it.isSuccessful) {
// 'it' is a Task<AuthResult>, so we can get our newly created user from result
val newUser = it.result.user
// store wanted values on your user model, e.g. email, name, phonenumber, etc.
val user = User()
user.email = email
user.name = name
user.created = Date().time
user.active = true
user.phone = phone
// set user model on /db_root/users/uid_of_created_user/, or wherever you want depending on your structure
FirebaseDatabase.getInstance().reference.child(Constants.db_users).child(newUser.uid).setValue(user)
// send newly created user email verification link
newUser.sendEmailVerification()
progressDialog.dismiss()
// sign him out
FirebaseAuth.getInstance(newAuth).signOut()
// DELETE SECONDARY AUTH! thanks, Jimmy :D
newAuth.delete()
} else {
progressDialog.dismiss()
try {
throw it.exception!!
// catch exception for already existing user (e-mail)
} catch (e: FirebaseAuthUserCollisionException) {
alert(resources.getString(R.string.exception_FirebaseAuthUserCollision), resources.getString(R.string.alertDialog_title_error)) {
okButton {
isCancelable = false
}
}.show()
}
}
}
For Android, i suggest a simpler way to do it, without having to provide api key, application id...etc by hand by just using the FirebaseOptions of the default instance.
val firebaseDefaultApp = Firebase.auth.app
val signUpAppName = firebaseDefaultApp.name + "_signUp"
val signUpApp = try {
FirebaseApp.initializeApp(
context,
firebaseDefaultApp.options,
signUpAppName
)
} catch (e: IllegalStateException) {
// IllegalStateException is throw if an app with the same name has already been initialized.
FirebaseApp.getInstance(signUpAppName)
}
// Here is the instance you can use to sign up without triggering auth state on the default Firebase.auth
val signUpFirebaseAuth = Firebase.auth(signUpApp)
How to use ?
signUpFirebaseAuth
.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener {
// Optional, you can send verification email here if you need
// As soon as the sign up with sign in is over, we can sign out the current user
firebaseAuthSignUp.signOut()
}
.addOnFailureListener {
// Log
}
My solution to this question is to store the User Name/Email and password in a static class and then add a new user log out the new user and immediately log in as the admin user(id pass you saved). Works like a charm for me :D
This is a version for Kotlin:
fun createUser(mail: String, password: String) {
val opts = FirebaseOptions.fromResource(requireContext())
if (opts == null) return
val app = Firebase.initialize(requireContext(), opts, "Secondary")
FirebaseAuth.getInstance(app)
.createUserWithEmailAndPassword(mail, password)
.addOnSuccessListener {
app.delete()
doWhateverWithAccount(it)
}.addOnFailureListener {
app.delete()
showException(it)
}
}
It uses the configuration from your default Firebase application instance, just under a different name.
It also deletes the newly created instance afterwards, so you can call this multiple times without any exception about already existing Secondary application.

Email provider: How to preserve value in `signIn` callback between sending email and verification calls?

I want to implement role based authentication in my app.
After researching for a while I found a simple solution - to pass role to signIn function
signIn('email', { email, role: 'user' });
Then I can unpack it from req's body
import NextAuth from 'next-auth'
export default async function auth(req, res) {
return NextAuth(req, res, {
// providers, adapters, etc.
callbacks: {
signIn: async ({ user }) => {
const { body: { role } } = req;
if (role) {
user.role = role;
}
return true;
},
session: async ({ user, session }) => {
session.user = {...session.user, ...user };
return session
}
}
}
});
Or so I thought. Magic link sign in flow has two steps: sending email and confirming it.
The role I provide to signIn function is only available during the first step and saving user to users collection only happens after user confirms email, so when user confirms email there's no role.
I tried storing role in cookies
if (role) res.setHeader('Set-Cookie', 'user-role=' + role + ';Max-Age=600');
But cookies get overriden after I confirm email and the role is lost.
I don't want to create additional collections and/or records in my database.
How can I preserve role inside signIn callback without storing it in a separate database collection? Maybe there's some other solution you can think of?

How to await the creation of a document created in cloud functions with onSnapshot

I want to securely create a user document onCreate that is in sync with the auth.user database in Firebase v9.
I think it wouldn't be secure to let a registered user create a user document. So I wrote a cloud function which triggers on functions.auth.user().onCreate() and creates a user document.
Problem:
I have the problem keeping them in sync as the onSnapshotmethod which should await for the user document to exists already returns a promise if the user document does not yet exists. Sometimes it works and sometimes not. So I don't know when I can update the by the cloud function created user document.
Question:
Why does the onSnapshot sometimes work and sometimes not. How can I fix it?
Here is a link to a helpful Article which seem to doesn't work in v9. Link
I tried and searched everywhere. I can't believe this is not a standard feature and still a requested topic. This seems so basic.
Error
error FirebaseError: No document to update: as const user = await createAccount(displayName, email, password); returns even if user is not yet in doc.data()
Sign Up function
interface SignUpFormValues {
email: string;
password: string;
confirm: string;
firstName: string;
lastName: string;
}
const createAccount = async (
displayName: string,
email: string,
password: string
) => {
// Create auth user
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// -> Signed in
// Update Profile
const user = userCredential.user;
const uid = user.uid;
await updateProfile(user, {
displayName: displayName,
});
// IMPORTANT: Force refresh regardless of token expiration
// auth.currentUser.getIdToken(true); // -> will stop the onSnapshot function from resolving properly
// Build a reference to their per-user document
const userDocRef = doc(db, "users", uid);
return new Promise((resolve, reject) => {
const unsubscribe = onSnapshot(userDocRef, {
next: (doc) => {
unsubscribe();
console.log("doc", doc); // -> returning undefined
console.log("doc.data()", doc.data()); // -> returning undefined
resolve(user); // -> returning undefined
},
error: (error) => {
unsubscribe();
console.log("error", error);
reject(error);
},
});
});
};
const handleSignUp = async (values: SignUpFormValues) => {
const { firstName, lastName, email, password } = values;
const displayName = `${firstName} ${lastName}`;
try {
setError("");
setLoading(true);
// Create user account
const user = await createAccount(displayName, email, password);
console.log("createAccount -> return:", user); // -> problem here sometimes return undefined
// Update user
const newUserData = {
displayName: displayName,
firstName,
lastName,
};
// Build a reference to their per-user document
const userDocRef = doc(db, "users", user.uid);
await updateDoc(userDocRef, newUserData);
// Send Email verification
await authSendEmailVerification(user);
// Logout
await logout();
navigate("/sign-up/email-verification", { state: values });
} catch (error: any) {
const errorCode = error.code;
const errorMessage = error.message;
console.log("error", error);
console.log("error", errorCode);
if (errorCode === "auth/email-already-in-use") {
const errorMessage =
"Failed to create an account. E-Mail address is already registered.";
setError(errorMessage);
console.log("error", errorMessage);
} else {
setError("Failed to create account.");
}
}
setLoading(false);
};
Cloud function which triggers the user onCreate
// On auth user create
export const authUserWriteListener = functions.auth
.user()
.onCreate(async (user, context) => {
console.log("user:", user);
const userRef = db.doc(`users/${user.uid}`);
await userRef.set({
email: user.email,
createdAt: context.timestamp,
firstTimeLogin: true,
});
return db.doc("stats/users").update({
totalDocsCount: FieldValue.increment(1),
});
});
The issue is that the Cloud Function code runs asynchronously. There is no guarantee that it will run quickly enough to have the document created in Firestore between the end of createAccount() and your call to updateDoc(). In fact, if your system has been idle for a while it could be a minute (or more!) for the Cloud Function to execute (do a search for "cold start firebase cloud functions").
One option, depending on your design, might be to not take in first name and last name during sign up? But instead take the user to a "profile page" once they are logged in where they could modify aspects of their profile (by that time the user profile document hopefully is created). On that page, if the get() returns no document, you could put up a notification to the user that the system "is still processing their registration" or something like that.

Firebase Sign Up with username. Cliend Side + Security Rules or Backend?

I have implemented a Firebase SignUp with username, email and password. Basically what I am doing is:
1- Create user with email and password (if the username and email are not used by other users)
2- Add the username to the user
Like this:
firebase
.createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Get the username from the input
const username = this.usernameInput.current.getText();
// Create a user with this username and the current user uid in the db
firebase.createUserWithUsername(username, currentUser.user.uid); // <----------
})
.catch((err) => {
// ...
});
And my createUserWithUsername function basically do this:
createUserWithUsername = (username, userId) => {
/*
Create a document in the usernames collection
which uid (of the document itself, not a field) is the given username.
*/
// Pass username to lowercase
username = username.toLowerCase();
// Initial user's data
const data = {
email: this.auth.currentUser.email,
username,
};
return this.db
.collection("usernames")
.doc(username)
.set({ userId })
.then(() => {
this.db.collection("users").doc(userId).set(data);
})
.catch((err) => {
console.log(err);
throw err;
});
/*
Pd: As firestore automatically removes empty documents, we have to
assign them a field. The user's id is a good option, because it will help us to
update usernames faster, acting like a 'Foreign Key' in a NoSql DB.
*/
};
My question is? Is it wrong to leave this code on the client side? Can it be a security problem? Do I need to move this to a cloud function / backend?
This is my firestore security rule for the usernames collection:
match /usernames/{username} {
function isUsernameAvailable() {
return !exists(/databases/$(database)/documents/usernames/$(username));
}
allow read: if true;
allow write, update: if isSignedIn() && isUsernameAvailable();
// TODO - Allow delete?
}
I would really appreciate any guide for this. Thank you.

firestore valueChanges() is not updating userdata instantly

I am using Angular and cloud firestore for backend. I have a user profile, a logged in user and other users with follow or unfollow action button, based on the logged in user's current following list. I want the button text and the list of followers and following to get updated in the front end as soon as the click event is successfully completed. But, I am able to see the updated values only after there is a change in route, or I click the button twice.
Is there any way that, as soon as follow or unfollow is successful, data of loggedUser and selectedUser gets updated and the same updated data is reflected in my component.
userDetails.component.ts
ngOnInit(): void {
this.loggedUser = this.userService.getLoggedUserData();
//--------Get displayed user data
this.userService.SelectedUserChanged.pipe(take(1))
.subscribe(
(user:User)=>{
this.user=user;
if(this.user){
this.btnToDisplay(); //.....To show follow or unfollow depending on logged user's data
}}
);
}
I have a UserService, where I have subscribed to logged user's valueChanges()
fetchLoggedUser(uid: string) { //Subscribed in the appcomponent using authenticated user's id.
return this.db.collection('Users').doc(uid)
.valueChanges()
.pipe(
tap((user: User) => {
this.loggeduser = user;
this.loggeduser.id = user.userid;
this.fetchAllUsers();
})
);
}
fetchAllUsers() {
this.userSubs.push(
this.db
.collection('Users')
.valueChanges({ idField: 'id' })
.subscribe((users: User[]) => {
this.allUsers = users;
this.usersChanged.next([...this.allUsers]);
})
);
}
selectUser(uid: string) {
this.selectedUser = this.allUsers.find((user) => user.id === uid);
this.SelectedUserChanged.next({ ...this.selectedUser });
}
getLoggedUserData() {
return ({...this.loggeduser});
}
followUser(uid: string, email: string) {
this.db.collection('Users').doc(this.loggeduser.userid)
.update({
following: firebase.firestore.FieldValue.arrayUnion({
uid: uid,
email: email,
}),
});
this.db.collection('Users').doc(uid)
.update({
followers: firebase.firestore.FieldValue.arrayUnion({
uid: this.loggeduser.userid,
email: this.loggeduser.email,
}),
});
}
According to this post, valueChanges() and onSnapshot() automatically return the changes taking place to the document or collection that they are listening to. get() is used to get the data only once.
To achieve what you would like to, you'll need to follow the instructions on
Get real time updates with Cloud Firestore.
Based on this documentation, I have tested this code sample and when I am updating a value in the database the new document with the updated data are returned.
async function monitorUser(uid){
const doc = db.collection('users').doc(uid);
const observer = doc.onSnapshot(docSnapshot => {
console.log(`Received doc snapshot:`, docSnapshot.data());
}, err => {
console.log(`Encountered error: ${err}`);
});
}
Then you can update the public variable that corresponds to your user's data with the new values and the view should be updated.

Categories