Update: 2026-08-08 13:33:53
This commit is contained in:
@@ -28,31 +28,42 @@ class CRUD {
|
||||
|
||||
// ── فحص صلاحية JWT بدون مكتبات خارجية ──────────────────────
|
||||
static bool _isJwtValid(String? token) {
|
||||
if (token == null || token.isEmpty) return false;
|
||||
try {
|
||||
final parts = token.split('.');
|
||||
if (parts.length != 3) return false;
|
||||
// فك تشفير الـ payload (الجزء الثاني)
|
||||
String payload = parts[1];
|
||||
// إضافة padding للـ base64
|
||||
switch (payload.length % 4) {
|
||||
case 2:
|
||||
payload += '==';
|
||||
break;
|
||||
case 3:
|
||||
payload += '=';
|
||||
break;
|
||||
}
|
||||
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||
final exp = decoded['exp'];
|
||||
if (exp == null) return false;
|
||||
// نعتبر التوكن منتهي قبل 30 ثانية من انتهاء الصلاحية (buffer)
|
||||
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||
} catch (_) {
|
||||
final decoded = LoginDriverController.decodeJwtPayload(token);
|
||||
if (decoded == null) return false;
|
||||
|
||||
final exp = decoded['exp'];
|
||||
if (exp is! int) return false;
|
||||
// نعتبر التوكن منتهي قبل 30 ثانية من انتهاء الصلاحية (buffer)
|
||||
if (DateTime.now().millisecondsSinceEpoch >= (exp * 1000 - 30000)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// توكن التسجيل صالح زمنياً لكن الباك إند يرفضه بـ 403 على أي endpoint
|
||||
// محمي. ما دام عندنا driverID فالتسجيل انتهى ويلزمنا توكن driver.
|
||||
if (LoginDriverController.isRegistrationToken(decoded) &&
|
||||
(box.read(BoxName.driverID)?.toString().isNotEmpty ?? false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// الباك إند يردّ 403 بهذه الرسالة تحديداً عندما يكون التوكن من نوع
|
||||
/// registration على endpoint محمي — وهي الحالة الوحيدة من حالات الـ 403
|
||||
/// التي يحلّها تجديد التوكن (بقيّتها: بصمة جهاز / HMAC / سلامة التطبيق).
|
||||
static bool _isUnauthorizedTokenType(String body) =>
|
||||
body.contains('Token not authorized for this action');
|
||||
|
||||
/// endpoints عامة لا يتحقق فيها الباك إند من التوكن (طلب/تحقق OTP،
|
||||
/// تسجيل الدخول، التسجيل، تسجيل الأخطاء). حجب الطلب عنها عند غياب
|
||||
/// التوكن يقفل بالضبط المسار الذي يستعيد به التطبيق توكناً صالحاً —
|
||||
/// وهو ما كان يترك جهازاً جديداً عالقاً بلا مخرج.
|
||||
static bool _isPublicEndpoint(String link) =>
|
||||
link.contains('/auth/otp/') ||
|
||||
link.contains('login') ||
|
||||
link.contains('register') ||
|
||||
link.contains('errorApp.php');
|
||||
|
||||
static Future<void> addError(
|
||||
String error, String details, String where) async {
|
||||
try {
|
||||
@@ -204,8 +215,9 @@ class CRUD {
|
||||
return 'rate_limited';
|
||||
}
|
||||
|
||||
// 401 → تجديد التوكن مرة واحدة ثم إعادة الطلب مرة واحدة فقط
|
||||
if (sc == 401) {
|
||||
// 401 (منتهي) أو 403 بتوكن تسجيل (بلا صلاحيات) →
|
||||
// تجديد التوكن مرة واحدة ثم إعادة الطلب مرة واحدة فقط
|
||||
if (sc == 401 || (sc == 403 && _isUnauthorizedTokenType(body))) {
|
||||
// تخطي تجديد التوكن لـ endpoints غير حرجة (مثل تسجيل الأخطاء)
|
||||
final isNonCritical = link.contains('errorApp.php');
|
||||
if (isNonCritical || !allowRefresh) return 'token_expired';
|
||||
@@ -245,11 +257,9 @@ class CRUD {
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
String token = await _ensureJwt();
|
||||
if (token.isEmpty) {
|
||||
if (token.isEmpty && !_isPublicEndpoint(link)) {
|
||||
// إذا فشل الحصول على توكن، لا ترسل الطلب للباك إند لأنّه سيرفض حتماً.
|
||||
if (!link.contains('login') && !link.contains('errorApp.php')) {
|
||||
return 'token_expired';
|
||||
}
|
||||
return 'token_expired';
|
||||
}
|
||||
|
||||
final headers = {
|
||||
@@ -292,7 +302,9 @@ class CRUD {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['status'] == 'success') return response.body;
|
||||
return jsonData['status'];
|
||||
} else if (response.statusCode == 401) {
|
||||
} else if (response.statusCode == 401 ||
|
||||
(response.statusCode == 403 &&
|
||||
_isUnauthorizedTokenType(response.body))) {
|
||||
// تجديد التوكن مرة واحدة ثم إعادة الطلب مرة واحدة فقط
|
||||
// (allowRefresh: false تمنع أي تكرار إضافي) — نفس سلوك _makeRequest.
|
||||
if (!allowRefresh) return 'token_expired';
|
||||
|
||||
Reference in New Issue
Block a user