-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
151 lines (132 loc) · 5.72 KB
/
Copy pathfirestore.rules
File metadata and controls
151 lines (132 loc) · 5.72 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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAuthorizedUser() {
return request.auth != null && request.auth.token.email.matches('.*@example[.]com');
// IMPORTANT: replace example.com with your deployed ALLOWED_AUTH_DOMAIN before deploying rules.
}
function userDepartment() {
let userPath = /databases/$(database)/documents/users/$(request.auth.uid);
return exists(userPath) &&
get(userPath).data.keys().hasAny(['department'])
? get(userPath).data.department
: null;
}
function hasLegacyAiConfigSecretMaterial(data) {
return data != null &&
('aiConfig' in data) &&
data.aiConfig is map &&
(
('apiKey' in data.aiConfig) ||
('apiKeyEnc' in data.aiConfig) ||
('credentialEnc' in data.aiConfig)
);
}
// Dashboards collection
match /dashboards/{dashboardId} {
allow read: if isAuthorizedUser() && (
(('createdBy' in resource.data) && request.auth.uid == resource.data.createdBy) ||
(('visibility' in resource.data) && resource.data.visibility == "team") ||
(('allowedEmails' in resource.data) && resource.data.allowedEmails is list && resource.data.allowedEmails.hasAny([request.auth.token.email])) ||
(
('allowedDepartments' in resource.data) &&
resource.data.allowedDepartments is list &&
userDepartment() != null &&
resource.data.allowedDepartments.hasAny([userDepartment()])
)
);
// Dashboard creation is server-only (Admin SDK via /api/upload)
allow create: if false;
// Only the dashboard owner can update (ownership fields protected)
allow update: if isAuthorizedUser() && request.auth.uid == resource.data.createdBy
&& !request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['createdBy', 'createdByEmail', 'createdByName']);
// Only the dashboard owner can delete
allow delete: if isAuthorizedUser() && request.auth.uid == resource.data.createdBy;
// Version history — server-only (Admin SDK bypasses rules)
// API routes handle owner-only access checks
match /versions/{versionId} {
allow read: if false;
allow write: if false;
}
// View analytics events — server-only (Admin SDK bypasses rules)
match /views/{viewId} {
allow read: if false;
allow write: if false;
}
// Embed tokens — server-only (Admin SDK bypasses rules)
match /embedTokens/{tokenId} {
allow read: if false;
allow write: if false;
}
}
// Users collection — all access requires authorized email domain
match /users/{userId} {
allow read: if isAuthorizedUser() && request.auth.uid == userId
&& (resource == null || !hasLegacyAiConfigSecretMaterial(resource.data));
// Authorized users can create their own doc — role field BLOCKED (set server-side via Admin SDK)
allow create: if isAuthorizedUser() && request.auth.uid == userId
&& !request.resource.data.keys().hasAny(['role', 'department', 'aiConfig']);
// Updates cannot change role (admin-only via Admin SDK)
allow update: if isAuthorizedUser() && request.auth.uid == userId
&& !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role', 'department', 'aiConfig']);
// Favorites subcollection — Authorized user can read/write their own
match /favorites/{dashboardId} {
allow read: if isAuthorizedUser() && request.auth.uid == userId;
allow write: if isAuthorizedUser() && request.auth.uid == userId;
}
// Recently viewed subcollection — Authorized user can read/write their own
match /recent/{dashboardId} {
allow read: if isAuthorizedUser() && request.auth.uid == userId;
allow write: if isAuthorizedUser() && request.auth.uid == userId;
}
// Viewed timestamps for "Updated" badge (Sprint 2) — Authorized user reads own, server writes
match /viewed/{dashboardId} {
allow read: if isAuthorizedUser() && request.auth.uid == userId;
}
// Personal folders — Authorized user can read/write their own
match /folders/{folderId} {
allow read: if isAuthorizedUser() && request.auth.uid == userId;
allow write: if isAuthorizedUser() && request.auth.uid == userId;
}
}
// Shared folders — server-only (Admin SDK bypasses rules)
match /shared-folders/{folderId} {
allow read: if false;
allow write: if false;
}
// AI provider secrets — server-only (Admin SDK bypasses rules)
match /ai_config_secrets/{userId} {
allow read, write: if false;
}
// Slugs collection — server-only (Admin SDK bypasses rules)
match /slugs/{slug} {
allow read: if false;
allow write: if false;
}
// Pending roles — Authorized users can read (for first login role assignment)
match /pendingRoles/{docId} {
allow read: if isAuthorizedUser();
allow write: if false;
}
// Settings — Authorized users can read, server writes only
match /settings/{docId} {
allow read: if isAuthorizedUser();
allow write: if false;
}
// App prompts — server-only (Admin SDK bypasses rules)
// All access goes through /api/admin/prompts with verifySuperAdmin
match /app_prompts/{promptKey} {
allow read: if false;
allow write: if false;
match /versions/{versionId} {
allow read: if false;
allow write: if false;
}
}
// Fontes externas são acessíveis apenas no servidor. O Admin SDK ignora as regras.
match /data_sources/{id} {
allow read, write: if false;
}
}
}