Forked from aliraja-dev/firestore-security-rules-examples.js
Created
October 18, 2022 03:33
-
-
Save winterdl/6213ea10c92980e559a4fc242555be17 to your computer and use it in GitHub Desktop.
Firestore Rules
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Firestore Security Rules - Examples | |
| Default: Every resource is locked down by default, and its a redundant rule, but can be a good reminder in the start of rules file | |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| match /{document=\*\*} { | |
| allow read, write: if false; | |
| } | |
| } | |
| } | |
| Only authenticated users can read & write to the buckets, cloud storage | |
| service firebase.storage { | |
| match /b/{bucket}/o { | |
| match /{allPaths=\*\*} { | |
| allow read, write: if request.auth != null; | |
| } | |
| } | |
| } | |
| ## Authenticated users only | |
| Allow authenticated users to Read and write to all collections and subcollections | |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| match /{document=\*\*} { | |
| allow read, write: if request.auth != null; | |
| } | |
| } | |
| } | |
| service firebase.storage { | |
| match /b/{bucket}/o { | |
| match /{allPaths=\*\*} { | |
| allow read, write: if request.auth != null; | |
| }}} | |
| Consider writing rules as you structure your data, | |
| since the way you set up your rules impacts how you restrict access to data at different paths. | |
| ## owner of document access | |
| Allow write & delete by the owner of the document only. Where docId for every document in the users collection matches UID from Firebase authentication | |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| match /users/{docId} { | |
| allow write, delete: if request.auth.uid == docId; | |
| }}} | |
| ## Subcollections | |
| Subcollections are isolated by default from the rules of their parent documents. We can acess them using nested rules | |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| match /users/{docId} { | |
| allow read: if request.auth!= null; | |
| allow update: if request.auth == docId; | |
| match /notes/{docId}{ | |
| allow read: if request.auth.uid == request.resource.data.userId | |
| allow write, delete: if request.auth.uid == request.resource.data.userId | |
| } | |
| } | |
| } | |
| } | |
| ## Example conditions: | |
| // Limit documents per request to 50 | |
| allow list: if request.query.limit <= 50 | |
| // Make sure that 'myServerTimestampField' was set using a | |
| // server-side timestamp. | |
| request.time == request.resource.data.myServerTimestampField | |
| ## Example: Immutable usernames | |
| Allow write: if request.resource.data.username == resource.data.username; | |
| only allow writes if they username already in document matches the one sent with the payload. I.e. not allowed to change the username | |
| export const onCreateUser = functions.auth.user().onCreate(async (user: UserRecord, ctx: EventContext) => { | |
| //.. created user email, displayname, uid, photoURL etc are available in user object | |
| // and we can use it to create a document in another collection or send them welcome email | |
| const userRef = db.collection('users').doc(user.uid); | |
| return userRef.set({ | |
| name: user.displayName, | |
| createdAt: ctx.timestamp, | |
| email: user.email, | |
| photoURL: user.photoURL, | |
| uid: user.uid | |
| }) | |
| }); | |
| ## Example: Functions and local variables in rules | |
| match /users/{userId} { | |
| allow read: if isLoggedIn(); | |
| allow write: if belongsTo(userId); | |
| } | |
| match /todos/{docId} { | |
| allow read: if resource.data.status == 'published'; | |
| allow create: if canCreateTodo(); | |
| allow update: if belongsTo() | |
| && request.resource.data.keys().hasOnly(['text', 'status']); | |
| } | |
| function isLoggedIn() { | |
| return request.auth.uid != null; | |
| } | |
| function belongsTo(userId) { | |
| return request.auth.uid == userId || request.auth.uid == resource.data.uid; | |
| } | |
| function canCreateTodo() { | |
| let uid = request.auth.uid; | |
| let hasValidTimestamp = request.time == request.resource.data.createdAt; | |
| return belongsTo(uid) && hasValidTimestamp; | |
| } | |
| ## Check docs in another path/collection | |
| get(/databases/$(database)/documents/users/$(request.auth.uid)) | |
| exists(/databases/$(database)/documents/users/$(SOME_DOC_ID)) | |
| rules_version = '2'; | |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| match /{document=**} { | |
| allow read, write: if false; | |
| } | |
| match /messages/{docId} { | |
| allow read: if request.auth.uid != null; | |
| allow create: if canCreateMessage(); | |
| } | |
| function canCreateMessage() { | |
| let isSignedIn = request.auth.uid != null; | |
| let isOwner = request.auth.uid == request.resource.data.uid; | |
| let isNotTooLong = request.resource.data.text.size() < 255; | |
| let isNow = request.time == request.resource.data.createdAt; | |
| let isNotBanned = exists( | |
| /databases/$(database)/documents/banned/$(request.auth.uid) | |
| ) == false; | |
| return isSignedIn && isOwner && isNotTooLong && isNow && isNotBanned; | |
| } | |
| } | |
| } | |
| return isSignedIn && isOwner && isNotTooLong && isNow && isNotBanned; | |
| ## Role based Auth Rules | |
| rules_version = '2'; | |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| match /users/{userId} { | |
| allow read: if isSignedIn(); | |
| allow update, delete: if hasAnyRole(['admin']); | |
| } | |
| match /posts/{postId} { | |
| allow read: if ( isSignedIn() && resource.data.published ) || hasAnyRole(['admin']); | |
| allow create: if isValidNewPost() && hasAnyRole(['author']); | |
| allow update: if isValidUpdatedPost() && hasAnyRole(['author', 'editor', 'admin']); | |
| allow delete: if hasAnyRole(['admin']); | |
| } | |
| function isSignedIn() { | |
| return request.auth != null; | |
| } | |
| function hasAnyRole(roles) { | |
| return isSignedIn() | |
| && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny(roles) | |
| } | |
| function isValidNewPost() { | |
| let post = request.resource.data; | |
| let isOwner = post.uid == request.auth.uid; | |
| let isNow = request.time == request.resource.data.createdAt; | |
| let hasRequiredFields = post.keys().hasAll(['content', 'uid', 'createdAt', 'published']); | |
| return isOwner && hasRequiredFields && isNow; | |
| } | |
| function isValidUpdatedPost() { | |
| let post = request.resource.data; | |
| let hasRequiredFields = post.keys().hasAny(['content', 'updatedAt', 'published']); | |
| let isValidContent = post.content is string && post.content.size() < 5000; | |
| return hasRequiredFields && isValidContent; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment