-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
148 lines (133 loc) · 7.92 KB
/
Copy pathfirestore.rules
File metadata and controls
148 lines (133 loc) · 7.92 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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ── postbox collection ──────────────────────────────────────────────────
// Authenticated users may read postbox data. Only server-side (Cloud
// Functions admin SDK) may write postbox documents.
match /postbox/{postboxId} {
allow read: if request.auth != null;
allow write: if false;
}
// ── claims collection ───────────────────────────────────────────────────
// Claims are written exclusively by Cloud Functions (admin SDK).
// Authenticated users may read their own claims only.
match /claims/{claimId} {
allow read: if request.auth != null && resource.data.userid == request.auth.uid;
allow write: if false;
}
// ── users collection ────────────────────────────────────────────────────
// Profile fields (displayName, streak, lastClaimDate, createdAt) are
// written exclusively by Cloud Functions (Admin SDK) — this prevents
// clients from bypassing the profanity filter by writing displayName
// directly. The friends array is the only field clients may modify.
match /users/{uid} {
allow read: if request.auth != null;
// The friends array, notificationPrefs map, and mapColor string are the
// only fields clients may modify on their own document. All other fields
// (displayName, streak, lastClaimDate, createdAt, points) are written by
// Cloud Functions using the Admin SDK, which bypasses these rules. FCM
// tokens live in the separate fcmTokens/{uid} collection to avoid
// exposure here.
// friends is capped at 200 entries to prevent O(n) leaderboard read abuse.
// notificationPrefs must be a map (type-validated to prevent garbage writes).
// mapColor must be a short string (palette key) when present; deletion
// (FieldValue.delete()) is also allowed since the diff only fires on
// present fields.
allow update: if request.auth != null && request.auth.uid == uid
&& request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['friends', 'notificationPrefs', 'mapColor'])
&& (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['friends'])
|| (request.resource.data.friends is list
&& request.resource.data.friends.size() <= 200))
&& (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['notificationPrefs'])
|| request.resource.data.notificationPrefs is map)
&& (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['mapColor'])
|| !('mapColor' in request.resource.data)
|| (request.resource.data.mapColor is string
&& request.resource.data.mapColor.size() <= 32));
// Create and all other writes are handled by Cloud Functions.
allow create: if false;
}
// Per-county lifetime stats kept under users/{uid}/countyStats/{slug}.
// Readable by any signed-in user (matches the parent users/{uid} rule
// used by friend-profile views); written only by Cloud Functions.
match /users/{uid}/countyStats/{countySlug} {
allow read: if request.auth != null;
allow write: if false;
}
// ── leaderboards collection ─────────────────────────────────────────────
// Leaderboard documents are written by Cloud Functions only.
// Authenticated users may read them.
match /leaderboards/{period} {
allow read: if request.auth != null;
allow write: if false;
}
// Per-county lifetime leaderboards live under
// leaderboards/lifetime_by_county/counties/{countySlug}. Same access
// pattern as the period docs above.
match /leaderboards/lifetime_by_county/counties/{countySlug} {
allow read: if request.auth != null;
allow write: if false;
}
// ── meta collection ──────────────────────────────────────────────────────
// Read-only aggregate stats (e.g. meta/stats.totalPostboxes) used to
// compute lifetime leaderboard percentages. Written by import scripts only.
match /meta/{docId} {
allow read: if request.auth != null;
allow write: if false;
}
// ── reports collection ───────────────────────────────────────────────────
// User-submitted reports about postbox data problems. Created and reviewed
// exclusively by Cloud Functions (submitReport / reviewReport, Admin SDK).
// A reporter may read their own reports; users with the `admin` custom
// claim may read all of them (the in-app admin review interface).
match /reports/{reportId} {
allow read: if request.auth != null
&& (resource.data.reporterUid == request.auth.uid
|| request.auth.token.admin == true);
allow write: if false;
}
// ── reportQuotas collection ──────────────────────────────────────────────
// Per-user daily counter enforcing the submitReport rate limit. Written
// (and only ever read) by the submitReport Cloud Function (Admin SDK);
// clients have no business reading or writing it.
match /reportQuotas/{uid} {
allow read, write: if false;
}
// ── fcmTokens collection ─────────────────────────────────────────────────
// Stores FCM device tokens separate from the world-readable users collection
// so that tokens are not exposed to other authenticated users. Written
// exclusively by the registerFcmToken Cloud Function (Admin SDK).
// Users may only read their own token document.
match /fcmTokens/{uid} {
allow read: if request.auth != null && request.auth.uid == uid;
allow write: if false;
}
// ── moderationFlags collection ───────────────────────────────────────────
// Shadow-mode abuse-detection flags, written exclusively by the
// onClaimCreated Cloud Function (Admin SDK) and updated by reviewFlag.
// Readable only by users with the `admin` custom claim (the in-app abuse
// review interface). No client writes.
match /moderationFlags/{flagId} {
allow read: if request.auth != null && request.auth.token.admin == true;
allow write: if false;
}
// ── trustScores collection ───────────────────────────────────────────────
// Per-user server-only trust score decayed by the abuse detector. Never
// client-readable (kept out of the world-readable users/{uid} doc so it
// can't leak) and never client-writable — Cloud Functions (Admin SDK) only.
match /trustScores/{uid} {
allow read, write: if false;
}
// ── deletions collection ─────────────────────────────────────────────────
// Per-London-day account-deletion audit counter, written by the
// onUserDeleted Cloud Function (Admin SDK). Server-only; clients have no
// business reading or writing it.
match /deletions/{docId} {
allow read, write: if false;
}
}
}