-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
132 lines (116 loc) · 4.83 KB
/
Copy pathfirestore.rules
File metadata and controls
132 lines (116 loc) · 4.83 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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ── Helper functions ──────────────────────────────────────
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return isAuthenticated() && request.auth.uid == userId;
}
function isValidString(field, maxLen) {
return field is string && field.size() > 0 && field.size() <= maxLen;
}
// SEC-VAL-01: Streak anti-cheat — server rejects future start dates
// and streak days exceeding elapsed time since start.
function isValidStreakData() {
let data = request.resource.data;
return data.currentStreakStart is timestamp
&& data.currentStreakStart <= request.time
&& data.currentStreakDays is int
&& data.currentStreakDays >= 0;
}
// ── User document ─────────────────────────────────────────
// SEC-FSR-01: Owner-only read/write
match /users/{userId} {
allow read: if isOwner(userId);
allow create: if isOwner(userId)
&& isValidString(request.resource.data.displayName, 30)
&& isValidString(request.resource.data.email, 254)
&& request.resource.data.uid == userId;
allow update: if isOwner(userId)
&& request.resource.data.uid == userId
&& request.resource.data.email == resource.data.email
&& isValidStreakData();
allow delete: if isOwner(userId);
// SEC-FSR-02: Subcollections inherit owner-only auth
match /streaks/{streakId} {
allow read: if isOwner(userId);
allow create: if isOwner(userId);
// Archived streaks are immutable
allow update: if isOwner(userId)
&& !('endDate' in resource.data);
allow delete: if false;
}
// SEC-VAL-02: Journal content max 10,000 chars (base64 encrypted)
match /journals/{journalId} {
allow read: if isOwner(userId);
allow create, update: if isOwner(userId)
&& request.resource.data.content is string
&& request.resource.data.content.size() <= 13400;
allow delete: if isOwner(userId);
}
// SEC-FSR-15: Check-in mood range 1-5, type must be valid
match /checkIns/{checkInId} {
allow read: if isOwner(userId);
allow create, update: if isOwner(userId)
&& request.resource.data.mood is int
&& request.resource.data.mood >= 1
&& request.resource.data.mood <= 5
&& request.resource.data.type in ['morning', 'evening'];
allow delete: if false;
}
// SEC-FSR-14: Habit name max 50 chars on create and update
match /habits/{habitId} {
allow read: if isOwner(userId);
allow create: if isOwner(userId)
&& isValidString(request.resource.data.name, 50);
allow update: if isOwner(userId)
&& isValidString(request.resource.data.name, 50);
allow delete: if false;
match /completions/{completionId} {
allow read, write: if isOwner(userId);
}
}
match /emergencyLogs/{logId} {
allow read: if isOwner(userId);
// SEC-FSR-07: Append-only — no update or delete
allow create: if isOwner(userId);
allow update, delete: if false;
}
// SEC-WALL: Partners — owner can manage; partner cross-reads
// limited to shared view fields only (enforced at repo + UI layer).
// Firestore rules deny any cross-user subcollection reads.
match /partners/{partnerId} {
allow read: if isOwner(userId);
allow create: if isOwner(userId)
&& request.resource.data.status in ['pending', 'active'];
allow update: if isOwner(userId)
&& request.resource.data.status in ['active', 'removed'];
allow delete: if isOwner(userId);
// NEW-005 / SEC-MSG: Partner messages — append-only, max 1000 chars.
match /messages/{messageId} {
allow read: if isOwner(userId);
allow create: if isOwner(userId)
&& isValidString(request.resource.data.text, 1000);
allow update, delete: if false;
}
}
}
// ── Global content ────────────────────────────────────────
// SEC-FSR-03: Readable by any authenticated user, admin-write only
match /content/{contentId} {
allow read: if isAuthenticated();
allow write: if false;
}
// SEC-FSR-04: Daily quotes
match /dailyQuotes/{quoteId} {
allow read: if isAuthenticated();
allow write: if false;
}
// SEC-FSR-05: Deny everything else
match /{document=**} {
allow read, write: if false;
}
}
}