rules_version = '2'; // Sicherheitsregeln für LeafItToMe. // // Grundprinzip: Alle App-Daten hängen an einem Haushalt. Lesen darf nur, // wer im Feld `memberUids` des Haushalts steht. Schreiben hängt zusätzlich // von der Rolle ab: "member" darf alles, "sitter" (Pflanzen-Sitter) darf // nur Aufgaben bestätigen. Das eigene Nutzer-Dokument sieht nur der Nutzer. service cloud.firestore { match /databases/{database}/documents { function signedIn() { return request.auth != null; } function householdData(householdId) { return get(/databases/$(database)/documents/households/$(householdId)).data; } function isMember(householdId) { return signedIn() && request.auth.uid in householdData(householdId).memberUids; } // Rolle des aufrufenden Nutzers. Haushalte aus der Zeit vor V2 haben // noch keine members-Map – dann gilt jeder Eingetragene als "member". function roleOf(householdId) { return householdData(householdId) .get('members', {}) .get(request.auth.uid, {'role': 'member'}) .get('role', 'member'); } // Eigenes Nutzerprofil (enthält u. a. die householdId). match /users/{uid} { allow read, write: if signedIn() && request.auth.uid == uid; } // Einladungscodes: Erstellen darf jedes Mitglied des jeweiligen // Haushalts. Gelesen und eingelöst werden Codes ausschließlich über // die Cloud Function joinHousehold (Admin-Rechte) – Clients nie direkt. match /invites/{code} { allow create: if signedIn() && request.resource.data.createdBy == request.auth.uid && isMember(request.resource.data.householdId); allow read, update, delete: if false; } match /households/{householdId} { allow read: if signedIn() && request.auth.uid in resource.data.memberUids; // Vom Client aus dürfen volle Mitglieder nur den Namen ändern; // jede Mitglieder-Verwaltung (Beitritt, Austritt, Entfernen) läuft // ausschließlich über Cloud Functions mit Admin-Rechten. // Ausnahme: Jeder darf seinen EIGENEN members-Eintrag pflegen (die App // trägt so die E-Mail in Alt-Haushalten nach), die Rolle aber nicht // ändern – sonst könnte sich ein Sitter selbst befördern. allow update: if signedIn() && request.auth.uid in resource.data.memberUids && ( (resource.data.get('members', {}) .get(request.auth.uid, {'role': 'member'}) .get('role', 'member') == 'member' && request.resource.data.diff(resource.data).affectedKeys() .hasOnly(['name'])) || (request.resource.data.diff(resource.data).affectedKeys() .hasOnly(['members']) && request.resource.data.members .diff(resource.data.get('members', {})) .affectedKeys() .hasOnly([request.auth.uid]) && request.resource.data.members[request.auth.uid] .get('role', 'member') == resource.data.get('members', {}) .get(request.auth.uid, {'role': 'member'}) .get('role', 'member')) ); // Anlegen nur, wenn man sich selbst als Mitglied einträgt // (passiert automatisch bei der Registrierung). allow create: if signedIn() && request.auth.uid in request.resource.data.memberUids; allow delete: if false; match /plants/{plantId} { allow read: if isMember(householdId); allow create, delete: if isMember(householdId) && roleOf(householdId) == 'member'; // Sitter dürfen ausschließlich die Bestätigungs-Felder ändern. allow update: if isMember(householdId) && (roleOf(householdId) == 'member' || request.resource.data.diff(resource.data).affectedKeys() .hasOnly(['lastWatered', 'lastWateredBy', 'lastFertilized', 'lastFertilizedBy', 'lastRepotted', 'lastRepottedBy', 'fitStars', 'fitReasoning', 'fitLocationId', 'fitAssessedAt'])); // Untersuchungshistorie (KI-Diagnosen). Speichern und lesen darf // jede*r im Haushalt (auch Sitter — die dürfen den Check nutzen); // löschen nur volle Mitglieder, nachträglich ändern niemand. match /diagnoses/{diagnosisId} { allow read, create: if isMember(householdId); allow delete: if isMember(householdId) && roleOf(householdId) == 'member'; allow update: if false; } } match /locations/{locationId} { allow read: if isMember(householdId); allow write: if isMember(householdId) && roleOf(householdId) == 'member'; } } } }