Firebase - Best way to safely check if and value exists - javascript

I'm having a list of E-Mail subscribers and want to add a new one on button click. This should be possible for non-logged-in users. Therefore there need to be a check if this entry is already in the database. As I don't want to expose the full list to the non-logged-in user, how would I check if an E-Mail address already exists without making a fetch GET request that is having rights to access that list with firebase, as the whole code is exposed in the frontend? I'm working with version Web9.
import { initializeApp } from "firebase/app";
import { getFirestore, collection, addDoc, getDocs, query, where } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { getAnalytics } from "firebase/analytics";
const firebaseConfig = {
apiKey: "",
authDomain: "",
projected: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore();
const auth = getAuth(app);
Fuction for adding the doc:
const addDocument = (dbName, obj) => {
return addDoc(collection(db, dbName), obj);
};

With the Firestore security rules you can break down a read rule into get and list as explained in the doc.
In your case you would assign read access to everybody with the get rule and deny access to the list rule. This way, the unauthenticated users can only fetch a specific document (identified with a specific email address) and verify if this given "entry" is already in the database. They cannot list all the existing entries.
Of course, a user could try many different email addresses but will not be able to directly get the list of all existing entries in the database with one query.

Here is my code solution that hopefully helps somebody. The goal has been to check if a single document exists and restrict the database access to check existence. I found only one possible option to do this (Excluding cloud functions). Thanks to #RenaudTarnec answer. There is no way to get a single doc with a query by field value. The only option is to set the id of the document to the specified E-Mail address (value you want to query).
Code:
Check if already subscribed, if not add to database of subscribers:
const [error, setError] = useState("");
const [message, setMessage] = useState("");
const handleSubmit = async (values: { email: string }) => {
const dbName = "newsletterSubscribers";
const docId = values.email;
setMessage("");
setError("");
const docRef = doc(db, dbName, docId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
// E-Mail address already subscribed, data: docSnap.data())
setMessage("E-Mail address already subscribed");
} else {
// E-Mail is not yet subscribed
try {
const docRef = await setDoc(doc(db, dbName, docId), {
createdAt: serverTimestamp(),
});
alert("Thank you for subscribing, you will receive all news and updates to " + values.email);
} catch (e) {
// Error adding document
setError("Failed to subscribe. We're sorry there was an error.");
}
}
};
Security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /newsletterSubscribers/{subscriberId} {
allow get: if true;
allow list: if false;
allow create: if true;
}
}}

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.

Is there a way to write this firebase 8 function in firebase 9?

I got this function to add data to a Firestore db and was wondering how to do it in the newer version.
db.doc(`User/${fields.user}/Address/${fields.address}`)
.set({
User: fields.user,
Address: fields.address,
})
.then(
db.doc(`User/${fields.user}/Address/${fields.address}/Orders/${fields.ID}`)
.set({
ID: fields.ID,
});
This function is to add a document with data in a collection then create a subcollection with a diferent document with its own data. The document id are form inputs.
You first need to use doc() function to create a DocumentReference for the documents and then use setDoc() function to add document in Firestore as mentioned in the documentation.
import { doc, setDoc } from "firebase/firestore"
// here db is getFirestore()
const docRef = doc(db, `User/${fields.user}/Address/${fields.address}`)
await setDoc(docRef, { test: "test" })
Alternatively you can use a batched write to add both the documents at once. Try refactoring the code as shown below:
import {
writeBatch,
doc
} from "firebase/firestore";
// Get a new write batch
const batch = writeBatch(db);
const docRef = doc(db, `User/${fields.user}/Address/${fields.address}`);
batch.set(docRef, {
User: fields.user,
Address: fields.address
});
const subDocRef = doc(db, `User/${fields.user}/Address/${fields.address}/Orders/${fields.ID}`);
batch.update(subDocRef, {
ID: fields.ID
});
// Commit the batch
batch.commit().then(() => {
console.log("Documents added")
}).catch(e => console.log(e));
Also checkout: Firestore: What's the pattern for adding new data in Web v9?

Adding documents to firestore using javascript

I'm trying to create a function to add documents to firestore. At the moment this function could be called two different ways. 1) Adding a user to a collection called 'Users'. 2) adding a site to a collection called 'Sites'.
This function should take the following parameters:
The name of the collection (required)
The users uid (make this optional)
An object with the data for a site (make this optional)
I'm also trying to utilize JS modules to keep my code better organised. At the moment my folder looks something like this:
myApp
assets
img
dist
index.html
index.js
modules
auth.js
firestore.js
Inside auth.js I have a signUp() inside witch I want to call a function called addToFirestore() (this comes from firestore.js). My code looks something like this:
firestore.js
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.6.4/firebase-
app.js';
import { getFirestore, setDoc, doc } from
'https://www.gstatic.com/firebasejs/9.6.4/firebase-firestore.js';
const firebaseConfig = {
...
};
// Initialize Firebase
initializeApp(firebaseConfig);
const db = getFirestore;
function addToFirestore(collName, user = 0, data = 0) {
// check if adding user
if (user != 0 && data == 0){
//adding user to firestore
try {
setDoc(doc(db, collName, user.uid), {
email: user.email,
});
} catch (e) {
console.error('Error adding document: ', e);
}
// check if adding site
} else if (data != 0 && user == 0) {
setDoc(doc(db, collName), data);
}
export { addToFirestore};
Inside auth.js calling function like this:
// * Auth
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from
'https://www.gstatic.com/firebasejs/9.6.4/firebase-auth.js';
import {addToFirestore} from '/modules/firestore.js';
function signUp(email, password) {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
addToFirestore('Users', user);
})
.then(() => {
openApp();
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
alertAuth.classList.remove('d-none');
alertAuth.classList.add('show');
alertAuth.innerHTML = `<strong>Error: </strong> ${errorCode}`;
});
}
And inside index.js calling signUp():
btnAuthSignUp.addEventListener('click', function () {
event.preventDefault();
let email = inpAuthEmail.value;
let password = inpAuthPassword.value;
signUp(email, password);
});
And it is giving me an error like this:
firestore.js:31 Error adding document: FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
I tried doing everything in one file and still got the same problem. Any help would be greatly appreciated.
As stated on the error you encountered:
Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore.
Firestore expects a collection reference to pass the data to. You didn't specify your collection reference. You should put a collection reference by using given code below:
const collectionRef = doc(db, 'collection_name', user.uid);
db here is not a collection reference. Its just an instance of Firestore:
const db = getFirestore;
Then use it as code below:
setDoc(collectionRef, {
email: user.email,
});
You could also check Add data to Cloud Firestore for more information.

Users duplicate on Firestore when I use googleSignIn

I have two ways to register a user in Firebase: through email and through Google Sign In.
I perform the user registration by email as follows:
signUp() {
const auth = getAuth();
const db = getFirestore();
createUserWithEmailAndPassword(
auth,
this.createEmail,
this.createPassword
).then(
(userCredential) => {
const user = userCredential.user;
this.$router.push("/");
addDoc(collection(db, "users"), {
email: this.createEmail,
name: this.createName,
});
},
);
},
In other words, in addition to saving the user in Firebase Authentication, I also send their name and email to Firestore. And this is my first question:
Is it the most effective way to save the username and future data that will still be added to it?
Finally, login by Google is done as follows:
googleSignIn() {
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((result) => {
this.$router.push("/");
addDoc(collection(db, "users"), {
email: result.user.email,
name: result.user.displayName,
});
})
},
Here a problem arises because if a user logs in more than once in Firebase Authentication everything is ok, but in Firebase Firestore a user is created for each new login with Google.
How do I handle this issue of storing users in Firestore, especially users coming from Google Login?
First, I'd move the router.push() statement below addDoc() so I can confirm that the document has been added and then user is redirected to other pages. In case of Google SignIn, you can check if the user is new by accessing the isNewUser property by fetching additional information. If true, then add document to Firestore else redirect to dashboard:
signInWithPopup(auth, provider)
.then(async (result) => {
// Check if user is new
const {isNewUser} = getAdditionalUserInfo(result)
if (isNewUser) {
await addDoc(collection(db, "users"), {
email: result.user.email,
name: result.user.displayName,
});
}
this.$router.push("/");
})
It might be a good idea to set the document ID as user's Firebase Auth UID instead of using addDoc() which generated another random ID so it's easier to write security rules. Try refactoring the code to this:
signInWithPopup(auth, provider)
.then(async (result) => {
// Check if user is new
const {isNewUser} = getAdditionalUserInfo(result)
const userId = result.user.uid
if (isNewUser) {
await setDoc(doc(db, "users", userId), {
email: result.user.email,
name: result.user.displayName,
});
}
this.$router.push("/");
})

How to get document by current signed in user in Google Firestore

I am trying to get a specific document that is created when a user signs up. I made it so that when the use signs up, their firebase user ID is the name of the document.
I can't seem to figure out how to call that document.
I have tried: firebase.auth().currentUser.uid;
uid
userId (from computed)
and several other related things to the uid.
When I look for a solution online I see the exact examples I have tried and I am wondering if there has been a change to how uid works maybe?
<script>
// eslint-disable-next-line
import firebase from "firebase";
// eslint-disable-next-line
import firestore from "firestore";
import db from "#/components/fbInit";
export default {
components: {},
data() {
return {
users: []
};
},
computed: {
userId() {
return firebase.auth().currentUser.uid;
}
},
created() {
db.collection("userProfiles")
.document(userId)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
const data = {
id: doc.id,
name: doc.data().name,
company: doc.data().company,
state: doc.data().state
};
this.users.push(data);
});
});
}
};
</script>
Usually I get no error but it doesn't load any document. Sometimes it will say "uid" or whatever I am trying to use is undefined.
This is the solution for anyone else wondering. unique is a field I set to match the user ID when a user is created. Alternatively I believe you can use author to get the author of the document.
var user = firebase.auth().currentUser
db.collection("userProfiles").where('unique', '==', user.uid).get()
.then(querySnapshot => {
querySnapshot.forEach(doc => { }

Categories