File size: 5,701 Bytes
a667b81 | 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 | rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Global Safety net default-deny catch-all
match /{document=**} {
allow read, write: if false;
}
// Hardened helper functions for static verification (denial of wallet guard)
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;
}
// High fidelity schema assertion helper for Tire Products
function isValidTireProduct(data) {
return data.keys().hasAll(['id', 'name', 'brand', 'size', 'feature', 'price', 'badge', 'segment', 'image', 'description'])
&& data.keys().size() == 10
&& data.id is string && data.id.size() <= 128
&& data.name is string && data.name.size() <= 128
&& data.brand is string && data.brand.size() <= 128
&& data.size is string && data.size.size() <= 64
&& data.feature is string && data.feature.size() <= 256
&& (data.price is int || data.price is float) && data.price >= 0
&& data.badge is string && data.badge.size() <= 128
&& data.segment is string && (data.segment == 'hot' || data.segment == 'new' || data.segment == 'famous')
&& data.image is string && data.image.size() <= 1024
&& data.description is string && data.description.size() <= 4096;
}
// Modern High fidelity schema helper for the requested "products" collection
function isValidProduct(data) {
return data.name is string && data.name.size() <= 128
&& data.brand is string && data.brand.size() <= 128
&& data.size is string && data.size.size() <= 64
&& (data.price is int || data.price is float) && data.price >= 0
&& data.category is string && (data.category == 'hot-selling' || data.category == 'new-brands' || data.category == 'famous')
&& (data.stock is int || data.stock is float) && data.stock >= 0
&& data.feature is string && data.feature.size() <= 256
&& data.imageUrl is string && data.imageUrl.size() <= 1024;
}
// High fidelity schema assertion helper for User Testimonials
function isValidUserReview(data) {
return data.keys().hasAll(['name', 'rating', 'text', 'date', 'initials'])
&& data.keys().size() == 5
&& data.name is string && data.name.size() <= 128
&& data.rating is int && data.rating >= 1 && data.rating <= 5
&& data.text is string && data.text.size() <= 4096
&& data.date is string && data.date.size() <= 128
&& data.initials is string && data.initials.size() <= 16;
}
// High fidelity schema assertion helper for Visitor Analytics
function isValidVisitorLog(data) {
return data.keys().hasAll(['sessionId', 'userAgent', 'platform', 'language', 'pagePath', 'timestamp'])
&& data.keys().size() == 6
&& data.sessionId is string && data.sessionId.size() <= 256
&& data.userAgent is string && data.userAgent.size() <= 1024
&& data.platform is string && data.platform.size() <= 128
&& data.language is string && data.language.size() <= 64
&& data.pagePath is string && data.pagePath.size() <= 256
&& data.timestamp is timestamp && data.timestamp == request.time;
}
// High fidelity schema assertion helper for Newsletter Subscribers
function isValidSubscriber(data) {
return data.keys().hasAll(['email', 'createdAt'])
&& data.keys().size() == 2
&& data.email is string && data.email.size() <= 256 && data.email.matches('^[^@]+@[^@]+\\.[^@]+$')
&& data.createdAt is timestamp && data.createdAt == request.time;
}
// Hardened constraints for the /products/ collection (Public read, authenticated write or seed prefix)
match /products/{productId} {
allow read: if true;
allow create: if (request.auth != null || productId.startsWith('seed-')) && isValidId(productId) && isValidProduct(incoming());
allow update: if request.auth != null && isValidId(productId) && isValidProduct(incoming());
allow delete: if request.auth != null && isValidId(productId);
}
// Allow public read on the system connectivity check path
match /test/connection {
allow read: if true;
}
// Hardened constraints for the /tyres/ collection
match /tyres/{tyreId} {
allow read: if true;
allow create, update: if isValidId(tyreId) && isValidTireProduct(incoming());
allow delete: if isValidId(tyreId);
}
// Hardened constraints for the /reviews/ collection
match /reviews/{reviewId} {
allow read: if true;
allow create: if isValidId(reviewId) && isValidUserReview(incoming());
allow update, delete: if false; // Reviews are write-once append-only
}
// Hardened constraints for the /visitors/ collection
match /visitors/{visitorId} {
allow read: if request.auth != null; // Admin-only read for visitor stats
allow create: if isValidId(visitorId) && isValidVisitorLog(incoming());
allow update, delete: if false; // Visitor logs are immutable
}
// Hardened constraints for the /subscribers/ collection (Public sign up, Admin-only read)
match /subscribers/{subscriberId} {
allow read: if request.auth != null; // Admin can view mailing list
allow create: if isValidId(subscriberId) && isValidSubscriber(incoming());
allow update, delete: if false; // Newsletter entries are append-only/immutable
}
}
}
|