Securing Rules
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