I am building a permissions-based auth system with Firebase authentication and Cloud Firestore and I am having trouble checking for a user doc at login. The app is built using Angular 6 and angularfire2.
The idea is that once a user logs in the app will check to see if a user document has been created for the user. If not, it will create a user doc and fill it with default permissions and user information.
Here is my code and the two functions I have tried:
import { Injectable } from '@angular/core';
import { User } from './../models/user.model';
import { PermissionsService } from './permissions.service';
import { auth } from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import {
AngularFirestore,
AngularFirestoreDocument,
AngularFirestoreCollection,
} from 'angularfire2/firestore';
import { Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class AuthService {
usersCollection = null;
user: Observable<User>;
constructor(
private afAuth: AngularFireAuth,
private db: AngularFirestore,
private permissionsService: PermissionsService,
) {
this.usersCollection = db.collection('users');
this.user = this.afAuth.authState.pipe(
switchMap((user) => {
if (user) {
return this.db
.doc<User>(`users/${user.uid}`)
.valueChanges();
} else {
return of(null);
}
}),
);
}
loginGoogle() {
const provider = new auth.GoogleAuthProvider();
return this.oAuthLogin(provider);
}
loginFacebook() {
const provider = new auth.FacebookAuthProvider();
return this.oAuthLogin(provider);
}
loginTwitter() {
const provider = new auth.TwitterAuthProvider();
return this.oAuthLogin(provider);
}
oAuthLogin(provider) {
return this.afAuth.auth.signInWithPopup(provider).then((credential) => {
// My first attempt using .where to find a doc with matching email
// This gives an error saying .where() does not exist
const docExists = this.usersCollection.where(
'email',
'==',
credential.user.email,
);
if (docExists) {
console.log('User logged in');
} else {
console.log('user does not exist');
this.createUser(credential.user);
}
//My second attempt using .get() to find a doc with matching uid
//The id of the doc matches the uid of the auth user by design
//This gives an error saying .get() does not exist
// this.usersCollection
// .doc(credential.user.uid)
// .get()
// .then((docSnapshot) => {
// if (docSnapshot.exists) {
// console.log('User logged in');
// } else {
// console.log('user does not exist');
// this.createUser(credential.user);
// }
// });
});
}
createUser(user) {
console.log('creating user');
const newUser: User = {
uid: user.uid,
email: user.email,
photoURL: user.photoURL,
displayName: user.displayName,
roles: {
member: true,
},
permissions: this.permissionsService.memberPermissions,
};
this.usersCollection
.add(newUser)
.then((docRef) => {
console.log('added new user');
newUser.uid = docRef.id;
docRef.set(newUser);
})
.catch((err) => {
console.log('Error adding user: ' + err);
});
}
logout() {
this.afAuth.auth.signOut();
this.user = null;
}
}
Here are the errors that are thrown:
zone.js:192 Uncaught TypeError: _this.usersCollection.where is not a function
and
zone.js:192 Uncaught TypeError: _this.usersCollection.doc(...).get is not a function
I'm pretty new to firebase and especially new to Firestore (I guess everyone is) and I can not find anything in the documentation of angularfire2 or firebase that would indicate why I can't do this.
Please help me understand why these functions are not viewed as valid.
Also, if you have any tips or comments on how I might go about this whole auth process in a better way feel free to add a comment. This is my first attempt at creating an auth service like this.
// This gives an error saying .where() does not exist:
Well that's because .where()
does not exist on type AngularFirestoreCollection
. You're assinging it by this.usersCollection = db.collection('users')
in the constructor.
Querying a collection can be done this way:
const docExists = this.afs.collection<User[]>('users', ref => ref.where('email', '==', credential.user.email));
But this will not solve your issue. In previous statement, docExist
will also be an AngularFirestoreCollection
, and the check on if (docExists) {
will always be true. So what your looking for is a way to check if a document exists based on a query:
private oAuthLogin(provider: any) {
return this.afAuth.auth
.signInWithPopup(provider)
.then(credential => {
const usersCollection = this.afs.collection<User[]>('users', ref => ref.where('email', '==', credential.user.email));
const users = usersCollection.snapshotChanges()
.pipe(
map(actions => {
return actions.map(action => {
const data = action.payload.doc.data();
const id = action.payload.doc.id;
return { id, ...data };
});
}),
take(1));
users.subscribe(snap => {
if (snap.length === 0) {
console.log('user does not exist');
this.createUser(credential.user);
} else {
console.log('User logged in');
}
});
});
}