leafittome/firestore.rules
cschlaefke f0b420b656 V3: Stellplatz-Bewertung (Standort-Analyse per Foto + Eignungs-Sterne auf Abruf)
Letzter Roadmap-Punkt von V3. Foto-basierte Lichtverhältnis-Analyse pro
Stellplatz (Cloud Function analyzeLocation) und darauf aufbauende, rein
textbasierte Eignungs-Bewertung einer Pflanze (assessPlantFit) — beides
ausschließlich auf Abruf, nicht automatisch (Kostenkontrolle, eigener
Anthropic-Key). Neue Stellplatz-Detailseite, Verlinkung im Pflanzen-Detail,
Erkennung veralteter Bewertungen bei Umzug/Neuanalyse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:17:18 +02:00

115 lines
4.8 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',
'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';
}
}
}
}