-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
136 lines (117 loc) · 4.72 KB
/
Copy pathfirestore.rules
File metadata and controls
136 lines (117 loc) · 4.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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 0. Global Safety Net
match /{document=**} {
allow read, write: if false;
}
// Phase 3: Primitives
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 incoming() {
return request.resource.data;
}
function existing() {
return resource.data;
}
function isVerified() {
return request.auth.token.email_verified == true;
}
function isAdmin() {
return isSignedIn() && (
request.auth.token.email == 'mearsbokaba3@gmail.com' && request.auth.token.email_verified == true
);
}
// Phase 4: Validation Blueprints
function isValidUser(data) {
return data.keys().hasAll(['userId', 'name', 'avatar', 'level', 'coins', 'inventory', 'achievements', 'completedTrades', 'hasSeenTutorial', 'createdAt', 'updatedAt']) &&
data.userId == request.auth.uid &&
data.name is string && data.name.size() <= 50 &&
data.avatar is string &&
data.level is int && data.level >= 1 &&
data.coins is int && data.coins >= 0 &&
data.inventory is list && data.inventory.size() <= 100 &&
data.achievements is list &&
data.completedTrades is int &&
data.hasSeenTutorial is bool;
}
function isValidTrade(data) {
return data.keys().hasAll(['senderId', 'senderName', 'receiverId', 'receiverName', 'offeredItems', 'offeredCoins', 'requestedItems', 'requestedCoins', 'status', 'createdAt']) &&
data.senderId == request.auth.uid &&
data.senderName is string &&
data.receiverId is string &&
data.receiverName is string &&
data.offeredItems is list &&
data.offeredCoins is int &&
data.requestedItems is list &&
data.requestedCoins is int &&
data.status == 'pending' &&
data.createdAt == request.time;
}
// 1. Users Collection
match /users/{userId} {
allow get: if isSignedIn();
allow list: if isSignedIn(); // For trading discovery, we allow listing (could be narrowed)
allow create: if isOwner(userId) &&
isVerified() &&
isValidUser(incoming()) &&
incoming().createdAt == request.time &&
incoming().updatedAt == request.time;
allow update: if isOwner(userId) &&
isVerified() &&
isValidUser(incoming()) &&
incoming().updatedAt == request.time &&
incoming().createdAt == existing().createdAt &&
incoming().level == existing().level &&
incoming().userId == existing().userId &&
(
incoming().diff(existing()).affectedKeys().hasOnly(['name', 'avatar', 'coins', 'inventory', 'achievements', 'completedTrades', 'hasSeenTutorial', 'updatedAt'])
);
}
// 2. Items Collection (System items)
match /items/{itemId} {
allow read: if isSignedIn();
allow write: if isAdmin();
}
// 3. Market Collection
match /market/{itemId} {
allow read: if isSignedIn();
allow write: if isAdmin();
}
// 4. Trades Collection
match /trades/{tradeId} {
allow read: if isSignedIn() && (isOwner(existing().senderId) || isOwner(existing().receiverId));
allow list: if isSignedIn() && (resource.data.senderId == request.auth.uid || resource.data.receiverId == request.auth.uid);
allow create: if isVerified() && isValidTrade(incoming());
allow update: if isVerified() && (
// Receiver can Accept or Reject
(isOwner(existing().receiverId) &&
existing().status == 'pending' &&
incoming().status in ['accepted', 'rejected'] &&
incoming().diff(existing()).affectedKeys().hasOnly(['status'])) ||
// Sender can Cancel
(isOwner(existing().senderId) &&
existing().status == 'pending' &&
incoming().status == 'cancelled' &&
incoming().diff(existing()).affectedKeys().hasOnly(['status']))
);
}
// 5. Events Collection
match /events/{eventId} {
allow read: if isSignedIn();
allow write: if isAdmin();
}
// 6. Global World State
match /world_state/{id} {
allow read: if isSignedIn();
allow write: if isAdmin();
}
}
}