leafittome/firestore.rules
cschlaefke 1b2c880c89 V2: Haushalt teilen – Einladungscodes, Mitglieder- und Sitter-Rolle, sichtbare Bestätigungen
- Einladen per einmaligem 6-stelligem Code (7 Tage gültig), wahlweise als Mitglied oder Pflanzen-Sitter
- Beitritt über Cloud Function joinHousehold (Transaktion, Admin-Rechte)
- Security Rules: Sitter dürfen an Pflanzen nur Bestätigungs-Felder ändern; invites nur erstellen, nie lesen
- Haushalts-Screen: Mitgliederliste mit Rollen, Einladen, Beitreten mit Wechsel-Warnung
- UI-Gating: Sitter sehen keine Anlegen-/Bearbeiten-/Löschen-Aktionen
- Bestätigungen speichern den Namen (lastWateredBy/lastFertilizedBy), Anzeige im Profil
- Tests: Members-Map im Seed, neuer Sitter-Test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:14:34 +02:00

83 lines
3.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;
// Ändern (z. B. Name) nur für volle Mitglieder; Mitglieder-Verwaltung
// läuft über die Cloud Function.
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';
// 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';
}
}
}
}