-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
139 lines (118 loc) · 4.34 KB
/
Copy pathfirestore.rules
File metadata and controls
139 lines (118 loc) · 4.34 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
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;
}
// Strict string validation with length limits
function isValidString(field, maxLength) {
return field is string && field.size() > 0 && field.size() <= maxLength;
}
function isOptionalString(field, maxLength) {
return field == null || (field is string && field.size() <= maxLength);
}
// Strict timestamp validation (only actual timestamps, not strings)
function isValidTimestamp(field) {
return field is timestamp;
}
function isOptionalTimestamp(field) {
return field == null || field is timestamp;
}
// Boolean validation
function isValidBool(field) {
return field is bool;
}
// Number validation with optional range
function isValidNumber(field, min, max) {
return field is number && field >= min && field <= max;
}
function isOptionalNumber(field, min, max) {
return field == null || (field is number && field >= min && field <= max);
}
// Document size limit (1MB max, but we enforce 100KB for safety)
function isReasonableSize() {
return request.resource.size() < 100000;
}
// Rate limiting helper - checks if enough time has passed since last update
// Note: This requires lastUpdated field in documents
function hasRateLimitPassed(minIntervalMs) {
return !('lastUpdated' in resource.data) ||
request.time.toMillis() - resource.data.lastUpdated.toMillis() >= minIntervalMs;
}
// Validate todo item structure
function isValidTodo(todo) {
return todo.keys().hasAll(['id', 'text', 'completed']) &&
isValidString(todo.id, 50) &&
isValidString(todo.text, 500) &&
isValidBool(todo.completed);
}
// Validate attendance record
function isValidAttendanceRecord(record) {
return record.keys().hasAll(['date', 'status']) &&
isValidString(record.date, 10) &&
record.status in ['office', 'wfh', 'leave', 'holiday'];
}
// Validate note structure
function isValidNote(note) {
return note.keys().hasAll(['id', 'title', 'content']) &&
isValidString(note.id, 50) &&
isOptionalString(note.title, 200) &&
isOptionalString(note.content, 10000);
}
// Validate expense structure
function isValidExpense(expense) {
return expense.keys().hasAll(['id', 'amount', 'category']) &&
isValidString(expense.id, 50) &&
isValidNumber(expense.amount, 0, 100000000) &&
isValidString(expense.category, 50);
}
// Validate leave request
function isValidLeave(leave) {
return leave.keys().hasAll(['id', 'type', 'startDate', 'endDate', 'status']) &&
isValidString(leave.id, 50) &&
leave.type in ['casual', 'sick', 'earned', 'unpaid', 'compOff', 'other'] &&
isValidString(leave.startDate, 10) &&
isValidString(leave.endDate, 10) &&
leave.status in ['pending', 'approved', 'rejected'];
}
// Users collection - each user can only access their own data
match /users/{userId} {
// Allow read/write only to the owner
allow read: if isOwner(userId);
allow write: if isOwner(userId) && isReasonableSize();
// User data subcollection (todos, attendance, notes, etc.)
match /data/{document} {
allow read: if isOwner(userId);
// Validate writes based on document type with strict validation
allow write: if isOwner(userId) &&
isReasonableSize() &&
validateDocument(document);
}
}
// Document validation function - validates document type names
function validateDocument(docType) {
return docType in [
'todos',
'attendance',
'notes',
'officeConfig',
'gamification',
'leaves',
'trips',
'expenses',
'salaryStructure',
'quickLinks',
'meetings',
'pomodoroStats'
];
}
// Deny all other access by default
match /{document=**} {
allow read, write: if false;
}
}
}