Securing Rules

In notebook:
FrontEndMasters Serverless
Created at:
2017-10-12
Updated:
2017-10-13
Tags:
backend JavaScript libraries React

We need to set up rules so that a user cannot change the image of another user. 1. not change his database record, and 2. not upload to his file storage

Securing the database

Goes to the web console / Database / Rules ☛ there aren't any rules set up

Rules cascade...

// this does not work:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null",
  "users": {
    ".write": "false"
  }
}

This, above rule, DOES NOT work.

The highest level of rule you can write at, you can write at all of the children

You have to be more specific:

{
  "rules": {
    ".read": "auth != null",
    // remove this:
    // ".write": "auth != null",
    // so nobody can write here
  "users": {
    ".write": "false"
  }
}

This is still not ideal. Any user can write to any other user.

{
  "rules": {
    ".read": "auth != null",
  "users": {
    "$userId": {
      ".write": "$userId === auth.uid"
    }
  }
}

Simulator

The web console has a simulator that lets you try out the rules.

More on the available security rules

The Key to Firebase Security: 45 minutes talk that goes through all the available options in depth. Another link for another great talk from the repo: https://gist.github.com/stevekinney/362fe71167f8de6f6346df0c4cf46199

Storage security

The concept is very similar

Goes to storage rules:

  service firebase.storage {
  match /b/ {bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

The above means that for the storage, anyone can read, as long as they are logged in.

We want to limit that a user only uploads to his own bucket and maybe also the file type, file size, etc.

  service firebase.storage {
  match /b/ {bucket}/o {
    allow read;
    match /user-images/{userId}/{allPaths=**} {
      allow write: if request.auth.uid == userId;
    }
  }
}

allPaths=** ☛ means that it can go any level deep.

allow write: if request.auth.uid == userId; ☛ only write to the user's folder.

This above logic can grow to be very (too) complex

  service firebase.storage {
  match /b/ {bucket}/o {
    allow read;
    match /user-images/{userId}/{allPaths=**} {
      allow write: if request.auth.uid == userId && request.resource.size < 5 * 1024; // less than 5MB
    }
  }
}

Instead, you can write JavaScript functions here!

  function isCorrectUser(userId) {
  return request.auth.uid == userId;
}

function isLessThanNMegabytes(n) {
  return request.resource.size < n * 1024
}

service firebase.storage {
  match /b/ {bucket}/o {
    allow read;
    match /user-images/{userId}/{allPaths=**} {
      allow write: if isCorrectUser(userId) && isLessThanNMegabytes(5);
    }
  }
}

This is more readable when you come back 6 months later...

Question: Does the white space matter in the rules?

No, you can format your code to make it more readable.


Now, he gets a 403 not allowed error, when he tries to upload an image for someone else than him.

Progress bar for file upload

For now we're just going to do it in the console.log()

The uploadTask returns a Promise and also emits an event as it is uploading.

  //	****		ProfileCard.js		****

import React, { Component, PropTypes } from 'react';
import FileInput from 'react-file-input';
import { storage, database } from './firebase';
import './ProfileCard.css';

class ProfileCard extends Component {
  constructor(props) {
    super(props);
    
    this.storageRef = storage.ref('user-images').child(props.uid)
    this.userRef = database.ref('/users').child(props.uid)
    this.handleSubmit = this.handleSubmit.bind(this)
  }
  
  handleSubmit(event) {
    const file = event.target.files[0] 
    const uploadTask = this.storageRef.child(file.name)
    // ****  listen to events.  ↴
    uploadTask.on('state_changed', (snapshot) => { // listen to any state change
      console.log(snapshot.bytesTransferred / snaphsot.totalBytes * 100 + '%') // snapshot.bytesTransferred is what we need
    }) 
    uploadTask.then(snapshot => {
      this.userRef.child('photoURL').set(snapshot.downloadURL)
    })
  }

  render() {
    const { displayName, photoURL } = this.props.user
    return (
      <article className="ProfileCard">
        ..
      </article>
    );
  }
}

ProfileCard.propTypes = {...};

export default ProfileCard;

snapshot.bytesTransferred will show tell the progress