0
votes

Short version

I'm using firestore to build an application where users can create courses, add resources to these courses, and then have other users sign up to their courses which would give them access to all of its resources. I'm struggling to get the security rules set up so that only the course organiser or course members can access the resources. My main problem is allowing a user to list all resources within a course they have signed up for, as list requests don't allow queries in the security rules which I need to check if that user is a member of that course. What is the best approach that would allow hundreds (or potentially thousands) of course members to easily list all of a courses resources, while ensuring it's not possible for non-members to access it?

In the limitations section of the docs it specifically mentions moving the roles into a separate collection for large or complex groups, but doing so will require the security rules performing a query to check access which is not possible for list requests.


Full verison

I'm creating a prototype web application with clients directly accessing firestore. Eliminating the need for a backend to handle simple data access is refreshing, but I'm now really struggling to work out the correct data structure and approach for my use-case, particularly around implementing appropriate security rules.

I've worked extensively with relational databases, but am quite new to nosql databases and firestore specifically which doesn't help.

Concept

The main collections in my application are users, courses, and resources. Users can create courses within which they create learning resources, other users can then sign up for these courses and therefore have access to all of the resources within them. As some courses will be invite only it can not be possible for a user to get access to resources for a course they are not a member of.

The main requirements are:

  • A course organisor needs full read and write access to their courses and resources.
  • A course member needs full read access to their courses and resources.
  • Organisors and members both need to be able to easily list all of their courses, and for each course list all of their resources.
  • Organisors and members should not be able to retrieve information about courses or resources that they do not own or belong to.

My approach

To start with my approach is to heavily utilise sub-collections. For the organisor perspective users have courses which have resources, and so I modelled it that way.

users -> courses -> resources

Security rules for the organisor were pretty straightforward here too, as the user id is part of the document path it can be easily checked with the user uid available (thanks firebase auth).

i.e

match /users/{userId}/courses/{courseId}/resources/{resourceId} {
    allow read, write, update, delete: if (request.auth.uid == userId) 
}

Giving members read-only access proved a little bit more difficult though. I needed somewhere appropriate to store that mapping. I ended up trying to use a sub-collection of users to store that, so course memberships could be looked up with: /users/{userId}/course_memberships/{courseId}

The theory being I could then write a security rule to grant read access to courses and resources that looked like:

match /users/{userId}/courses/{courseId}/resources/{resourceId} {
    allow read: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/course_memberships/${courseId})
}

However this only works when retrieving a single document, list requests won't evaluate a query in the security rules, therefore this set up doesn't allow a course member to view a list of all resources in a given course.

I can't work out what the correct approach here is, the only options I see are:

  1. Create a separate collection hierarchy for each member that contains all of their courses and all of their resources, including key information used for listing their resources, and synchronise these using cloud functions. I understand that nosql is about storing denormalised data, but this feels like a lot of extra data, particularly when it's only necessary for security rules.

  2. Add the uid of every member to an array on every resource (could be in the thousands for each), which also feels like a lot of extra data and it would be exposing those ids to all other members which seems wrong.

Is there a better approach here, or is firestore not well suited for this type of problem?

Hopefully this question hasn't been answered already, I did a search and couldn't find something covering quite the same scenario.

1

1 Answers

0
votes

A read rule can be broken into get and list, and the latter can apply to queries. Example:

allow list: if [condition];

More information can be found here.