-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
109 lines (98 loc) · 5.52 KB
/
Copy pathfirestore.rules
File metadata and controls
109 lines (98 loc) · 5.52 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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 1. Global Safety Net
match /{document=**} {
allow read, write: if false;
}
// Helper functions
function isSignedIn() {
return request.auth != null;
}
function isOwner(userId) {
return isSignedIn() && request.auth.uid == userId;
}
function isValidId(id) {
return id is string && id.size() <= 128 && id.matches('^[a-zA-Z0-9_\\-]+$');
}
function isValidUser(data) {
return data.uid is string && data.uid.size() <= 128 &&
data.email is string && data.email.size() <= 256 &&
(!('displayName' in data) || (data.displayName is string && data.displayName.size() <= 256)) &&
(!('photoURL' in data) || (data.photoURL is string && data.photoURL.size() <= 1024)) &&
(!('createdAt' in data) || (data.createdAt is string && data.createdAt.size() <= 128));
}
function isValidTask(data) {
return data.id is string && data.id.size() <= 128 &&
data.userId is string && data.userId.size() <= 128 &&
data.name is string && data.name.size() <= 256 &&
(!('description' in data) || (data.description is string && data.description.size() <= 2048)) &&
data.deadline is string && data.deadline.size() <= 64 &&
data.priority is string && (data.priority == 'low' || data.priority == 'medium' || data.priority == 'high') &&
data.completed is bool &&
(!('estimatedHours' in data) || (data.estimatedHours is number)) &&
(!('actualHours' in data) || (data.actualHours is number)) &&
(!('createdAt' in data) || (data.createdAt is string && data.createdAt.size() <= 128)) &&
(!('riskLevel' in data) || (data.riskLevel is string && data.riskLevel.size() <= 64)) &&
(!('riskReason' in data) || (data.riskReason is string && data.riskReason.size() <= 1024)) &&
(!('isRecurring' in data) || (data.isRecurring is bool)) &&
(!('recurrencePattern' in data) || (data.recurrencePattern is string && data.recurrencePattern.size() <= 64)) &&
(!('subtasks' in data) || (data.subtasks is list)) &&
(!('aiEstimateHours' in data) || (data.aiEstimateHours is number)) &&
(!('aiEstimateReasoning' in data) || (data.aiEstimateReasoning is string && data.aiEstimateReasoning.size() <= 2048)) &&
(!('completedAt' in data) || (data.completedAt is string && data.completedAt.size() <= 128));
}
function isValidFocusEvent(data) {
return data.id is string && data.id.size() <= 128 &&
data.userId is string && data.userId.size() <= 128 &&
data.taskId is string && data.taskId.size() <= 128 &&
data.taskName is string && data.taskName.size() <= 256 &&
(!('subtaskId' in data) || (data.subtaskId is string && data.subtaskId.size() <= 128)) &&
(!('subtaskName' in data) || (data.subtaskName is string && data.subtaskName.size() <= 256)) &&
data.eventType is string && (
data.eventType == 'start_session' ||
data.eventType == 'complete_step' ||
data.eventType == 'restart_step' ||
data.eventType == 'complete_task' ||
data.eventType == 'skip_step'
) &&
data.durationMinutes is number &&
data.timestamp is string && data.timestamp.size() <= 128;
}
// User profile rules
match /users/{userId} {
allow read: if isSignedIn() && request.auth.uid == userId;
allow create: if isSignedIn() && request.auth.uid == userId && isValidUser(request.resource.data);
allow update: if isSignedIn() && request.auth.uid == userId && isValidUser(request.resource.data) &&
request.resource.data.uid == resource.data.uid;
}
// Task rules: Owner access only
match /tasks/{taskId} {
allow read: if isSignedIn() && (resource == null || resource.data.userId == request.auth.uid);
allow create: if isSignedIn() && isValidId(taskId) &&
request.resource.data.userId == request.auth.uid &&
isValidTask(request.resource.data);
allow update: if isSignedIn() && isValidId(taskId) &&
(resource == null || resource.data.userId == request.auth.uid) &&
request.resource.data.userId == request.auth.uid &&
isValidTask(request.resource.data);
allow delete: if isSignedIn() && (resource == null || resource.data.userId == request.auth.uid);
}
// Focus session analytics rules: Owner access only
match /focus_events/{eventId} {
allow read: if isSignedIn() && (resource == null || resource.data.userId == request.auth.uid);
allow create: if isSignedIn() && isValidId(eventId) &&
request.resource.data.userId == request.auth.uid &&
isValidFocusEvent(request.resource.data);
allow update: if isSignedIn() && isValidId(eventId) &&
(resource == null || resource.data.userId == request.auth.uid) &&
request.resource.data.userId == request.auth.uid &&
isValidFocusEvent(request.resource.data);
allow delete: if isSignedIn() && (resource == null || resource.data.userId == request.auth.uid);
}
// Fallback for security validation (test connections, etc.)
match /test/connection {
allow read: if true;
}
}
}