rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read: if true; } // Helper functions 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 isSignedIn() { return request.auth != null; } function isAdmin() { return isSignedIn() && exists(/databases/$(database)/documents/users/$(request.auth.uid)) && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin'; } // Validation blueprints function isValidUser(data) { return data.keys().hasAny(['email']) && data.email is string && data.email.size() <= 200; } function isValidAppointment(data) { return data.keys().hasAll(['patientName', 'patientEmail', 'doctorName']) && data.patientName is string && data.patientName.size() <= 100 && data.patientEmail is string && data.patientEmail.size() <= 200 && data.doctorName is string && data.doctorName.size() <= 100; } function isValidGalleryItem(data) { return true; // Admin only, we can be more lenient } function isValidCMSPage(data) { return data.keys().hasAny(['pageId']) && data.pageId is string && data.pageId.size() <= 100; } function isValidContactMessage(data) { return data.keys().hasAll(['firstName', 'email', 'message']) && data.firstName is string && data.firstName.size() <= 100 && data.email is string && data.email.size() <= 200 && data.message is string && data.message.size() <= 5000; } function isValidDoctor(data) { return true; // Admin only } function isValidStaff(data) { return true; // Admin only } function isValidInternshipApplication(data) { return data.keys().hasAll(['fullName', 'email', 'phone']) && data.fullName is string && data.fullName.size() <= 100 && data.email is string && data.email.size() <= 200 && data.phone is string && data.phone.size() <= 50; } function isValidSEOSetting(data) { return true; // Admin only } function isValidReview(data) { return true; // Admin only for now, since it might have missing fields } function isValidService(data) { return true; // Admin only } // Paths match /users/{userId} { allow read: if true; allow create: if isValidId(userId) && isSignedIn() && request.auth.uid == userId && isValidUser(incoming()); allow update: if isValidId(userId) && isSignedIn() && (request.auth.uid == userId || isAdmin()) && isValidUser(incoming()); allow delete: if isAdmin(); } match /appointments/{appointmentId} { allow read: if true; allow create: if isValidId(appointmentId) && isValidAppointment(incoming()); allow update: if isValidId(appointmentId) && isAdmin(); allow delete: if isAdmin(); } match /gallery/{galleryId} { allow read: if true; allow create: if isValidId(galleryId) && isAdmin() && isValidGalleryItem(incoming()); allow update: if isValidId(galleryId) && isAdmin(); allow delete: if isAdmin(); } match /cms/{cmsId} { allow read: if true; allow create: if isValidId(cmsId) && isAdmin() && isValidCMSPage(incoming()); allow update: if isValidId(cmsId) && isAdmin(); allow delete: if isAdmin(); } match /contact_messages/{messageId} { allow read: if true; allow create: if isValidId(messageId) && isValidContactMessage(incoming()); allow update: if isValidId(messageId) && isAdmin(); allow delete: if isAdmin(); } match /doctors/{doctorId} { allow read: if true; allow create: if isValidId(doctorId) && isAdmin() && isValidDoctor(incoming()); allow update: if isValidId(doctorId) && isAdmin(); allow delete: if isAdmin(); } match /staff/{staffId} { allow read: if true; allow create: if isValidId(staffId) && isAdmin() && isValidStaff(incoming()); allow update: if isValidId(staffId) && isAdmin(); allow delete: if isAdmin(); } match /internship_applications/{applicationId} { allow read: if true; allow create: if isValidId(applicationId) && isValidInternshipApplication(incoming()); allow update: if isValidId(applicationId) && isAdmin(); allow delete: if isAdmin(); } match /seo_settings/{settingId} { allow read: if true; allow create: if isValidId(settingId) && isAdmin() && isValidSEOSetting(incoming()); allow update: if isValidId(settingId) && isAdmin(); allow delete: if isAdmin(); } match /patient_stories/{storyId} { allow read: if true; allow create: if isValidId(storyId) && isAdmin() && isValidReview(incoming()); allow update: if isValidId(storyId) && isAdmin(); allow delete: if isAdmin(); } match /reviews/{reviewId} { allow read: if true; allow create: if isValidId(reviewId) && isAdmin() && isValidReview(incoming()); allow update: if isValidId(reviewId) && isAdmin(); allow delete: if isAdmin(); } match /services/{serviceId} { allow read: if true; allow create: if isValidId(serviceId) && isAdmin() && isValidService(incoming()); allow update: if isValidId(serviceId) && isAdmin(); allow delete: if isAdmin(); } } }