Lesson 23

[Sprint Four] - Client Survey

Providing a secure way to update client history

PRO

Lesson Outline

Now it's time to get started on our last major feature for this module, and really a core feature of this entire application. This uses the fundamental novel idea we came up with in the beginning related to the data/security model we designed for our Firestore database.

User story: As a massage therapist, I want to be able to send a client a link to the questionnaire that they can complete online, so that the client doesn't need to spend time filling out the questionnaire once they arrive


Project management

Remember to move the card for this task to the Test column, and create a new task branch to work on.


We put some thought into how this would all work earlier on in our prototype, so let's review that now. We have our sequence diagram that we created earlier to show the general flow of what should happen:

Client Survey Update

and we have the security rules we tested in the prototype:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

  	match /clients/{client} {
      allow update: if true;
      allow create, read, delete: if isAdmin();
    }

    match /{document=**} {
      allow read, write: if isAdmin();
    }
  }

  function isAdmin(){
  	return request.auth != null && request.auth.token.email == '[email protected]' && request.auth.token.email_verified;
  }
}

The key problem we had to solve here is that we want an unauthenticated user to be able to update the survey property of a client in the clients collection so that they can submit their survey response:

survey: ['JSON STRING 1', 'JSON STRING 2'];

But we don't want people to be able to just update anyone elses survey, and we certainly don't want them to be able to read anybody's survey. Our rules solve half of this problem: only the admin can read the surveys, but anybody can update them.

The way that we have solved this issue will be by supplying the client a link to the page that will contain the survey, and we will include the id of their document from the clients collection in the URL. We will then use that id to determine which document to update. This way the client can only update their survey if they are given a link containing the id, and they won't be able to update anybody elses because they won't know the id.

In any case, even if an id was compromised the worst someone can do is update the form with false data - they won't be able to retrieve any previous results because they don't have read permission. Given that the responses will also be confirmed in person at the appointment, this isn't really an issue.

To complete this user story there will be a few main parts:

  1. Implement the required security rules
  2. Implement the survey form
  3. Create a way for the admin to retrieve the link to the survey form containing the clients id (so that they can send the client the link)

We might consider breaking these down into their own smaller issues, each with their own task branch, but I think this work is small enough to just do all at once (I think that now at least).

Updating Security Rules

Let's start by implementing our security rules. We're back to testing Firestore security rules again, hopefully you're starting to get familiar with this!

Let's add our new test to firestore.rules.spec.ts:

  it('unauthenticated users can ONLY update documents in the clients collection', async () => {
    const db = getFirestore();
    const newDoc = doc(db, 'clients', 'testDoc');
    const existingDoc = doc(db, 'clients', 'existingDoc');

    await Promise.all([
      assertFails(getDoc(existingDoc)), //read
      assertFails(setDoc(newDoc, { foo: 'bar' })), // create
      assertSucceeds(setDoc(existingDoc, { foo: 'bar' })), // update
      assertFails(deleteDoc(existingDoc)), // delete
    ]);
  });

Make sure that it fails by running npm run test:rules:

 FAIL  firestore-test/firestore.rules.spec.ts (8.201 s)
  Firestore security rules
    ✓ non admin user can not read/write from the clients collection (3931 ms)
    ✓ non admin user can not read/write from the notes collection (255 ms)
    ✓ admin user can read/write from the clients collection (184 ms)
    ✓ admin user can read/write from the notes collection (115 ms)
    ✓ unauthenticated users can ONLY create documents in the feedback collection (127 ms)
    ✕ unauthenticated users can ONLY update documents in the clients collection (120 ms)

  ● Firestore security rules - unauthenticated users can ONLY update documents in the clients collection

    FirebaseError: 7 PERMISSION_DENIED:

      false for 'update' @ L11

Great - the update is failing but we want it to succeed. Now let's update the rules in our firestore.rules file:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

  	match /clients/{client} {
      allow update: if true;
      allow create, read, delete: if isAdmin();
    }

    match /feedback/{feedbackId}{
      allow create: if true;
    }

    match /{document=**} {
      allow read, write: if isAdmin();
    }
  }

  function isAdmin(){
  	return request.auth != null && request.auth.token.email == '[email protected]' && request.auth.token.email_verified;
  }
}

and now let's try the test again:

 FAIL  firestore-test/firestore.rules.spec.ts
  Firestore security rules
    ✕ non admin user can not read/write from the clients collection (1198 ms)

This is actually causing a previous test to fail, where we checked that a non admin user had no access to the client collection:

  it('non admin user can not read/write from the clients collection', async () => {
    const db = getFirestore();
    const newDoc = doc(db, 'clients', 'testDoc');
    const existingDoc = doc(db, 'clients', 'existingDoc');

    await Promise.all([
      assertFails(getDoc(existingDoc)), //read
      assertFails(setDoc(newDoc, { foo: 'bar' })), // create
      assertFails(setDoc(existingDoc, { foo: 'bar' })), // update
      assertFails(deleteDoc(existingDoc)), // delete
    ]);
  });

We can just delete this test now. Then if we try out tests again:

 PASS  firestore-test/firestore.rules.spec.ts
  Firestore security rules
    ✓ non admin user can not read/write from the notes collection (1213 ms)
    ✓ admin user can read/write from the clients collection (161 ms)
    ✓ admin user can read/write from the notes collection (123 ms)
    ✓ unauthenticated users can ONLY create documents in the feedback collection (123 ms)
    ✓ unauthenticated users can ONLY update documents in the clients collection (125 ms)

Perfect! Now let's make sure we deploy those rules using:

PRO

Thanks for checking out the preview of this lesson!

The full version of this lesson is only available to pro members. If you would like full access to this module and all of the other pro modules on Elite Ionic you can become a pro member (or log in if you are already a member).