-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
115 lines (98 loc) · 5.75 KB
/
firestore.rules
File metadata and controls
115 lines (98 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function isGroupMember(groupId) {
return isSignedIn() &&
exists(/databases/$(database)/documents/groups/$(groupId)/members/$(request.auth.uid));
}
function isGroupOwner(groupId) {
return isSignedIn() &&
get(/databases/$(database)/documents/groups/$(groupId)).data.createdBy == request.auth.uid;
}
// ── App config (version check) ──────────────────────────────────────────
// Public read so the update check works before authentication.
// Write is restricted to Admin SDK only (no client write allowed).
match /config/{document} {
allow read: if true;
allow write: if false;
}
// ── Users (FCM tokens) ──────────────────────────────────────────────────
match /users/{userId} {
allow read, write: if isSignedIn() && request.auth.uid == userId;
}
// ── Invite codes ────────────────────────────────────────────────────────
// Any authenticated user can read (needed to join by code).
// Any authenticated user can create/delete (owner creates on group
// creation or code regeneration; owner deletes on group deletion).
match /invite_codes/{code} {
allow read: if isSignedIn();
allow create, delete: if isSignedIn();
}
// ── Groups ──────────────────────────────────────────────────────────────
match /groups/{groupId} {
// Read: user must be a member
allow read: if isSignedIn() && request.auth.uid in resource.data.memberIds;
// Create: user creates a group where they are owner and first member
allow create: if isSignedIn()
&& request.auth.uid in request.resource.data.memberIds
&& request.resource.data.createdBy == request.auth.uid
&& request.resource.data.name is string
&& request.resource.data.name.size() >= 1
&& request.resource.data.name.size() <= 30
&& (request.resource.data.description == null
|| (request.resource.data.description is string
&& request.resource.data.description.size() <= 150));
// Update: existing member OR user adding themselves via invite code (joinGroup)
allow update: if (isGroupMember(groupId)
|| (isSignedIn()
&& request.auth.uid in request.resource.data.memberIds
&& !(request.auth.uid in resource.data.memberIds)))
&& (!('name' in request.resource.data.diff(resource.data).affectedKeys())
|| (request.resource.data.name is string
&& request.resource.data.name.size() >= 1
&& request.resource.data.name.size() <= 30))
&& (!('description' in request.resource.data.diff(resource.data).affectedKeys())
|| (request.resource.data.description == null
|| (request.resource.data.description is string
&& request.resource.data.description.size() <= 150)));
// Delete: owner only
allow delete: if isGroupOwner(groupId);
// ── Members ───────────────────────────────────────────────────────────
match /members/{memberId} {
allow read: if isGroupMember(groupId);
// create: user writes their own doc (covers createGroup & joinGroup batches,
// where the group doc may not exist yet for isGroupMember check)
allow create: if isSignedIn() && request.auth.uid == memberId;
allow update: if isGroupMember(groupId);
allow delete: if isSignedIn() && (
request.auth.uid == memberId // leave (self-remove)
|| isGroupOwner(groupId) // owner removes a member
);
}
// ── Messages ──────────────────────────────────────────────────────────
match /messages/{messageId} {
allow read, create, update, delete: if isGroupMember(groupId);
}
// ── Typing indicators ─────────────────────────────────────────────────
match /typing/{typingUserId} {
allow read: if isGroupMember(groupId);
allow write: if isSignedIn() && request.auth.uid == typingUserId;
}
// ── Expenses ──────────────────────────────────────────────────────────
match /expenses/{expenseId} {
allow read, write: if isGroupMember(groupId);
}
// ── Events (calendar) ─────────────────────────────────────────────────
match /events/{eventId} {
allow read, write: if isGroupMember(groupId);
}
// ── Notes ─────────────────────────────────────────────────────────────
match /notes/{noteId} {
allow read, write: if isGroupMember(groupId);
}
}
}
}