Firestore Security Rules - Rollen-basierter Zugriff¶
User Rollen¶
Basierend auf UserRoleEnum in /lib/models/all_enums/user_role_enum.dart:
Rollen in Firestore Rules¶
Die Rolle wird im users Document als Index gespeichert:
// Firestore Document: /users/{userId}
{
"firstName": "Max",
"lastName": "Mustermann",
"email": "max@example.com",
"userRole": 2, // ← 0=user, 1=admin, 2=superadmin
...
}
Helper Functions¶
// Prüft ob User SuperAdmin ist (userRole == 2)
function isSuperAdmin() {
return isAuthenticated() && getUserData().userRole == 2;
}
// Prüft ob User mindestens Admin ist (userRole >= 1)
function isAdmin() {
return isAuthenticated() && getUserData().userRole >= 1;
}
// Holt die User-Daten aus der users Collection
function getUserData() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
}
Berechtigungen nach Rolle¶
🔵 User (index 0)¶
- ✅ Lesen: Articles, Customers, Orders, eigene User-Daten
- ✅ Schreiben: Articles, Customers, Orders erstellen/bearbeiten
- ✅ Lesen: System Settings
- ❌ Schreiben: System Settings
- ❌ Schreiben: Andere User
🟡 Admin (index 1)¶
- ✅ Alle Berechtigungen von User
- ✅ Zusätzlich: (kann erweitert werden)
🔴 SuperAdmin (index 2)¶
- ✅ Alle Berechtigungen von Admin
- ✅ System Settings schreiben (einzige Rolle mit Zugriff!)
- ✅ Alle User bearbeiten
- ✅ Connector Credentials verwalten
Geschützte Collections¶
System Settings - NUR SuperAdmin¶
match /system_settings/{settingId} {
allow read: if isAuthenticated(); // Alle können lesen
allow write: if isSuperAdmin(); // Nur SuperAdmin schreiben
match /push_notification_settings/{notificationId} {
allow read: if isAuthenticated();
allow write: if isSuperAdmin();
}
}
Users Collection¶
match /users/{userId} {
allow read: if isAuthenticated(); // Alle können alle User lesen
allow write: if isOwner(userId) || // Eigene Daten
isSuperAdmin(); // Oder SuperAdmin
}
Warum können alle User lesen?
- Die isSuperAdmin() Funktion muss die Rolle aus /users/{uid} lesen
- Wenn User ihre eigene Rolle nicht lesen können, funktioniert die Funktion nicht
- Nur userRole, email, name sind sichtbar (keine sensitiven Daten)
Testing¶
Test 1: Normal User versucht System Settings zu ändern¶
// User Document: userRole = 0
await db.collection('system_settings').doc('test').update({value: 'test'});
// ❌ FEHLER: Missing or insufficient permissions
Test 2: SuperAdmin ändert System Settings¶
// User Document: userRole = 2
await db.collection('system_settings').doc('test').update({value: 'test'});
// ✅ ERFOLG
Test 3: User ändert eigene Daten¶
// Eigene UID = 'abc123'
await db.collection('users').doc('abc123').update({firstName: 'Max'});
// ✅ ERFOLG
Test 4: User ändert fremde User-Daten¶
// Fremde UID = 'xyz789'
await db.collection('users').doc('xyz789').update({firstName: 'Max'});
// ❌ FEHLER: Missing or insufficient permissions
Ersten SuperAdmin erstellen¶
Option 1: Firebase Console (manuell)¶
- Gehe zu Firebase Console → Firestore
- Navigiere zu
usersCollection - Finde deinen User (nach UID oder Email)
- Bearbeite das Feld
userRole→ setze auf2
Option 2: Cloud Function (einmalig)¶
// functions/setup_first_admin.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.makeFirstSuperAdmin = functions.https.onRequest(async (req, res) => {
const email = 'admin@example.com';
// Finde User by Email
const userSnapshot = await db.collection('users')
.where('email', '==', email)
.limit(1)
.get();
if (userSnapshot.empty) {
return res.status(404).send('User not found');
}
const userId = userSnapshot.docs[0].id;
// Setze SuperAdmin Rolle
await db.collection('users').doc(userId).update({
userRole: 2 // SuperAdmin
});
res.send(`User ${email} is now SuperAdmin`);
});
Deployment:
firebase deploy --only functions:makeFirstSuperAdmin
# Aufrufen (einmalig!)
curl https://YOUR-REGION-YOUR-PROJECT.cloudfunctions.net/makeFirstSuperAdmin
# Danach löschen:
# Lösche die Function aus functions/index.js
firebase deploy --only functions
Option 3: Admin SDK Script (lokal)¶
// scripts/make_superadmin.js
const admin = require('firebase-admin');
const serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
async function makeSuperAdmin(email) {
const snapshot = await db.collection('users')
.where('email', '==', email)
.limit(1)
.get();
if (snapshot.empty) {
console.log('User not found');
return;
}
const userId = snapshot.docs[0].id;
await db.collection('users').doc(userId).update({ userRole: 2 });
console.log(`✅ ${email} is now SuperAdmin`);
}
makeSuperAdmin('admin@example.com');
Ausführen:
Performance-Hinweis¶
⚠️ Die getUserData() Funktion macht einen zusätzlichen Firestore-Read!
Bei jedem Schreibzugriff auf system_settings wird:
1. Der Request geprüft
2. Ein zusätzlicher Read auf /users/{uid} gemacht
3. Die userRole geprüft
Kosten: - Jeder Schreibversuch = 1 Write + 1 Read - Bei vielen System Settings Writes → höhere Kosten
Alternative: Custom Claims (komplexer, aber performanter) - User-Rolle wird im Auth Token gespeichert - Keine zusätzlichen Firestore Reads - Siehe: https://firebase.google.com/docs/auth/admin/custom-claims
Migration auf Production Rules¶
# 1. Backup aktuelle Rules
cp firestore.rules firestore.rules.backup
# 2. Production Rules aktivieren
cp firebase_config/firestore.rules.production firestore.rules
# 3. Ersten SuperAdmin erstellen (siehe oben)
# 4. Deployen
firebase deploy --only firestore:rules
# 5. Testen mit verschiedenen User-Rollen
Troubleshooting¶
"Missing or insufficient permissions" für normale Admins¶
→ Nur SuperAdmin (userRole=2) kann System Settings ändern → Prüfe in Firebase Console: users/{uid}/userRole sollte 2 sein
"getUserData() returns null"¶
→ Der User existiert nicht in der users Collection
→ Stelle sicher, dass bei der Registrierung ein User-Document erstellt wird
"isSuperAdmin() always returns false"¶
→ Prüfe ob request.auth.uid mit der Document-ID in users übereinstimmt
→ Prüfe ob das Feld userRole als Number (nicht String) gespeichert ist