-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
138 lines (132 loc) · 6.72 KB
/
firestore.rules
File metadata and controls
138 lines (132 loc) · 6.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
/**
* @fileoverview Firestore Security Rules for PortfolioForge.
*
* Core Philosophy:
* This ruleset enforces a strict user-ownership model for user profiles and their associated portfolio items.
* Themes are publicly readable, but write access is restricted.
*
* Data Structure:
* - /users/{userId}: Stores user profile data.
* - /users/{userId}/portfolioItems/{itemId}: Stores portfolio items for each user.
* - /users/{userId}/messages/{messageId}: Stores contact form messages for each user.
* - /themes/{themeId}: Stores publicly available themes.
*
* Key Security Decisions:
* - Users can only access their own profile data and portfolio items.
* - Admins can list all users for the admin dashboard.
* - Themes are publicly readable.
* - General user listing is disallowed.
*
* Denormalization for Authorization:
* - UserProfile documents use their document ID as the user ID.
* - PortfolioItems store the userProfileId.
*/
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
/**
* @description Manages user profiles, ensuring only the user can access their data.
* @path /users/{userId}
* @allow (create) User with UID 'user123' creates their profile at /users/user123.
* @deny (create) User with UID 'user456' attempts to create a profile at /users/user123.
* @allow (get) User with UID 'user123' reads their profile at /users/user123.
* @deny (get) User with UID 'user456' attempts to read profile at /users/user123.
* @allow (update) User with UID 'user123' updates their profile at /users/user123.
* @deny (update) User with UID 'user456' attempts to update profile at /users/user123.
* @allow (delete) User with UID 'user123' deletes their profile at /users/user123.
* @deny (delete) User with UID 'user456' attempts to delete profile at /users/user123.
* @principle Enforces document ownership for all operations.
*/
match /users/{userId} {
function isOwner(userId) {
return request.auth != null && request.auth.uid == userId;
}
function isExistingOwner(userId) {
return isOwner(userId) && resource != null;
}
function isPro(userId) {
return get(/databases/$(database)/documents/users/$(userId)).data.subscriptionTier in ['pro', 'studio'];
}
function isPremiumTheme(themeId) {
return themeId == 'custom'
|| (themeId is string && get(/databases/$(database)/documents/themes/$(themeId)).data.isPremium == true);
}
function premiumFieldsUnchanged() {
return request.resource.data.customDomain == resource.data.customDomain
&& request.resource.data.customDomainStatus == resource.data.customDomainStatus
&& request.resource.data.customTheme == resource.data.customTheme;
}
allow get: if isOwner(userId);
// Allow an admin user to list all users for the admin dashboard.
allow list: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
allow create: if isOwner(userId)
&& request.resource.data.id == userId
&& (!isPremiumTheme(request.resource.data.themeId))
&& request.resource.data.customDomain == null
&& request.resource.data.customTheme == null;
allow update: if isExistingOwner(userId)
&& request.resource.data.id == resource.data.id
&& (isPro(userId) || premiumFieldsUnchanged())
&& (isPro(userId) || !isPremiumTheme(request.resource.data.themeId));
allow delete: if isExistingOwner(userId);
}
/**
* @description Manages portfolio items for a user, ensuring only the user can access their items.
* @path /users/{userId}/portfolioItems/{itemId}
* @allow (create) User with UID 'user123' creates an item in /users/user123/portfolioItems/item1.
* @deny (create) User with UID 'user456' attempts to create an item in /users/user123/portfolioItems/item1.
* @allow (get) User with UID 'user123' reads an item in /users/user123/portfolioItems/item1.
* @deny (get) User with UID 'user456' attempts to read item in /users/user123/portfolioItems/item1.
* @allow (update) User with UID 'user123' updates an item in /users/user123/portfolioItems/item1.
* @deny (update) User with UID 'user456' attempts to update item in /users/user123/portfolioItems/item1.
* @allow (delete) User with UID 'user123' deletes an item in /users/user123/portfolioItems/item1.
* @deny (delete) User with UID 'user456' attempts to delete item in /users/user123/portfolioItems/item1.
* @principle Enforces document ownership for all operations.
*/
match /users/{userId}/portfolioItems/{itemId} {
function isOwner(userId) {
return request.auth != null && request.auth.uid == userId;
}
function isExistingOwner(userId) {
return isOwner(userId) && resource != null;
}
function isPro(userId) {
return get(/databases/$(database)/documents/users/$(userId)).data.subscriptionTier in ['pro', 'studio'];
}
allow get: if isOwner(userId);
allow list: if isOwner(userId);
allow create: if false; // All creates must go through the server-side API (/api/portfolio-items)
allow update: if isExistingOwner(userId) && request.resource.data.userProfileId == resource.data.userProfileId;
allow delete: if isExistingOwner(userId);
}
/**
* @description Manages messages sent via the contact form.
* @path /users/{userId}/messages/{messageId}
* @allow (read, list) User with UID 'user123' can read their own messages.
* @deny (read, list) User 'user456' cannot read messages for 'user123'.
* @deny (create, update, delete) All client-side writes are forbidden. Writes should only happen via a trusted backend (e.g., Firebase Function).
* @principle Enforces ownership for reads and blocks all client writes.
*/
match /users/{userId}/messages/{messageId} {
function isOwner(userId) {
return request.auth != null && request.auth.uid == userId;
}
allow read, list: if isOwner(userId);
allow write: if false; // All client-side writes are disallowed.
}
/**
* @description Manages themes, allowing public read access but restricting write access.
* @path /themes/{themeId}
* @allow (get) Any user can read a theme at /themes/theme1.
* @allow (list) Any user can list themes.
* @deny (create) Any user attempts to create a theme.
* @deny (update) Any user attempts to update a theme.
* @deny (delete) Any user attempts to delete a theme.
* @principle Allows public read access and restricts write access.
*/
match /themes/{themeId} {
allow get, list: if true;
allow create, update, delete: if false;
}
}
}