Skip to content

temporary repository for the backend repo of the AI in SAR group

Notifications You must be signed in to change notification settings

RitvikS2201/temp-repo-for-SAR-backend

Repository files navigation

Security Rules

Security Rules are used to secure the database with user authentication and data validation.

Data being written to the database is accessed with request.data, info on the user writing to the database is accessed with request.auth, and data already in the database is accessed with resource.data.

Data types can be tested using the is condition. The supported data types are:

  • bool
  • int
  • float
  • number
  • string
  • list
  • map
  • timestamp
  • duration
  • path
  • latlong

Keys of a map can be accessed as a list of strings using data.keys().
The length of a map, list, or string can be determined using data.size().

In match statements, the values in braces in the path are available as variables. If the variable name is followed by =**, it will match the remainder of the path, including subcollections.

match /collection/{documentId} {
    allow write: if documentId is string
}

match /collection/{document=**} {
    // Matches on /collection/documentId, /collection/documentId/subcollection/anotherId, etc.
}

External documents in the database can be accessed with get(path), which fetches the document at the provided path, or exists(path), which returns a boolean representing if a document exists at the provided path.

The request.method variable is a string representing the CRUD operation: 'create', 'update', or 'delete'. Passing this argument allows for tests to be performed based on the operation the user is trying to commit. For example, the following condition can be used in to minimze fetches to other documents in the database.

(request.method == 'update' && request.data.field == resource.data.field) || request.data.field == get(/databases/$(database)/documents/incidents/$(incidentId)/collection/$(documentId)).data.field

This allows for the get() function, which is an external fetch, will only get called when a new document is created or, in the case of updating an existing document, the value of the field being written is different than what is already in the database.

Writing Security Rules

Start with the following template:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    function validPeriod(period) {
      return period is map &&
             period.keys() == ['begin', 'end'] &&
             period.begin is timestamp &&
             period.end is timestamp &&
             period.end > period.begin;
    }
	  
    function validUser(auth, user) {
      return user is map &&
	         user.keys.hasOnly(['uid', 'name', 'phone', 'email') &&
	         user.uid == auth.uid &&
	         user.name == auth.displayName &&
	         (user.phone == null || user.phone == auth.token.phone) &&
	         (user.email == null || user.email == auth.token.email)
    }
    
    function validComments(comments, required) {
      return (required != true && comments == null) ||
             (comments is string && comments.size() <= 2048);
    }
  
    match /incidents/{incidentId} {
      match /<collection>/{documentId} {
        function auth<Document>R() {
          return false;
        }

        function auth<Document>W() {
          return false;
        }

        function valid<Document>() {          
          let preparedBy = validUser(request.auth, request.data.preparedBy)
        
          let comments = validComments(request.data.comments, false);

          return field1 && preparedBy && comments;
        }
        
        allow read: if auth<Document>R();

        allow create: if request.data.keys().hasOnly(['field1', 'field2', 'preparedBy', 'comments']) && 
                         valid<Document>() && 
                         auth<Document>W();

        allow update: if request.data.keys().hasOnly(['field1', 'field2', 'preparedBy', 'comments']) && 
                         valid<Document>() && 
                         authDocumentW();

        allow delete: if auth<Document>W();
      }
    }
  }
}

Subject used as example document
Replace <collection> in match /<collection>/{documentId} with your collection name. (should be plural of document name)

match /subjects/{documentId}

Replace <Document> in auth<Document>R(), auth<Document>W(), and valid<Document>() with document name. (should be singular of collection name)

function authSubjectR() {}
function authSubjectW() {}
function validSubject() {}

In valid<Document>W(), test each field and store the condition in a variable. Then, after each field has been tested, return each variable ANDed together.
If a JSON schema exists for the document, the conditions must match the data types of each field according to the schema.
Optional fields must include a null condition, which will evaluate to true when the field is not present.

function validSubject() {
  let name = request.data.name is string
  let age = request.data.age == null || (request.data.age is int && request.data.age >= 0) // optional
  ...

  return name && age && ...
}

Under allow create and allow update, test for the keys in the data being submitted before calling valid<Document>() calling request.data.keys().hasOnly([]). The array passed to hasOnly([]) is a list of all required and optional keys for the document. This allows for short circuit evaluation ensuring the data being written contains the proper fields before any potentially stateful checks need to be made.

allow create: if request.data.keys().hasOnly(['name', 'age']) && validSubject() && authSubjectW()

References:

About

temporary repository for the backend repo of the AI in SAR group

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published