-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
197 lines (171 loc) · 10.2 KB
/
Copy pathfirestore.rules
File metadata and controls
197 lines (171 loc) · 10.2 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ─── HELPERS ────────────────────────────────────────────────────────────
function isAuthenticated() {
return request.auth != null;
}
function userData() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
}
function getUserRole() { return userData().role; }
function getUserCollegeId(){ return userData().collegeId; }
function isSuperAdmin() { return isAuthenticated() && getUserRole() == 'super-admin'; }
function isAdmin() { return isAuthenticated() && getUserRole() == 'admin'; }
function isFaculty() { return isAuthenticated() && getUserRole() == 'faculty'; }
function isStudent() { return isAuthenticated() && getUserRole() == 'student'; }
function isStaff() { return isAdmin() || isFaculty() || isSuperAdmin(); }
function isOwner(userId) { return request.auth.uid == userId; }
function sameCollege(collegeId) { return getUserCollegeId() == collegeId; }
// ─── USERS ──────────────────────────────────────────────────────────────
match /users/{userId} {
// Individual doc read: own profile, admin (same college), faculty, super-admin
allow get: if isOwner(userId)
|| (isAdmin() && sameCollege(resource.data.collegeId))
|| isFaculty()
|| isSuperAdmin();
// List (collection query): admin, faculty, super-admin can list all;
// students can list ONLY to search same-college users for chat
allow list: if isStaff()
|| (isStudent() && request.query.filters.size() > 0);
// Any authenticated user can create their own profile doc (registration)
allow create: if isAuthenticated() && isOwner(userId);
// Users update own profile (no role/college change); admins update college users
allow update: if (isOwner(userId)
&& !request.resource.data.diff(resource.data)
.affectedKeys().hasAny(['role','collegeId','uid','email']))
|| (isAdmin() && sameCollege(resource.data.collegeId))
|| isSuperAdmin();
allow delete: if isSuperAdmin();
// Enrolled Languages — student owns; admin/faculty can read for analytics
match /enrolledLanguages/{languageId} {
allow read: if isOwner(userId) || isStaff();
allow create, update: if isOwner(userId) || isSuperAdmin();
allow delete: if isOwner(userId) || isSuperAdmin();
}
// Saved Programs — student owns their programs; any authenticated user can
// READ a single doc (needed for shared program links and collaborative editing)
match /savedPrograms/{programId} {
allow read: if isAuthenticated();
allow create, update: if isOwner(userId);
allow delete: if isOwner(userId) || isSuperAdmin();
}
}
// ─── COLLEGES ───────────────────────────────────────────────────────────
match /colleges/{collegeId} {
// Any authenticated user reads colleges (registration page dropdown, etc.)
allow read: if isAuthenticated();
allow create: if isSuperAdmin();
// Admin updates their own college; super-admin updates any
allow update: if (isAdmin() && sameCollege(collegeId)) || isSuperAdmin();
allow delete: if isSuperAdmin();
// Resources — admin writes; any authenticated same-college user reads
match /resources/{resourceId} {
allow read: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
allow create, update, delete: if isAdmin() && sameCollege(collegeId) || isSuperAdmin();
}
// Feedback — students create; admins read/update via query & onSnapshot
match /feedback/{feedbackId} {
allow read: if isAdmin() && sameCollege(collegeId) || isSuperAdmin();
allow create: if isStudent() && sameCollege(collegeId);
allow update: if isAdmin() && sameCollege(collegeId)
&& request.resource.data.diff(resource.data)
.affectedKeys().hasOnly(['isRead'])
|| isSuperAdmin();
allow delete: if isSuperAdmin();
}
// Languages — all authenticated same-college users read; admin writes
match /languages/{languageId} {
allow read: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
allow create, update, delete:
if isAdmin() && sameCollege(collegeId) || isSuperAdmin();
// Questions — all authenticated same-college users read (students practice,
// faculty create tests); only admin writes
match /questions/{questionId} {
allow read: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
allow create, update, delete:
if isAdmin() && sameCollege(collegeId) || isSuperAdmin();
}
// Courses — faculty creates; students can update (submit enrollment request);
// faculty approves/rejects enrollments
match /courses/{courseId} {
allow read: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
allow create, delete:
if (isAdmin() || isFaculty()) && sameCollege(collegeId) || isSuperAdmin();
// Students need update to submit enrollment request; faculty to manage
allow update: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
}
// Tests — faculty creates/publishes; students update to register for a test;
// admin can also create tests
match /tests/{testId} {
allow read: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
allow create, delete:
if (isAdmin() || isFaculty()) && sameCollege(collegeId) || isSuperAdmin();
// Students update to register; faculty to update status/details
allow update: if isAuthenticated() && sameCollege(collegeId) || isSuperAdmin();
}
}
}
// ─── COLLEGE REGISTRATION REQUESTS ──────────────────────────────────────
// IMPORTANT: Code uses camelCase 'collegeRegistrationRequests' — rule MUST match
match /collegeRegistrationRequests/{requestId} {
allow read: if isSuperAdmin();
allow create: if true; // Public — anyone can submit a college registration request
allow update: if isSuperAdmin();
allow delete: if isSuperAdmin();
}
// ─── FEATURE REQUESTS ───────────────────────────────────────────────────
// IMPORTANT: Code uses camelCase 'featureRequests' — rule MUST match
match /featureRequests/{requestId} {
allow read: if isOwner(resource.data.userId) || isSuperAdmin();
allow create: if isAuthenticated() && isOwner(request.resource.data.userId);
allow update: if isSuperAdmin();
allow delete: if isSuperAdmin();
}
// ─── NOTIFICATIONS (queued for Cloud Functions to process) ───────────────
match /notifications/{notificationId} {
allow read: if isSuperAdmin();
allow create: if isAdmin() || isSuperAdmin();
allow update, delete: if isSuperAdmin();
}
// ─── CHATS ──────────────────────────────────────────────────────────────
match /chats/{chatId} {
// General College Chat uses collegeId as its document ID.
// Any authenticated user belonging to that college can read/update it.
// Regular DM/group chats require the user to be in the participants array.
allow read: if (isAuthenticated() && getUserCollegeId() == chatId)
|| (isAuthenticated() && request.auth.uid in resource.data.participants)
|| isSuperAdmin();
allow create: if isAuthenticated()
&& (request.auth.uid in request.resource.data.participants
|| getUserCollegeId() == chatId);
// Allow update for participants OR for same-college users on the general chat
allow update: if (isAuthenticated() && request.auth.uid in resource.data.participants)
|| (isAuthenticated() && getUserCollegeId() == chatId)
|| isSuperAdmin();
allow delete: if isAuthenticated() && request.auth.uid == resource.data.ownerId
|| isSuperAdmin();
// Messages subcollection
match /messages/{messageId} {
// Allow read for participants OR for same-college users (General Chat)
allow read: if (isAuthenticated()
&& request.auth.uid in
get(/databases/$(database)/documents/chats/$(chatId)).data.participants)
|| (isAuthenticated() && getUserCollegeId() == chatId)
|| isSuperAdmin();
// Allow create for participants OR for same-college users (General Chat)
allow create: if isAuthenticated()
&& request.resource.data.senderId == request.auth.uid
&& request.resource.data.chatId == chatId
&& (request.auth.uid in
get(/databases/$(database)/documents/chats/$(chatId)).data.participants
|| getUserCollegeId() == chatId);
allow update: if isAuthenticated()
&& resource.data.senderId == request.auth.uid
&& request.resource.data.senderId == request.auth.uid;
allow delete: if isAuthenticated() && resource.data.senderId == request.auth.uid
|| isSuperAdmin();
}
}
}
}