There doesn't seem to be a solid Firestore solution for this but I have a compound solution:
- A security rule on the first data collection accessed, where I check the email domain
- A catchError on the data retrieval, where I check for error code 'permission-denied'
- An error handler that will call the authentication service and force a logout with a redirect to an unauthorized page.
Sorry about pasting snippets, but I couldn't get the formatter to format all the code.
My component code:
ngOnInit(): void {
this.leadsDataSubscription = this.leadService.getLeadsSnapshot()
.pipe(
catchError((e: any) => Observable.throw(this.errorHandler(e)))
)
.subscribe(data => {
this.leadsDataSource.data = data;
this.leadsDataSource.paginator = this.paginator;
this.leadsDataSource.sort = this.sort;
});
this.currentUser = this.authSvc.getCurrentUser();
}
private errorHandler(error: any) {
if (error.name === 'FirebaseError' && error.code === 'permission-denied') {
this.leadsDataSubscription.unsubscribe()
this.authSvc.logout('/unauthorized');
}
}
My service code
logout(redirectURL?: string) {
this.unsubscribe()
this.afAuth.auth.signOut()
.then(response => {
this.snackBar.open('Signed out');
this.router.navigate([redirectURL || '/']);
})
.catch(error => this.snackBar.open('Error signing out: ' + error));
}
And the Firestore rules:
match /leads/{document=**} {
allow read: if isAllowedDomain() && isSignedIn();
allow update: if isAllowedDomain() && isSignedIn() && canUpdate()
allow delete: if isAllowedDomain() && isSignedIn() && isCreator() && canWrite() || isGod()
allow create: if isAllowedDomain() && isSignedIn() && userExists();
}
function isAllowedDomain() {
return request.auth.token.email_verified == true &&
request.auth.token.email.matches(".*@workdomain.se") ||
request.auth.token.email.matches(".*@privatedomain.org")
}