Zum Inhalt

Firestore Security Rules - Rollen-basierter Zugriff

User Rollen

Basierend auf UserRoleEnum in /lib/models/all_enums/user_role_enum.dart:

enum UserRoleEnum { 
  user,        // index 0
  admin,       // index 1
  superadmin   // index 2
}

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)

  1. Gehe zu Firebase Console → Firestore
  2. Navigiere zu users Collection
  3. Finde deinen User (nach UID oder Email)
  4. Bearbeite das Feld userRole → setze auf 2

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:

node scripts/make_superadmin.js

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