Jogosultságok projektenkénti kezelése
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s

This commit is contained in:
2026-08-10 00:18:16 +02:00
parent e2cedaf235
commit 443b35fbb9
4 changed files with 48 additions and 27 deletions
+37 -22
View File
@@ -1,17 +1,14 @@
import 'package:get/get.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
/// Oldal-/funkció-szintű jogosultságok kezelése.
/// Oldal-/funkció-szintű jogosultságok kezelése — PROJEKTENKÉNT.
///
/// A jogosultságokat egy Supabase `app_permissions` tábla tárolja
/// (user_id + area), és a felhasználó a saját sorait olvashatja (RLS).
/// A service induláskor és bejelentkezéskor betölti a jelenlegi
/// felhasználó jogosultságait egy halmazba, amit a UI reaktívan figyel.
///
/// Bővíthető: új védett terület = új 'area' string (pl. 'admin'),
/// a UI a [can] getterrel kérdez rá — kódmódosítás nélkül. A tényleges
/// védelmet a Supabase-oldali RLS adja (a `contacts`/admin táblákon),
/// ez a service csak a UI-t vezérli (menüpont elrejtése, üzenet).
/// A jogosultságokat a Supabase `app_permissions` tábla tárolja
/// (user_id + area + project_id), ahol a project_id NULL értéke
/// GLOBÁLIS jogot jelent (minden projektre érvényes — pl. admin).
/// A tényleges védelmet a Supabase-oldali RLS adja
/// (`has_permission(area, project_id)`), ez a service csak a UI-t
/// vezérli (menüpont elrejtése, üzenet).
class PermissionService extends GetxService {
static PermissionService get to => Get.find();
@@ -20,18 +17,31 @@ class PermissionService extends GetxService {
SupabaseClient get _client => Supabase.instance.client;
/// A jelenlegi felhasználó engedélyezett területei.
final _areas = <String>{}.obs;
/// projectId → engedélyezett területek. A `null` kulcs a GLOBÁLIS
/// (minden projektre érvényes) jogokat tárolja.
final _areasByProject = <String?, Set<String>>{}.obs;
final isLoaded = false.obs;
bool can(String area) => _areas.contains(area);
bool get canContacts => can(areaContacts);
/// [projectId] nélkül CSAK a globális jogokat nézi — projekt-specifikus
/// ellenőrzéshez mindig add meg az aktív projektet.
bool can(String area, {String? projectId}) {
if (_areasByProject[null]?.contains(area) ?? false) return true;
if (projectId != null) {
return _areasByProject[projectId]?.contains(area) ?? false;
}
return false;
}
bool canContacts({String? projectId}) =>
can(areaContacts, projectId: projectId);
/// Az admin jogot szándékosan globálisnak tartjuk — ha ezt is
/// projektenként szeretnéd, ugyanígy paraméterezhető.
bool get canAdmin => can(areaAdmin);
@override
void onInit() {
super.onInit();
// Induláskor és minden auth-változáskor újratöltjük.
reload();
_client.auth.onAuthStateChange.listen((_) => reload());
}
@@ -39,23 +49,28 @@ class PermissionService extends GetxService {
Future<void> reload() async {
final user = _client.auth.currentUser;
if (user == null) {
_areas.clear();
_areasByProject.clear();
isLoaded.value = true;
return;
}
try {
final rows = await _client
.from('terepi_seged_app_permissions')
.select('area')
.select('area, project_id')
.eq('user_id', user.id);
_areas
final map = <String?, Set<String>>{};
for (final r in rows) {
final pid = r['project_id'] as String?;
map.putIfAbsent(pid, () => {}).add(r['area'] as String);
}
_areasByProject
..clear()
..addAll(rows.map((r) => r['area'] as String));
..addAll(map);
} catch (_) {
// Hálózati hiba: nem adunk jogot (fail-closed), de nem is dobunk.
_areas.clear();
_areasByProject.clear();
} finally {
_areas.refresh();
_areasByProject.refresh();
isLoaded.value = true;
}
}