103 lines
3.7 KiB
Dart
103 lines
3.7 KiB
Dart
// lib/services/auth/jwt_utils.dart
|
|
//
|
|
// ابزار کوچک برای decode کردن claims یک JWT بدون اعتبارسنجی signature.
|
|
// از این فقط برای استخراج اطلاعات داخل توکن استفاده میکنیم (مثلا sub).
|
|
|
|
import 'dart:convert';
|
|
|
|
class JwtUtils {
|
|
/// claims یک JWT را به صورت Map برمیگرداند، یا در صورت نامعتبر بودن null.
|
|
static Map<String, dynamic>? 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<String, dynamic>) return map;
|
|
return Map<String, dynamic>.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<String> 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<String>.from(realmAccess['roles'] as List);
|
|
}
|
|
return const [];
|
|
}
|
|
|
|
/// لیست group path های کاربر — از claim `groups`.
|
|
static List<String> groupsOf(String token) {
|
|
final payload = decodePayload(token);
|
|
if (payload == null) return const [];
|
|
final g = payload['groups'];
|
|
if (g is List) return List<String>.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);
|
|
}
|
|
}
|