rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /ff_user_push_notifications/{document} { allow create: if /databases/$(database)/documents/users/$(request.auth.uid) == request.resource.data.sender; allow read: if false; allow write: if false; allow delete: if false; } // For GPT & ChatGPT only. // You can delete this if you don't use GPT & ChatGPT. // TODO : write better security rules for GPT & ChatGPT. `allow write: if myDoc()` will be enough. match /gpt/{document} { allow read: if true; allow write: if true; } match /chat_rooms/{document} { allow read: if isMyChatRoom() || isOpenChat() || isModerator(); allow create: if true; allow update: if // moderator can update anything isModerator() || // If is a single chat and it is my chat room. Then I can update it. ( (isMyChatRoom() || wilBeMyChatRoom()) && request.resource.data.isGroupChat == false ) || // chat member update last message fields. ( isMyChatRoom() && onlyUpdating(["noOfMessages", "unsubscribedUserDocumentReferences", "subChatRoomCount", "isSubChatRoom", "userDocumentReferences", "lastMessage", "lastMessageUploadUrl", "lastMessageSentAt", "lastMessageSeenBy", "lastMessageSentBy"]) ) // chat member can remove himself or add other users. || ( isMyChatRoom() && onlyUpdating(["userDocumentReferences"]) && request.resource.data.userDocumentReferences.hasAll(resource.data.userDocumentReferences.removeAll([myReference()])) ) || // 내 방이 아니더라도, (추천 링크에 의해) 오픈 챗이면, 참여를 할 수 있다. ( isOpenChat() && onlyUpdating(["userDocumentReferences"]) && request.resource.data.userDocumentReferences.hasAll(resource.data.userDocumentReferences.concat([myReference()])) ) ; allow delete: if isGroupChat() == false && isMyChatRoom(); } // Note that, admin can read all chat room messages for the safety reason. If you don't like it, remove the isAdmin() from the read rule. match /chat_room_messages/{document} { allow read: if get(resource.data.chatRoomDocumentReference).data.userDocumentReferences.hasAny([myReference()]) || isAdmin(); allow create: if get(request.resource.data.chatRoomDocumentReference).data.userDocumentReferences.hasAny([myReference()]) && willBeMyDoc(); allow update, delete: if isMyDoc(); } // Note that, this allow users to add chat friends (chat room references) into chat_friends collection. match /chat_friends/{document} { allow read, write: if isMyDoc() || willBeMyDoc(); } match /users/{document} { allow create: if request.auth.uid == document; allow read: if request.auth.uid == document || isAdmin(); allow update: if request.auth.uid == document ; allow delete: if false; match /fcm_tokens/{fcmTokenDocId} { allow get, list, delete: if request.auth.uid == document; allow create, update, delete: if request.auth.uid == document && required(['fcm_token', 'device_type', 'created_at']); } } match /{path=**}/fcm_tokens/{docId} { allow read: if true; } match /users_public_data/{document} { allow create: if request.auth.uid == document; allow read: if true; allow update: if isMyDoc() || onlyUpdating(['likes']); allow delete: if false; } match /user_settings/{document} { allow read: if true; allow create: if willBeMyDoc(); allow update: if isMyDoc(); allow delete: if isMyDoc(); } match /settings/{document} { allow read: if true; allow write: if isMyDoc() || willBeMyDoc(); } match /system_settings/{document} { allow read: if true; allow write: if isAdmin(); } // reports match /reports/{document} { allow read: if isMyDoc() || isAdmin(); allow write: if isMyDoc() || willBeMyDoc() || isAdmin(); } // category match /categories/{category} { allow read: if true; allow create, delete: if isAdmin(); allow update: if isAdmin() || onlyUpdating(['noOfPosts', 'noOfComments']) } // posts match /posts/{postId} { allow read: if true; allow create: if required(['category']) && categoryExists() && willBeMyDoc() ; // Post update rule; // - Success if nothing changes. if request.resource.data is same as resource.data // - Success if 'likes', 'noOfLikes', 'noOfComments' is updated only. allow update: if isMyDoc() || isAdmin() || onlyUpdating(['likes', 'noOfLikes', 'noOfComments']) ; // See the readme for post and comment deletion. allow delete: if isMyDoc() || isAdmin(); } // comments match /comments/{commentId} { allow read: if true; allow create: if willBeMyDoc(); allow update: if isMyDoc() || isAdmin() || onlyUpdating(['likes', 'noOfLikes']) ; // See the readme for post and comment deletion. allow delete: if isMyDoc() || isAdmin(); } // bookmarks match /bookmarks/{document} { allow create: if true; allow read: if isMyDoc(); allow update: if false; allow delete: if isMyDoc(); } match /storage_files/{document} { allow read, write: if true; } // check if the user is admin. function isAdmin() { return get(/databases/$(database)/documents/system_settings/admins).data[request.auth.uid] == true; } function isMyChatRoom() { return resource.data.userDocumentReferences.hasAny([myReference()]); } function wilBeMyChatRoom() { return request.resource.data.userDocumentReferences.hasAny([myReference()]); } function isModerator() { return 'moderatorUserDocumentReferences' in resource.data && myReference() in resource.data.moderatorUserDocumentReferences ; } // check if the document has the login user's uid or reference. function isMyDoc() { return resource.data.userDocumentReference == myReference() || request.auth.uid == resource.data.uid; } function willBeMyDoc() { return request.resource.data.userDocumentReference == myReference(); } function categoryExists() { return exists(/databases/$(database)/documents/categories/$(request.resource.data.category)); } function myReference() { return /databases/$(database)/documents/users/$(request.auth.uid); } } } // * Warning : It's check the fields after save. // * Warning : !!! It's not checking the incoming data fields !!! function required(fields) { return request.resource.data.keys().hasAll( fields ); } function onlyUpdating(fields) { return request.resource.data.diff(resource.data).affectedKeys().hasOnly(fields); } function isOpenChat() { return 'isOpenChat' in resource.data && resource.data.isOpenChat == true; } function isGroupChat() { return 'isGroupChat' in resource.data && resource.data.isGroupChat == true; }