leafittome/firestore.rules
cschlaefke edc4ecf405 Fix: Besitzer-Anzeige in Alt-Haushalten – eigene E-Mail wird nachgetragen
Haushalte aus der V1-Zeit haben keinen members-Eintrag für ihren Ersteller,
die Besitzer-Anzeige blieb dadurch leer. Die App trägt die eigene E-Mail
jetzt beim Start in allen eigenen Haushalten nach; die Rules erlauben dafür
nur den eigenen members-Eintrag ohne Rollenwechsel. Bis dahin zeigt die UI
„?" statt einer leeren Stelle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:50:22 +02:00

102 lines
4.1 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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']));
}
match /locations/{locationId} {
allow read: if isMember(householdId);
allow write: if isMember(householdId)
&& roleOf(householdId) == 'member';
}
}
}
}