diff --git a/lib/core/constants/app_constants.dart b/lib/core/constants/app_constants.dart index 8e3ab8d..36e01bf 100644 --- a/lib/core/constants/app_constants.dart +++ b/lib/core/constants/app_constants.dart @@ -11,4 +11,6 @@ class AppConstants { static const String googleWebClientId = "GOOGLE_WEB_CLIENT_ID"; static const String googleIosClientId = "GOOGLE_IOS_CLIENT_ID"; + static const String hashedSignature = "TALSEC_SIGNING_CERT_HASH"; + static const String watcherMail = "TALSEC_WATCHER_MAIL"; } diff --git a/lib/core/di/service_di.dart b/lib/core/di/service_di.dart index 8622e52..5053018 100644 --- a/lib/core/di/service_di.dart +++ b/lib/core/di/service_di.dart @@ -1,25 +1,33 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:moneyplus/core/security/app_secrets.dart'; +import 'package:moneyplus/data/repository/secure_storage.dart'; +import '../security/protection_service.dart'; import '../service/firebase_service.dart'; import '../service/supabase_service.dart'; import 'injection.dart'; -void initServiceDI() { +void initServiceDI() { getIt.registerSingletonAsync(() async { final service = FirebaseService(); await service.init(); return service; }); - + getIt.registerSingletonAsync( + () async => SecureStorage(storage: FlutterSecureStorage()) + ); + getIt.registerSingletonAsync( + () async => ProtectionService( secureStorage: getIt()), + dependsOn: [SecureStorage], + ); getIt.registerSingletonAsync( - () async => AppSecrets( - firebaseRemoteConfig: getIt().remoteConfig, - ), + () async => + AppSecrets(firebaseRemoteConfig: getIt().remoteConfig), dependsOn: [FirebaseService], ); getIt.registerSingletonAsync( - () async => SupabaseService(appSecrets: getIt()), + () async => SupabaseService(appSecrets: getIt()), dependsOn: [AppSecrets], ); -} \ No newline at end of file +} diff --git a/lib/core/security/protection_service.dart b/lib/core/security/protection_service.dart new file mode 100644 index 0000000..5a38370 --- /dev/null +++ b/lib/core/security/protection_service.dart @@ -0,0 +1,84 @@ + +import 'dart:async'; +import 'dart:io'; + +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:freerasp/freerasp.dart'; + +import '../../data/repository/secure_storage.dart'; +import '../constants/app_constants.dart'; + +class ProtectionService { + SecureStorage secureStorage; + + ProtectionService({required this.secureStorage}); + + Future init() { + final completer = Completer(); + const securityWindow = Duration(seconds: 2); + + Future handleThreat(String threatType) async { + if (kDebugMode) { + print("CRITICAL: Security threat detected! Type: $threatType. Terminating application."); + } + await FirebaseCrashlytics.instance.recordError( + Exception('Security Threat: $threatType'), + StackTrace.current, + reason: 'A security threat ($threatType) was detected by freerasp.', + fatal: true, + ); + + if (!kDebugMode) { + exit(0); + } + } + + final signingHash = dotenv.env[AppConstants.hashedSignature]; + final watcherMail = dotenv.env[AppConstants.watcherMail]; + final supportedStoresValue = dotenv.env['TALSEC_SUPPORTED_STORES']; + final supportedStores = supportedStoresValue?.split(',') ?? []; + + final config = TalsecConfig( + androidConfig: AndroidConfig( + packageName: 'com.example.checkout_flutter_ecommerce', + signingCertHashes: [if (signingHash != null) signingHash], + supportedStores: supportedStores, + ), + iosConfig: IOSConfig( + bundleIds: ['com.example.checkoutFlutterEcommerce'], + teamId: 'YOUR_TEAM_ID', + ), + watcherMail: watcherMail ?? "", + isProd: true, + ); + + final callback = ThreatCallback( + onAppIntegrity: () => handleThreat('AppIntegrity'), + onObfuscationIssues: () => handleThreat('ObfuscationIssues'), + onDebug: () => handleThreat('Debug'), + onDeviceBinding: () => handleThreat('DeviceBinding'), + onHooks: () => handleThreat('Hooks'), + onPrivilegedAccess: () => handleThreat('PrivilegedAccess'), + onSimulator: () => handleThreat('Simulator'), + //onUnofficialStore: handleThreat, // + onMultiInstance: () => handleThreat('MultiInstance'), + ); + + Talsec.instance.attachListener(callback); + + Talsec.instance.start(config); + Timer(securityWindow, () { + if (!completer.isCompleted) { + if (kDebugMode) { + debugPrint("Security window passed without threats."); + } + completer.complete(); + } + }); + + return completer.future; + } +} \ No newline at end of file diff --git a/lib/core/service/supabase_service.dart b/lib/core/service/supabase_service.dart index f6ddb78..9ab68dd 100644 --- a/lib/core/service/supabase_service.dart +++ b/lib/core/service/supabase_service.dart @@ -1,4 +1,6 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:moneyplus/data/repository/secure_storage.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../security/app_secrets.dart'; @@ -19,6 +21,9 @@ class SupabaseService { url: url, anonKey: anonKey, debug: kDebugMode, + authOptions: FlutterAuthClientOptions( + localStorage: SecureStorage(storage: FlutterSecureStorage()), + ), ); _supabaseClient = supabase.client; diff --git a/lib/data/repository/secure_storage.dart b/lib/data/repository/secure_storage.dart new file mode 100644 index 0000000..6b57f64 --- /dev/null +++ b/lib/data/repository/secure_storage.dart @@ -0,0 +1,33 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class SecureStorage extends LocalStorage { + + final FlutterSecureStorage storage; + + SecureStorage({required this.storage}); + + @override + Future initialize() async {} + + @override + Future accessToken() async { + return storage.read(key: supabasePersistSessionKey); + } + + @override + Future hasAccessToken() async { + return storage.containsKey(key: supabasePersistSessionKey); + } + + @override + Future persistSession(String persistSessionString) async { + return storage.write( + key: supabasePersistSessionKey, value: persistSessionString); + } + + @override + Future removePersistedSession() async { + return storage.delete(key: supabasePersistSessionKey); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index f0393d0..f9845a4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:logging/logging.dart'; +import 'package:moneyplus/core/security/protection_service.dart'; import 'package:moneyplus/money_app.dart'; import 'core/di/injection.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: "secrets.env"); Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { debugPrint('${record.level.name}: ${record.time}: ${record.message}'); @@ -13,7 +16,9 @@ void main() async { } }); initDI(); + await getIt.allReady(); + await getIt().init(); runApp(const MoneyApp()); } diff --git a/pubspec.yaml b/pubspec.yaml index c02f8bc..6bd8569 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: pdf: ^3.11.3 # --- Utils & Internationalization --- + intl: any uuid: ^4.5.1 firebase_core: ^4.4.0 @@ -49,7 +50,10 @@ dependencies: firebase_remote_config: ^6.1.4 firebase_analytics: ^12.1.1 firebase_performance: ^0.11.1+4 + flutter_secure_storage: ^10.0.0 + # --- Security --- + freerasp: ^7.3.0 dev_dependencies: flutter_test: sdk: flutter @@ -64,6 +68,7 @@ flutter: assets: - assets/icons/ - assets/images/ + - secrets.env uses-material-design: true