From 877862aaf22accabaf638903d9b4ba822e212f52 Mon Sep 17 00:00:00 2001 From: nicolas Date: Mon, 14 Sep 2026 11:44:04 +0200 Subject: [PATCH] refactor(admin): remove hardcoded email and dynamically resolve admin privileges via Firestore Admins collection --- firestore.rules | 9 ++++- lib/services/auth_service.dart | 68 ++++++++++++++++++++++++++++------ storage.rules | 3 +- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/firestore.rules b/firestore.rules index b0dc7a6..dded8d6 100644 --- a/firestore.rules +++ b/firestore.rules @@ -10,7 +10,8 @@ service cloud.firestore { function isAdmin() { return request.auth != null && ( request.auth.token.admin == true || - request.auth.token.email == 'nicolas@gruber.info' + exists(/databases/$(database)/documents/Admins/$(request.auth.uid)) || + (request.auth.token.email != null && exists(/databases/$(database)/documents/Admins/$(request.auth.token.email))) ); } @@ -22,6 +23,12 @@ service cloud.firestore { ); } + // Admins-Sammlung: Angemeldete Nutzer können prüfen, nur Admins schreiben + match /Admins/{adminDoc} { + allow read: if isAuthenticated(); + allow write: if isAdmin(); + } + // Öffentliche Sammlungen: News, Mängel (Issues) & Ideen (CitizensForum) match /{collection}/{document=**} { // Jeder darf News, Mängel & Ideen öffentlich lesen diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 7900a85..a641623 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,5 +1,6 @@ -import 'package:flutter/foundation.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; class AuthService extends ChangeNotifier { @@ -47,21 +48,64 @@ class AuthService extends ChangeNotifier { _initAuth(); } - static const List adminEmails = [ - 'nicolas@gruber.info', - ]; - Future _checkAdminClaim(User? user) async { if (user != null && !user.isAnonymous) { - final email = user.email?.trim().toLowerCase(); - if (email != null && adminEmails.contains(email)) { - _isAdmin = true; - return; - } try { + // 1. Custom claims check (token.admin == true) final tokenResult = await user.getIdTokenResult(); - _isAdmin = tokenResult.claims?['admin'] == true; - } catch (_) { + if (tokenResult.claims?['admin'] == true) { + _isAdmin = true; + return; + } + + final firestore = FirebaseFirestore.instance; + + // 2. Check Firestore 'Admins' collection by user UID (doc id) + final docByUid = + await firestore.collection('Admins').doc(user.uid).get(); + if (docByUid.exists) { + _isAdmin = true; + return; + } + + // 3. Check Firestore 'Admins' collection by email (doc id) + final userEmail = user.email?.trim().toLowerCase(); + if (userEmail != null && userEmail.isNotEmpty) { + final docByEmail = + await firestore.collection('Admins').doc(userEmail).get(); + if (docByEmail.exists) { + _isAdmin = true; + return; + } + + // 4. Check Firestore 'Admins' collection where email field == userEmail + final queryByEmail = await firestore + .collection('Admins') + .where('email', isEqualTo: userEmail) + .limit(1) + .get(); + if (queryByEmail.docs.isNotEmpty) { + _isAdmin = true; + return; + } + } + + // 5. Check Firestore 'Admins' collection where uid field == user.uid + final queryByUid = await firestore + .collection('Admins') + .where('uid', isEqualTo: user.uid) + .limit(1) + .get(); + if (queryByUid.docs.isNotEmpty) { + _isAdmin = true; + return; + } + + _isAdmin = false; + } catch (e) { + if (kDebugMode) { + debugPrint('Error checking admin status: $e'); + } _isAdmin = false; } } else { diff --git a/storage.rules b/storage.rules index b3734df..df16fb1 100644 --- a/storage.rules +++ b/storage.rules @@ -15,8 +15,7 @@ service firebase.storage { // Nur der Verfasser oder ein Admin darf sein Foto löschen allow delete: if request.auth != null && ( request.auth.uid == userId || - request.auth.token.admin == true || - request.auth.token.email == 'nicolas@gruber.info' + request.auth.token.admin == true ); } }