angulargoogle-cloud-firestoreangular-route-guards

Angular Route Guard based on collection query result


I have a 'Teams' collection in Firestore. A team document will include a team_users map field where userID: true if they are a part of that team.

I have a query in a service that returns and displays all team documents where the logged in user id matches an ID within the team_users field.

getTeamsList(user_id: string) {
  this.teamsCollection = this.afs.collection<any>(`teams`, ref => ref.where(`team_users.${user_id}`, "==", true))
  this.teams = this.teamsCollection.snapshotChanges().pipe(
  map(actions => actions.map(a => {
    const data = a.payload.doc.data();
    const id = a.payload.doc.id;
    return { id, ...data };
  }))
 ).pipe(shareReplay());
}

What I need to achieve is a route guard that performs the following:

  1. Prevents a user accessing a team they don't belong to / use to belong to. For example, if the user has a bookmark for a document whilst they were in Team A, but have since left, they should not be able to access this route and should instead be redirected back to the url of /teams
  2. If the user does not belong to any teams (the collection query doesn't produce any results), then no routes are accessible a part from the /teams listing url

Note: A user can be a part of multiple teams. If the user was a part of Team A, B and C and has bookmarks for pages in Team A, but has since left, the route guard should prevent them from accessing any pages that were a part of Team A, but still allow access to Team B and C pages.

Does this mean that:

  1. Every URL within my app will need this guard?
  2. That every URL within my app will need to contain the team ID?

Any help would be greatly appreciated.


Solution

  • A user is on /teams page displaying teams (You don't need a guard on this route). When a user clicks on one of the teams navigate to /teams/teamID (Now this route needs a guard to prevent a user that doesn't belong to teamID from accessing it. To set the guard do this:

    1. Write a function that checks on firestore if a user is a member of a team.

    isTeamMember(userId: string, teamId: string): Observable < Boolean > {
      return this.firestore.doc < any > (`teams/${teamId}`).snapshotChanges().pipe(
        map(value => value.payload.data().team_users[userId] === true));
    }

    1. Call the function in your Guard (CanActivate implemented like this). Remember to inject a service/store containing your current user and retrieve teamId from Url. The 2 are used to call the function on firestore

    canActivate(
      next: ActivatedRouteSnapshot,
      state: RouterStateSnapshot): Observable < boolean > | Promise < boolean > | boolean {
      return this.firestoreService.isTeamMember(userIdFromWhereYouStoreYourCurrentUser, teamIdFromTheCurrentUrl).pipe(map(isTeamMember => {
          if (isTeamMember) {
            return true;
          } else {
            //Add your not team member logic here, eg Stay in the current route, may be show an error message
            return false;
          }
        }),
        take(1));
    }