// lib/services/auth/jwt_utils.dart // // ابزار کوچک برای decode کردن claims یک JWT بدون اعتبارسنجی signature. // از این فقط برای استخراج اطلاعات داخل توکن استفاده می‌کنیم (مثلا sub). import 'dart:convert'; class JwtUtils { /// claims یک JWT را به صورت Map برمی‌گرداند، یا در صورت نامعتبر بودن null. static Map? decodePayload(String token) { try { final parts = token.split('.'); if (parts.length < 2) return null; final payload = _padBase64(parts[1]); final decoded = utf8.decode(base64Url.decode(payload)); final map = json.decode(decoded); if (map is Map) return map; return Map.from(map as Map); } catch (_) { return null; } } /// مقدار claim `sub` (شناسه کاربر در Keycloak) را برمی‌گرداند. static String? subjectOf(String token) { final payload = decodePayload(token); return payload?['sub'] as String?; } /// لیست realm role های کاربر — از `realm_access.roles`. static List realmRolesOf(String token) { final payload = decodePayload(token); if (payload == null) return const []; final realmAccess = payload['realm_access']; if (realmAccess is Map && realmAccess['roles'] is List) { return List.from(realmAccess['roles'] as List); } return const []; } /// لیست group path های کاربر — از claim `groups`. static List groupsOf(String token) { final payload = decodePayload(token); if (payload == null) return const []; final g = payload['groups']; if (g is List) return List.from(g); return const []; } /// tenant slug کاربر — از `tenant_id` یا استخراج از groups. static String tenantSlugOf(String token) { final payload = decodePayload(token); if (payload == null) return ''; final direct = payload['tenant_id']; if (direct is String && direct.isNotEmpty) return direct; // Fallback: از groups استخراج کن final groups = groupsOf(token); const groupToSlug = { 'Fartak': 'fartak', 'MobarakehSteel': 'mobarakeh', 'GoharZamin': 'gohar-zamin', }; for (final path in groups) { for (final segment in path.split('/')) { final slug = groupToSlug[segment]; if (slug != null) return slug; } } return ''; } /// True اگر کاربر super-admin است (گروه Fartak یا role superadmin). static bool isSuperTenant(String token) { final slug = tenantSlugOf(token); if (slug == 'fartak') return true; final roles = realmRolesOf(token); if (roles.contains('superadmin')) return true; final groups = groupsOf(token); for (final g in groups) { if (g == '/Fartak' || g.startsWith('/Fartak/')) return true; } return false; } /// True اگر کاربر می‌تواند نوع محتوایی مشخص را ببیند. /// deny role همیشه اولویت دارد؛ در غیر این صورت بخش باز است. /// /// انواع شناخته‌شده: news، radar، trend، risk، technology، startup، /// podcast، videocast، didvan-plus، strategy-bite، monthly، survey، story، /// infography، hoshan، swot. static bool canView(String token, String contentType) { final roles = realmRolesOf(token); if (roles.contains('deny:view:$contentType')) return false; return true; } static String _padBase64(String input) { final mod = input.length % 4; if (mod == 0) return input; return input + '=' * (4 - mod); } }