flamelink / flamelink-js-sdk

🦊 Official Flamelink JavaScript SDK for both the Firebase Realtime database and Cloud Firestore
https://flamelink.github.io/flamelink-js-sdk
MIT License
43 stars 5 forks source link

Firestore And Storage Security Rules for Flamelink projects. #86

Open ribalnasr opened 5 years ago

ribalnasr commented 5 years ago

Hi,

I've been working on a set of rules for Flamelink that i'd love to share with you. Forgive me if it's not the right place for it, and feel free to move it to where it belongs.

EDITED: Find firestore updated rules below

jperasmus commented 5 years ago

Hi @ribalnasr

Thanks so much for doing this. I'm sure other users will also find this useful. I'll see where we can add this to our documentation.

Danelund commented 4 years ago

@ribalnasr - thanks for this awesome work. Would love to implement it in my solution. Currently however - if I implement these security rules I am unable to edit from flamelink CMS, as no permissions are set from there. How would I implement to have it work there as well?

jperasmus commented 4 years ago

Amazing work, guys. I've included this in one of the Flamelink articles for other developers to reference: https://intercom.help/flamelink/en/articles/3068550-flamelink-and-cloud-firestore

ribalnasr commented 4 years ago

lovely update @Danelund , this is is exactly what i was trying to achieve without using custom claims but i couldn't at the time...

i remember your updated code was here in this thread before but its not now.. @jperasmus i made some updates after testing it in multiple scenarios and found some bugs, mainly typos.

please update the docs as the current ones do not allow creating new schemas or updating existing entries from the flamelink cms.

PS: It would be perfect if the guest profile is automatically created upon creating the project in flamelink cms.

here's the updated code:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    function guestPermissions() {
      return get(/databases/$(database)/documents/fl_permissions/guest).data
    }  

    function userPermissions(){
      return get(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions).data
    }

    function isContentPermitted(schema, action) {
      return guestPermissions().content.production[schema][action] == true || (request.auth != null && (userPermissions().content.production[schema][action] == true || userPermissions().id == '1'))
    }

    function isCollectionPermitted(collection, action) {
      return guestPermissions()[collection][action] == true || (request.auth != null && userPermissions()[collection][action] == true)
    }

    function isCollectionPermittedProduction(collection, action) {
      return guestPermissions()[collection].production[action] == true || (request.auth != null && userPermissions()[collection].production[action] == true)
    }

    function isSettingsPermitted(settings, action) {
      return guestPermissions().settings[settings][action] == true || (request.auth != null && userPermissions().settings[settings][action] == true)
    }

    match /fl_content/{document=**} {
      allow read: if isContentPermitted(resource.data._fl_meta_.schema, 'view') ;
      allow update: if isContentPermitted(request.resource.data._fl_meta_.schema, 'update');
      allow create: if isContentPermitted(request.resource.data._fl_meta_.schema, 'create');
      allow delete: if isContentPermitted(resource.data._fl_meta_.schema, 'delete');
    }

    match /fl_environments/{document} {
      allow read: if isSettingsPermitted('environments', 'view');
      allow update: if isSettingsPermitted('environments', 'update');
      allow create: if isSettingsPermitted('environments', 'create');
      allow delete: if isSettingsPermitted('environments', 'delete');
    }

    match /fl_files/{document=**} {
      allow read: if isCollectionPermitted('media', 'view');
      allow update: if isCollectionPermitted('media', 'update');
      allow create: if isCollectionPermitted('media', 'create');
      allow delete: if isCollectionPermitted('media', 'delete');
    }    

    match /fl_folders/{document=**} {
      allow read: if isCollectionPermitted('media', 'view');
      allow update: if isCollectionPermitted('media', 'update');
      allow create: if isCollectionPermitted('media', 'create');
      allow delete: if isCollectionPermitted('media', 'delete');
    }    

    match /fl_locales/{document=**} {
      allow read: if isSettingsPermitted('locales', 'view');
      allow update: if isSettingsPermitted('locales', 'update');
      allow create: if isSettingsPermitted('locales', 'create');
      allow delete: if isSettingsPermitted('locales', 'delete');
    }   

    match /fl_navigation/{document=**} {
      allow read: if isCollectionPermittedProduction('navigation', 'view');
      allow update: if isCollectionPermittedProduction('navigation', 'update');
      allow create: if isCollectionPermittedProduction('navigation', 'create');
      allow delete: if isCollectionPermittedProduction('navigation', 'delete');
    }

    match /fl_permissions/{document=**} {
      allow read: if isCollectionPermitted('permissions', 'view');
      allow update: if isCollectionPermitted('permissions', 'update');
      allow create: if isCollectionPermitted('permissions', 'create');
      allow delete: if isCollectionPermitted('permissions', 'delete');
    }

    match /fl_schemas/{document=**} {
      allow read: if isCollectionPermittedProduction('schemas', 'view');
      allow update: if isCollectionPermittedProduction('schemas', 'update');
      allow create: if isCollectionPermittedProduction('schemas', 'create');
      allow delete: if isCollectionPermittedProduction('schemas', 'delete');
    }    

    match /fl_settings/{document=**} {
      allow read: if isSettingsPermitted('general', 'view');
      allow update: if isSettingsPermitted('general', 'update');
      allow create: if isSettingsPermitted('general', 'create');
      allow delete: if isSettingsPermitted('general', 'delete');
    }

    match /fl_users/{document=**} {
      allow read: if request.auth.uid == resource.id || isCollectionPermitted('users', 'view');
      allow update: if request.auth.uid == resource.id || isCollectionPermitted('users', 'update');
      allow create: if isCollectionPermitted('users', 'create');
      allow delete: if isCollectionPermitted('users', 'delete');
    }   

    match /fl_backups/{document=**} {
      allow read: if isSettingsPermitted('backups', 'view');
      allow update: if isSettingsPermitted('backups', 'update');
      allow create: if isSettingsPermitted('backups', 'create');
      allow delete: if isSettingsPermitted('backups', 'delete');
    }

    match /fl_workflows/{document} {
      allow read: if isSettingsPermitted('workflows', 'view');
      allow update: if isSettingsPermitted('workflows', 'update');
      allow create: if isSettingsPermitted('workflows', 'create');
      allow delete: if isSettingsPermitted('workflows', 'delete');
    }

    match /fl_webhooks/{document} {
      allow read: if isCollectionPermitted('webhooks', 'view');
      allow update: if isCollectionPermitted('webhooks', 'update');
      allow create: if isCollectionPermitted('webhooks', 'create');
      allow delete: if isCollectionPermitted('webhooks', 'delete');
    }

    match /fl_webhooks/{document}/activityLog/{log} {
      allow read: if isCollectionPermitted('webhooks', 'view');
      allow update: if isCollectionPermitted('webhooks', 'update');
      allow create: if isCollectionPermitted('webhooks', 'create');
      allow delete: if isCollectionPermitted('webhooks', 'delete');
    }

  }
}
gitdubz commented 4 years ago

Thanks a million! I have updated the article with the latest code updates.

Danelund commented 4 years ago

@ribalnasr - excellent idea! @gitdubz I thoroughly second the idea of having a default guest account created by flamelink (i.e. with id 0), which would mean that these rules could be copied in by anyone and work straight away.

Next step in improving the rules would be to evaluate [env], as currently they only set permissions for the production environment.

ribalnasr commented 4 years ago

hello people! :) @gitdubz, small bug fix that should be updated in the docs,

under match /fl_content/{document=} { allow read: if isContentPermitted(resource.data._flmeta.schema, 'view') ; allow update: if isContentPermitted(request.resource.data._flmeta.schema, 'update'); allow create: if isContentPermitted(request.resource.data._flmeta.schema, 'create'); allow delete: if isContentPermitted(resource.data._flmeta.schema, 'delete');** }

update the delete action to be resource instead of request.resource. this was preventing deleting a doc by anyone except the sudo user.

@Danelund thank you and i agree about environment, i personally can't work on it soon so please feel free to update it

peace.

gitdubz commented 4 years ago

Thank you so much for continuously updating this! I will update the docs on our end to reflect the changes.

As for the guest user suggestion, I will table it, but I think because we are limited to the number of users on the payment plans this might be possible right now.

ribalnasr commented 4 years ago

a guest and a sudo profile within the free plan.. just a thought :)

gitdubz commented 4 years ago

It would not be too difficult to add a guest permission group with only read/view permissions. It will then just keep in sync the same way Super Admin (permission id 1) does but only for those privileges.

rileynetadmin commented 4 years ago

What is the best way to secure content to the user who created the content?

YariKoen commented 3 years ago

Hi, I am trying to login to my Flamelink, after I updated Firestore rules for my app. I added your rules and in test (Rules Playground), everything is OK / passed. But when I try to login to Flamelink Dashboard, access is denied. Honestly, I don't understand why - I removed the global rule for "deny all", bcs in default (without a match) is all denied. It seems that in these rules is something missing. If I use "allow all" global rule, the login works fine.

I also tried to just allow read & write for all fl_ collections if user is authenticated - without success. Am I missing something about Flamelink? 😅 It's very frustrating.

ribalnasr commented 3 years ago

hey @YariKoen,

in the firebase console, inside firestorm, find your user under fl_users, does it have permissions = fl_permissions/1 ?

YariKoen commented 3 years ago

@ribalnasr yea, there are two doc's in fl_users - first have field permissions = reference on fl_permissions/1, second (same field) have string 1. Not sure why there are two with the same email.

ribalnasr commented 3 years ago

the problem seems to be there, change the one with a string value to a reference on 'fl_permissions/1', and check the user uid under authentication then compare it to the ids in the fl_users collection. delete the duplicated user with the wrong id.

YariKoen commented 3 years ago

@ribalnasr It works, thanks! 🎉

gitdubz commented 3 years ago

Hi @ribalnasr,

We have a user who has implemented the rules and mentioned that they are unable to log in, so I tried to replicate the issue and found that the emulator does give an error.

the user permissions is a reference

{ 
  ... 
  permissions: 'fl_permissions/LXmaoasdp1' // reference
}

The function that is giving an error 👇

function userPermissions(){
  return get(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions).data
}

Screenshot 2021-05-05 at 09 45 10

I have tried to see if the following works


function getPermissionValue(r) {
  // return get(rel).data // 1
  // return get(rel.path).data // 2
  // return get(get(/databases/$(database)/documents/fl_permissions/$(r.id)).data // 3
  // return get(get(/databases/$(database)/documents/$(r.path)).data // 4
  return get(/databases/$(database)/documents/fl_permissions/LXmaoasdp1).data // sanity check - works.
}

function userPermissions(){
  return getPermissionValue(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions))
}

From what I can tell the rules does not allow for a reference value. Have you faced a similar issue at some point?

ribalnasr commented 3 years ago

hi @gitdubz, sorry for the late reply.. not really, been using these rules for a while now without updating them and haven't experienced any issue.

the error here seems to be that the result of the first get() (the inner one) does not return a valid document on the permissions field, hence the parent get() function is unable to work.

are we sure the user.permissions reference refers to an existing document? i have noticed sometimes that the default permissions field for the sudo user when starting a new flamelink project is [object Object] for some reason instead of a path to document reference and i had to change this manually. could that be the case?

gitdubz commented 3 years ago

Thanks @ribalnasr,

The document in question is a reference, I even created on manually to make sure. Thanks for the reply, I have reached out to the Firebase team for more assistance.

ribalnasr commented 3 years ago

i might be able to assist you further if it's possible to have access to the firebase project. let me know if i can help.