You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
4.7 KiB
Plaintext

rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ===============================================================
// Helper Functions
// ===============================================================
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return isAuthenticated() && request.auth.uid == userId;
}
function isDocOwner() {
return isAuthenticated() && request.auth.uid == resource.data.authorUid;
}
function uidUnchanged() {
return !('authorUid' in request.resource.data) ||
request.resource.data.authorUid == request.auth.uid;
}
function uidNotModified() {
return !('authorUid' in request.resource.data) ||
request.resource.data.authorUid == resource.data.authorUid;
}
function hasRequiredFields(fields) {
return request.resource.data.keys().hasAll(fields);
}
function isValidUrl(url) {
return url is string &&
(url.matches("^https://.*") || url.matches("^http://.*"));
}
function isValidNewsItem(item) {
return item is map &&
item.keys().hasAll(['title', 'url', 'summary']) &&
item.title is string && item.title.size() > 0 && item.title.size() < 500 &&
item.url is string && item.url.size() < 1000 &&
item.summary is string && item.summary.size() > 0 && item.summary.size() < 2000;
}
function isValidNewsArray(arr) {
return arr is list && arr.size() <= 50; // Max 50 items per section
}
function isValidSynthesis(data) {
return hasRequiredFields(['week', 'createdAt', 'authorUid']) &&
data.week is string && data.week.size() > 0 && data.week.size() < 20 &&
data.createdAt is timestamp &&
data.authorUid is string && data.authorUid.size() > 0 &&
(!('majorAnnouncements' in data) || isValidNewsArray(data.majorAnnouncements)) &&
(!('financialSector' in data) || isValidNewsArray(data.financialSector)) &&
(!('otherEnterprises' in data) || isValidNewsArray(data.otherEnterprises)) &&
(!('publicSector' in data) || isValidNewsArray(data.publicSector)) &&
(!('generalPublic' in data) || isValidNewsArray(data.generalPublic)) &&
(!('sections' in data) || data.sections is list);
}
function isValidSettings(data) {
return hasRequiredFields(['theme', 'maxAgeDays', 'categories', 'maxItemsPerCategory']) &&
data.theme is string && data.theme.size() > 0 && data.theme.size() < 200 &&
data.maxAgeDays is number && data.maxAgeDays > 0 && data.maxAgeDays <= 365 &&
data.categories is list && data.categories.size() > 0 && data.categories.size() <= 20 &&
data.maxItemsPerCategory is number && data.maxItemsPerCategory > 0 && data.maxItemsPerCategory <= 50;
}
function isValidSource(data) {
return hasRequiredFields(['title', 'url', 'authorUid', 'createdAt']) &&
data.title is string && data.title.size() > 0 && data.title.size() < 200 &&
data.url is string && isValidUrl(data.url) && data.url.size() < 1000 &&
data.authorUid is string && data.authorUid.size() > 0 &&
data.createdAt is timestamp;
}
match /syntheses/{synthesisId} {
allow read: if isAuthenticated() && isDocOwner();
allow create: if isAuthenticated() &&
isValidSynthesis(request.resource.data) &&
uidUnchanged();
allow update: if isAuthenticated() &&
isDocOwner() &&
isValidSynthesis(request.resource.data) &&
uidNotModified() &&
request.resource.data.createdAt == resource.data.createdAt;
allow delete: if isAuthenticated() && isDocOwner();
}
match /sources/{sourceId} {
allow read: if isAuthenticated() && isDocOwner();
allow create: if isAuthenticated() &&
isValidSource(request.resource.data) &&
uidUnchanged();
allow update: if isAuthenticated() &&
isDocOwner() &&
isValidSource(request.resource.data) &&
uidNotModified() &&
request.resource.data.createdAt == resource.data.createdAt;
allow delete: if isAuthenticated() && isDocOwner();
}
match /settings/{userId} {
allow read: if isAuthenticated() && request.auth.uid == userId;
allow write: if isAuthenticated() && request.auth.uid == userId &&
isValidSettings(request.resource.data);
}
}
}