Fix: SSL pinning, root detection, network resilience, and compile errors
SSL pinning (all 4 apps): IOClient import, subdomain-safe domain matching Root detection (all 4 apps): modern Magisk/KernelSU/APatch paths Security checks (rider/driver/admin): PlatformException -> false Rider crud: 60s timeout, 3 retries, exponential backoff, JWT pre-validation Driver crud: exponential backoff for TimeoutException RxInt compile (rider/driver): 10.obs -> RxInt(10) Admin device_info: add missing imports, fix RxInt, add package_info_plus
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -29,7 +29,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// import 'dart:io';
|
|
||||||
|
|
||||||
// import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import '../../main.dart';
|
||||||
|
import '../../print.dart';
|
||||||
|
|
||||||
class DeviceHelper {
|
class DeviceHelper {
|
||||||
static Future<String> getDeviceFingerprint() async {
|
static Future<String> getDeviceFingerprint() async {
|
||||||
@@ -80,7 +83,7 @@ class SecurityHelper {
|
|||||||
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error during security checks: $e");
|
Log.print("Error during security checks: $e");
|
||||||
}
|
}
|
||||||
|
|
||||||
await box.write('isNotTrust', isNotTrust);
|
await box.write('isNotTrust', isNotTrust);
|
||||||
@@ -98,7 +101,7 @@ class SecurityHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _showSecurityWarning() {
|
static void _showSecurityWarning() {
|
||||||
RxInt secondsRemaining = 10.obs;
|
final secondsRemaining = RxInt(10);
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
CupertinoAlertDialog(
|
CupertinoAlertDialog(
|
||||||
@@ -150,90 +153,3 @@ class SecurityHelper {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// class DeviceInfoPlus {
|
|
||||||
// static List<Map<String, dynamic>> deviceDataList = [];
|
|
||||||
|
|
||||||
// static Future<List<Map<String, dynamic>>> getDeviceInfo() async {
|
|
||||||
// final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// if (Platform.isAndroid) {
|
|
||||||
// AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
|
||||||
// Map<String, dynamic> deviceData = {
|
|
||||||
// 'platform': 'Android',
|
|
||||||
// 'brand': androidInfo.brand,
|
|
||||||
// 'model': androidInfo.model,
|
|
||||||
// 'androidId': androidInfo.device,
|
|
||||||
// 'versionRelease': androidInfo.version.release,
|
|
||||||
// 'sdkVersion': androidInfo.version.sdkInt,
|
|
||||||
// 'manufacturer': androidInfo.manufacturer,
|
|
||||||
// 'isPhysicalDevice': androidInfo.isPhysicalDevice,
|
|
||||||
// 'serialNumber': androidInfo.serialNumber,
|
|
||||||
// 'fingerprint': androidInfo.fingerprint,
|
|
||||||
// 'type': androidInfo.type,
|
|
||||||
// 'data': androidInfo.data,
|
|
||||||
// 'version': androidInfo.version,
|
|
||||||
// 'tags': androidInfo.tags,
|
|
||||||
// 'display': androidInfo.display,
|
|
||||||
// };
|
|
||||||
// deviceDataList.add(deviceData);
|
|
||||||
// } else if (Platform.isIOS) {
|
|
||||||
// IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
|
|
||||||
// Map<String, dynamic> deviceData = {
|
|
||||||
// 'brand': 'Apple',
|
|
||||||
// 'model': iosInfo.model,
|
|
||||||
// 'systemName': iosInfo.systemName,
|
|
||||||
// 'systemVersion': iosInfo.systemVersion,
|
|
||||||
// 'utsname': iosInfo.utsname,
|
|
||||||
// 'isPhysicalDevice': iosInfo.isPhysicalDevice,
|
|
||||||
// 'identifierForVendor': iosInfo.identifierForVendor,
|
|
||||||
// 'name': iosInfo.name,
|
|
||||||
// 'localizedModel': iosInfo.localizedModel,
|
|
||||||
// };
|
|
||||||
// deviceDataList.add(deviceData);
|
|
||||||
// } else if (Platform.isMacOS) {
|
|
||||||
// MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
|
|
||||||
// Map<String, dynamic> deviceData = {
|
|
||||||
// 'platform': 'macOS',
|
|
||||||
// 'model': macInfo.model,
|
|
||||||
// 'version': macInfo.systemGUID,
|
|
||||||
// };
|
|
||||||
// deviceDataList.add(deviceData);
|
|
||||||
// } else if (Platform.isWindows) {
|
|
||||||
// WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo;
|
|
||||||
// Map<String, dynamic> deviceData = {
|
|
||||||
// 'platform': 'Windows',
|
|
||||||
// 'manufacturer': windowsInfo.computerName,
|
|
||||||
// 'version': windowsInfo.majorVersion,
|
|
||||||
// 'deviceId': windowsInfo.deviceId,
|
|
||||||
// 'userName': windowsInfo.userName,
|
|
||||||
// 'productName': windowsInfo.productName,
|
|
||||||
// 'installDate': windowsInfo.installDate,
|
|
||||||
// 'productId': windowsInfo.productId,
|
|
||||||
// 'numberOfCores': windowsInfo.numberOfCores,
|
|
||||||
// 'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
|
|
||||||
// };
|
|
||||||
// deviceDataList.add(deviceData);
|
|
||||||
// } else if (Platform.isLinux) {
|
|
||||||
// LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo;
|
|
||||||
// Map<String, dynamic> deviceData = {
|
|
||||||
// 'platform': 'Linux',
|
|
||||||
// 'manufacturer': linuxInfo.name,
|
|
||||||
// 'version': linuxInfo.version,
|
|
||||||
// };
|
|
||||||
// deviceDataList.add(deviceData);
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return deviceDataList;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Method to print all device data
|
|
||||||
// static void printDeviceInfo() {
|
|
||||||
// for (Map<String, dynamic> deviceData in deviceDataList) {
|
|
||||||
// 'Version: ${deviceData['version'] ?? deviceData['versionRelease'] ?? 'N/A'}');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class SecurityChecks {
|
|||||||
return result;
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print("Failed to check security status: ${e.message}");
|
print("Failed to check security status: ${e.message}");
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
return false; // Platform not supported → treat as secure
|
||||||
|
} catch (e) {
|
||||||
|
print("Security check error: $e");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import firebase_messaging
|
|||||||
import flutter_image_compress_macos
|
import flutter_image_compress_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import local_auth_darwin
|
import local_auth_darwin
|
||||||
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
@@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||||
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- AppAuth (1.7.6):
|
|
||||||
- AppAuth/Core (= 1.7.6)
|
|
||||||
- AppAuth/ExternalUserAgent (= 1.7.6)
|
|
||||||
- AppAuth/Core (1.7.6)
|
|
||||||
- AppAuth/ExternalUserAgent (1.7.6):
|
|
||||||
- AppAuth/Core
|
|
||||||
- AppCheckCore (11.2.0):
|
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
|
||||||
- PromisesObjC (~> 2.4)
|
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- file_selector_macos (0.0.1):
|
- file_selector_macos (0.0.1):
|
||||||
@@ -80,20 +70,9 @@ PODS:
|
|||||||
- flutter_secure_storage_macos (6.1.3):
|
- flutter_secure_storage_macos (6.1.3):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- google_sign_in_ios (0.0.1):
|
|
||||||
- AppAuth (>= 1.7.4)
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- GoogleSignIn (~> 8.0)
|
|
||||||
- GTMSessionFetcher (>= 3.4.0)
|
|
||||||
- GoogleDataTransport (10.1.0):
|
- GoogleDataTransport (10.1.0):
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- GoogleSignIn (8.0.0):
|
|
||||||
- AppAuth (< 2.0, >= 1.7.3)
|
|
||||||
- AppCheckCore (~> 11.0)
|
|
||||||
- GTMAppAuth (< 5.0, >= 4.1.1)
|
|
||||||
- GTMSessionFetcher/Core (~> 3.3)
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||||
- GoogleUtilities/Environment
|
- GoogleUtilities/Environment
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
@@ -118,14 +97,6 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.1.0):
|
- GoogleUtilities/UserDefaults (8.1.0):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
- GTMAppAuth (4.1.1):
|
|
||||||
- AppAuth/Core (~> 1.7)
|
|
||||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
|
|
||||||
- GTMSessionFetcher (3.5.0):
|
|
||||||
- GTMSessionFetcher/Full (= 3.5.0)
|
|
||||||
- GTMSessionFetcher/Core (3.5.0)
|
|
||||||
- GTMSessionFetcher/Full (3.5.0):
|
|
||||||
- GTMSessionFetcher/Core
|
|
||||||
- local_auth_darwin (0.0.1):
|
- local_auth_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -134,6 +105,9 @@ PODS:
|
|||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- PromisesSwift (2.4.0):
|
- PromisesSwift (2.4.0):
|
||||||
- PromisesObjC (= 2.4.0)
|
- PromisesObjC (= 2.4.0)
|
||||||
@@ -152,15 +126,13 @@ DEPENDENCIES:
|
|||||||
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
|
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
|
||||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- google_sign_in_ios (from `Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin`)
|
|
||||||
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- AppAuth
|
|
||||||
- AppCheckCore
|
|
||||||
- Firebase
|
- Firebase
|
||||||
- FirebaseCore
|
- FirebaseCore
|
||||||
- FirebaseCoreExtension
|
- FirebaseCoreExtension
|
||||||
@@ -171,10 +143,7 @@ SPEC REPOS:
|
|||||||
- FirebaseRemoteConfigInterop
|
- FirebaseRemoteConfigInterop
|
||||||
- FirebaseSessions
|
- FirebaseSessions
|
||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleSignIn
|
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- GTMAppAuth
|
|
||||||
- GTMSessionFetcher
|
|
||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- PromisesSwift
|
- PromisesSwift
|
||||||
@@ -196,20 +165,18 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
google_sign_in_ios:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin
|
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
|
||||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
|
||||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||||
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7
|
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
|
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
|
||||||
firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7
|
firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7
|
||||||
@@ -224,19 +191,16 @@ SPEC CHECKSUMS:
|
|||||||
FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7
|
FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7
|
||||||
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
|
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
|
||||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
|
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
|
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
|
||||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||||
|
|
||||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||||
|
|
||||||
|
|||||||
@@ -912,6 +912,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
path:
|
path:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ dependencies:
|
|||||||
device_info_plus: ^11.5.0
|
device_info_plus: ^11.5.0
|
||||||
flutter_staggered_animations: ^1.1.1
|
flutter_staggered_animations: ^1.1.1
|
||||||
jailbreak_root_detection: ^1.1.5
|
jailbreak_root_detection: ^1.1.5
|
||||||
|
package_info_plus: ^4.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -29,7 +29,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,21 +132,19 @@ class CRUD {
|
|||||||
try {
|
try {
|
||||||
attempts++;
|
attempts++;
|
||||||
response = await doPost();
|
response = await doPost();
|
||||||
break; // نجح الاتصال — نخرج
|
break;
|
||||||
} on SocketException catch (_) {
|
} on SocketException catch (_) {
|
||||||
Log.print('⚠️ SocketException attempt $attempts — $link');
|
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||||
if (attempts >= 3) {
|
if (attempts >= 3) {
|
||||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||||
return 'no_internet';
|
return 'no_internet';
|
||||||
}
|
}
|
||||||
// انتظار قبل إعادة المحاولة — مهم للشبكات المتقطعة
|
await Future.delayed(Duration(seconds: attempts));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
} on TimeoutException catch (_) {
|
} on TimeoutException catch (_) {
|
||||||
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||||
if (attempts >= 3) return 'failure';
|
if (attempts >= 3) return 'failure';
|
||||||
// لا انتظار — نعيد فوراً
|
await Future.delayed(Duration(milliseconds: 500 * attempts));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// errno = 9 (Bad file descriptor) — إعادة المحاولة
|
|
||||||
if (e.toString().contains('errno = 9') && attempts < 3) {
|
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ class SecurityHelper {
|
|||||||
// }
|
// }
|
||||||
static void _showSecurityWarning() {
|
static void _showSecurityWarning() {
|
||||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||||
RxInt secondsRemaining = 10.obs;
|
final secondsRemaining = RxInt(10);
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
CupertinoAlertDialog(
|
CupertinoAlertDialog(
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ class SecurityChecks {
|
|||||||
return result;
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print("Failed to check security status: ${e.message}");
|
print("Failed to check security status: ${e.message}");
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
return false; // Platform not supported → treat as secure
|
||||||
|
} catch (e) {
|
||||||
|
print("Security check error: $e");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -28,7 +28,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,16 +24,35 @@ class CRUD {
|
|||||||
final NetGuard _netGuard = NetGuard();
|
final NetGuard _netGuard = NetGuard();
|
||||||
final _client = SslPinning.createPinnedClient();
|
final _client = SslPinning.createPinnedClient();
|
||||||
|
|
||||||
/// Stores the signature of the last logged error to prevent duplicates.
|
static bool _isRefreshingJWT = false;
|
||||||
static String _lastErrorSignature = '';
|
static String _lastErrorSignature = '';
|
||||||
|
|
||||||
/// Stores the timestamp of the last logged error.
|
|
||||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||||
|
|
||||||
/// The minimum time that must pass before logging the same error again.
|
|
||||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||||
|
|
||||||
/// Asynchronously logs an error to the server with debouncing to prevent log flooding.
|
/// JWT validity check without external libraries.
|
||||||
|
static bool _isJwtValid(String? token) {
|
||||||
|
if (token == null || token.isEmpty) return false;
|
||||||
|
try {
|
||||||
|
final parts = token.split('.');
|
||||||
|
if (parts.length != 3) return false;
|
||||||
|
String payload = parts[1];
|
||||||
|
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;
|
||||||
|
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> addError(
|
static Future<void> addError(
|
||||||
String error, String details, String where) async {
|
String error, String details, String where) async {
|
||||||
try {
|
try {
|
||||||
@@ -54,11 +73,9 @@ class CRUD {
|
|||||||
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
||||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
||||||
|
|
||||||
// طباعة الخطأ في الكونسول للمطور للمتابعة الفورية
|
|
||||||
Log.print(
|
Log.print(
|
||||||
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
||||||
|
|
||||||
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
|
|
||||||
CRUD().post(
|
CRUD().post(
|
||||||
link: AppLink.addError,
|
link: AppLink.addError,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -75,22 +92,14 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
String _getFpHeader() {
|
String _getFpHeader() {
|
||||||
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن)
|
|
||||||
// بدلاً من GetStorage (غير مشفر)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
Future<String> _getJwt() async {
|
Future<String> _getJwt() async {
|
||||||
try {
|
try {
|
||||||
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
|
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
|
||||||
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
||||||
// Fallback إلى GetStorage للتوافقية
|
|
||||||
final String? fallback = box.read(BoxName.jwt);
|
final String? fallback = box.read(BoxName.jwt);
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
return r(fallback).toString().split(Env.addd)[0];
|
return r(fallback).toString().split(Env.addd)[0];
|
||||||
@@ -100,7 +109,6 @@ class CRUD {
|
|||||||
return r(encryptedJwt).toString().split(Env.addd)[0];
|
return r(encryptedJwt).toString().split(Env.addd)[0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.print('Error reading JWT from SecureStorage: $e');
|
Log.print('Error reading JWT from SecureStorage: $e');
|
||||||
// Fallback
|
|
||||||
final String? fallback = box.read(BoxName.jwt);
|
final String? fallback = box.read(BoxName.jwt);
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
return r(fallback).toString().split(Env.addd)[0];
|
return r(fallback).toString().split(Env.addd)[0];
|
||||||
@@ -109,47 +117,62 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Centralized private method to handle all API requests.
|
/// Centralized request handler with retry for weak networks.
|
||||||
/// Includes retry logic, network checking, and standardized error handling.
|
/// For Syria (3G): 60s total timeout, 3 retries, exponential backoff.
|
||||||
Future<dynamic> _makeRequest({
|
Future<dynamic> _makeRequest({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
required Map<String, String> headers,
|
required Map<String, String> headers,
|
||||||
}) async {
|
}) async {
|
||||||
const connectTimeout = Duration(seconds: 6);
|
const totalTimeout = Duration(seconds: 60);
|
||||||
const receiveTimeout = Duration(seconds: 10);
|
|
||||||
|
|
||||||
Future<http.Response> doPost() {
|
Future<http.Response> doPost() {
|
||||||
final url = Uri.parse(link);
|
final url = Uri.parse(link);
|
||||||
return _client
|
return _client
|
||||||
.post(url, body: payload, headers: headers)
|
.post(url, body: payload, headers: headers)
|
||||||
.timeout(connectTimeout + receiveTimeout);
|
.timeout(totalTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Response response;
|
http.Response? response;
|
||||||
try {
|
int attempts = 0;
|
||||||
// retry ذكي: محاولة واحدة إضافية فقط لأخطاء شبكة/5xx
|
|
||||||
|
while (attempts < 3) {
|
||||||
try {
|
try {
|
||||||
|
attempts++;
|
||||||
response = await doPost();
|
response = await doPost();
|
||||||
|
break;
|
||||||
} on SocketException catch (_) {
|
} on SocketException catch (_) {
|
||||||
response = await doPost();
|
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||||
} on TimeoutException catch (_) {
|
if (attempts >= 3) {
|
||||||
response = await doPost();
|
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||||
|
return 'no_internet';
|
||||||
}
|
}
|
||||||
|
await Future.delayed(Duration(seconds: attempts));
|
||||||
|
} on TimeoutException catch (_) {
|
||||||
|
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||||
|
if (attempts >= 3) return 'failure';
|
||||||
|
} catch (e) {
|
||||||
|
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addError(
|
||||||
|
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
|
||||||
|
return 'failure';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) return 'failure';
|
||||||
|
|
||||||
final sc = response.statusCode;
|
final sc = response.statusCode;
|
||||||
final body = response.body;
|
final body = response.body;
|
||||||
Log.print('request: ${response.request}');
|
Log.print('request: ${response.request}');
|
||||||
Log.print('body: $body');
|
Log.print('body: $body');
|
||||||
// Log.print('link: $link');
|
|
||||||
Log.print('headers: $headers');
|
|
||||||
Log.print('payload: $payload');
|
Log.print('payload: $payload');
|
||||||
|
|
||||||
// 2xx
|
|
||||||
if (sc >= 200 && sc < 300) {
|
if (sc >= 200 && sc < 300) {
|
||||||
try {
|
try {
|
||||||
final jsonData = jsonDecode(body);
|
return jsonDecode(body);
|
||||||
return jsonData;
|
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
addError('JSON Decode Error', 'Body: $body\n$st',
|
addError('JSON Decode Error', 'Body: $body\n$st',
|
||||||
'CRUD._makeRequest $link');
|
'CRUD._makeRequest $link');
|
||||||
@@ -157,120 +180,56 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 401 → تجديد التوكن تلقائياً
|
|
||||||
if (sc == 401) {
|
if (sc == 401) {
|
||||||
|
final isNonCritical = link.contains('errorApp.php');
|
||||||
|
if (!_isRefreshingJWT && !isNonCritical) {
|
||||||
|
_isRefreshingJWT = true;
|
||||||
|
try {
|
||||||
await Get.put(LoginController()).getJWT();
|
await Get.put(LoginController()).getJWT();
|
||||||
|
} finally {
|
||||||
|
_isRefreshingJWT = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return 'token_expired';
|
return 'token_expired';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5xx
|
|
||||||
if (sc >= 500) {
|
if (sc >= 500) {
|
||||||
addError(
|
addError(
|
||||||
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
||||||
return 'failure';
|
return 'failure';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4xx أخرى
|
|
||||||
return 'failure';
|
return 'failure';
|
||||||
} on SocketException {
|
|
||||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
|
||||||
return 'no_internet';
|
|
||||||
} on TimeoutException {
|
|
||||||
return 'failure';
|
|
||||||
} catch (e, st) {
|
|
||||||
addError('HTTP Request Exception: $e', 'Stack: $st',
|
|
||||||
'CRUD._makeRequest $link');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// post — طلب POST عادي للراكب/السائق
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
|
||||||
// التغيير: إضافة X-Device-FP header
|
|
||||||
// القيمة: fp_encrypted من GetStorage
|
|
||||||
// السيرفر يتحقق: sha256(fp_encrypted + FP_PEPPER) == JWT.fingerPrint
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
Future<dynamic> post({
|
Future<dynamic> post({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
final token = await _getJwt();
|
String token = await _getJwt();
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return await _makeRequest(
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
link: link,
|
|
||||||
payload: payload,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// get — طلب GET للراكب/السائق (يستخدم POST method)
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
|
||||||
// التغيير: إضافة X-Device-FP header
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
Future<dynamic> get({
|
Future<dynamic> get({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
final token = await _getJwt();
|
String token = await _getJwt();
|
||||||
var url = Uri.parse(link);
|
|
||||||
var response = await _client.post(
|
final headers = {
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Log.print('request: ${response.request}');
|
|
||||||
Log.print('body: ${response.body}');
|
|
||||||
Log.print('payload: $payload');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.body;
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['error'] == 'Token expired') {
|
|
||||||
print("CRUD.get: Token expired, refreshing and retrying once...");
|
|
||||||
await Get.put(LoginController()).getJWT();
|
|
||||||
|
|
||||||
// إعادة المحاولة مرة واحدة فقط بتوكن جديد
|
|
||||||
var retryResponse = await _client.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization':
|
|
||||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
|
||||||
'X-Device-FP': _getFpHeader(),
|
'X-Device-FP': _getFpHeader(),
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
if (retryResponse.statusCode == 200) {
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
return retryResponse.body;
|
|
||||||
}
|
|
||||||
return jsonEncode(
|
|
||||||
{'status': 'failure', 'message': 'token_expired_retry_failed'});
|
|
||||||
} else {
|
|
||||||
return jsonEncode({'status': 'failure', 'message': '401_unauthorized'});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addError('Non-200 response code: ${response.statusCode}',
|
|
||||||
'crud().get - Other', url.toString());
|
|
||||||
return jsonEncode({
|
|
||||||
'status': 'failure',
|
|
||||||
'message': 'server_error_${response.statusCode}'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
@@ -290,65 +249,30 @@ class CRUD {
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $jwt',
|
'Authorization': 'Bearer $jwt',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(),
|
||||||
};
|
};
|
||||||
// add print debug
|
|
||||||
Log.print('headers: $headers');
|
Log.print('headers: $headers');
|
||||||
Log.print('payload: $payload');
|
Log.print('payload: $payload');
|
||||||
Log.print('link: $link');
|
Log.print('link: $link');
|
||||||
|
|
||||||
return await _makeRequest(
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
link: link,
|
|
||||||
payload: payload,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// getWallet — طلب GET لسيرفر المدفوعات (يستخدم POST method)
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
|
||||||
// التغيير: إضافة X-Device-FP header
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
Future<dynamic> getWallet({
|
Future<dynamic> getWallet({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
var s = await LoginController().getJwtWallet();
|
var s = await LoginController().getJwtWallet();
|
||||||
final hmac = box.read(BoxName.hmac);
|
final hmac = box.read(BoxName.hmac);
|
||||||
var url = Uri.parse(link);
|
|
||||||
|
|
||||||
var response = await _client.post(
|
final headers = {
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $s',
|
'Authorization': 'Bearer $s',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(),
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
return jsonData['status'];
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['error'] == 'Token expired') {
|
|
||||||
await Get.put(LoginController()).getJwtWallet();
|
|
||||||
return 'token_expired';
|
|
||||||
} else {
|
|
||||||
addError('Unauthorized: ${jsonData['error']}', 'crud().getWallet - 401',
|
|
||||||
url.toString());
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addError('Non-200 response code: ${response.statusCode}',
|
|
||||||
'crud().getWallet - Other', url.toString());
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
@@ -361,65 +285,20 @@ class CRUD {
|
|||||||
{required String link, Map<String, dynamic>? payload}) async {
|
{required String link, Map<String, dynamic>? payload}) async {
|
||||||
final s = await LoginController().getJwtWallet();
|
final s = await LoginController().getJwtWallet();
|
||||||
final hmac = box.read(BoxName.hmac);
|
final hmac = box.read(BoxName.hmac);
|
||||||
final url = Uri.parse(link);
|
|
||||||
|
|
||||||
try {
|
final headers = {
|
||||||
final response = await _client.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $s',
|
'Authorization': 'Bearer $s',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(),
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> wrap(String status, {Object? message, int? code}) {
|
final result = await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
return {
|
if (result is Map || result is List) return result;
|
||||||
'status': status,
|
if (result == 'no_internet') {
|
||||||
'message': message,
|
return {'status': 'failure', 'message': 'no_internet', 'code': -1};
|
||||||
'code': code ?? response.statusCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
try {
|
|
||||||
return jsonDecode(response.body);
|
|
||||||
} catch (e) {
|
|
||||||
return wrap('failure',
|
|
||||||
message: 'JSON decode error', code: response.statusCode);
|
|
||||||
}
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
try {
|
|
||||||
final jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData is Map && jsonData['error'] == 'Token expired') {
|
|
||||||
await Get.put(LoginController()).getJWT();
|
|
||||||
return {
|
|
||||||
'status': 'failure',
|
|
||||||
'message': 'token_expired',
|
|
||||||
'code': 401
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return wrap('failure', message: jsonData);
|
|
||||||
} catch (_) {
|
|
||||||
return wrap('failure', message: response.body);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
final jsonData = jsonDecode(response.body);
|
|
||||||
return wrap('failure', message: jsonData);
|
|
||||||
} catch (_) {
|
|
||||||
return wrap('failure', message: response.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
'status': 'failure',
|
|
||||||
'message': 'HTTP request error: $e',
|
|
||||||
'code': -1
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendWhatsAppAuth(String to, String token) async {
|
Future sendWhatsAppAuth(String to, String token) async {
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ class SecurityHelper {
|
|||||||
/// Deletes all app data
|
/// Deletes all app data
|
||||||
static void _showSecurityWarning() {
|
static void _showSecurityWarning() {
|
||||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||||
RxInt secondsRemaining = 10.obs;
|
final secondsRemaining = RxInt(10);
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
CupertinoAlertDialog(
|
CupertinoAlertDialog(
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class SecurityChecks {
|
|||||||
return result;
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
Log.print("Failed to check security status: ${e.message}");
|
Log.print("Failed to check security status: ${e.message}");
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
return false; // Platform not supported → treat as secure (not compromised)
|
||||||
|
} catch (e) {
|
||||||
|
Log.print("Security check error: $e");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -29,7 +29,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user