-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
122 lines (110 loc) · 4.7 KB
/
firestore.rules
File metadata and controls
122 lines (110 loc) · 4.7 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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function validSyncCounter(value) {
return value is int && value >= 0 && value <= 1000000000;
}
function validEpochMs(value) {
return value is int && value >= 0 && value <= 253402300799999;
}
function validSite(site) {
return site == 'lw' || site == 'eaf';
}
function validSyncNode(syncNode) {
return syncNode.matches('^pr_sync_[0-9a-f]{64}$');
}
function validWriterLabel(value) {
return value is string && value.size() > 0 && value.size() <= 128;
}
function validBoundedTtl(expiresAt) {
return expiresAt is timestamp &&
expiresAt > request.time &&
expiresAt < request.time + duration.value(15638400, 's');
}
function validReadField(readField) {
return readField is map &&
readField.keys().hasAll(['updatedAt', 'updatedBy', 'clearEpoch', 'value']) &&
readField.keys().hasOnly(['updatedAt', 'updatedBy', 'clearEpoch', 'value']) &&
readField.updatedAt is timestamp &&
validWriterLabel(readField.updatedBy) &&
validSyncCounter(readField.clearEpoch) &&
readField.value is map &&
readField.value.size() <= 10000;
// Firestore rules cannot safely iterate dynamic map entries; entry-level
// key/value validation is enforced by client decode + merge guards.
}
function validLoadFromField(loadFromField) {
return loadFromField is map &&
loadFromField.keys().hasAll(['updatedAt', 'updatedBy', 'version', 'clearEpoch']) &&
loadFromField.keys().hasOnly(['updatedAt', 'updatedBy', 'version', 'clearEpoch', 'value']) &&
loadFromField.updatedAt is timestamp &&
validWriterLabel(loadFromField.updatedBy) &&
validSyncCounter(loadFromField.version) &&
validSyncCounter(loadFromField.clearEpoch) &&
(
!('value' in loadFromField) ||
(
loadFromField.value is string &&
loadFromField.value.size() <= 40 &&
(
loadFromField.value == '__LOAD_RECENT__' ||
loadFromField.value.matches('^\\d{4}-\\d{2}-\\d{2}T.*$')
)
)
);
}
function validAuthorPrefsField(authorPrefsField) {
return authorPrefsField is map &&
authorPrefsField.keys().hasAll(['updatedAt', 'updatedBy', 'clearEpoch', 'value']) &&
authorPrefsField.keys().hasOnly(['updatedAt', 'updatedBy', 'clearEpoch', 'value']) &&
authorPrefsField.updatedAt is timestamp &&
validWriterLabel(authorPrefsField.updatedBy) &&
validSyncCounter(authorPrefsField.clearEpoch) &&
authorPrefsField.value is map &&
authorPrefsField.value.size() <= 1000;
// Firestore rules cannot safely iterate dynamic map entries; entry-level
// key/value validation is enforced by client decode + merge guards.
}
function validAIStudioPrefixField(aiStudioPrefixField) {
return aiStudioPrefixField is map &&
aiStudioPrefixField.keys().hasAll(['updatedAt', 'updatedBy', 'version']) &&
aiStudioPrefixField.keys().hasOnly(['updatedAt', 'updatedBy', 'version', 'value']) &&
aiStudioPrefixField.updatedAt is timestamp &&
validWriterLabel(aiStudioPrefixField.updatedBy) &&
validSyncCounter(aiStudioPrefixField.version) &&
(
!('value' in aiStudioPrefixField) ||
(
aiStudioPrefixField.value is string &&
aiStudioPrefixField.value.size() <= 8000
)
);
}
function validEnvelope(data) {
return data.keys().hasOnly(['schemaVersion', 'site', 'lastPushedBy', 'lastPushedAt', 'lastPushedAtMs', 'expiresAt', 'fields']) &&
data.schemaVersion == 1 &&
validSite(data.site) &&
validWriterLabel(data.lastPushedBy) &&
data.lastPushedAt is timestamp &&
(!('lastPushedAtMs' in data) || validEpochMs(data.lastPushedAtMs)) &&
validBoundedTtl(data.expiresAt) &&
data.fields is map &&
data.fields.keys().hasOnly(['read', 'loadFrom', 'authorPrefs', 'aiStudioPrefix']) &&
validReadField(data.fields.read) &&
validLoadFromField(data.fields.loadFrom) &&
validAuthorPrefsField(data.fields.authorPrefs) &&
(
!('aiStudioPrefix' in data.fields) ||
validAIStudioPrefixField(data.fields.aiStudioPrefix)
);
}
match /pr_sync_v1/{site}/nodes/{syncNode} {
allow get: if validSite(site) && validSyncNode(syncNode);
allow create, update: if validSite(site) && validSyncNode(syncNode) && validEnvelope(request.resource.data);
allow delete: if false;
}
match /{document=**} {
allow read, write: if false;
}
}
}