Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ jobs:
- name: Install dependencies
run: flutter pub get

- name: Create secrets.env file
env:
SECRETS_ENV_BASE64: ${{ secrets.SECRETS_ENV_BASE64 }}
run: echo "$SECRETS_ENV_BASE64" | base64 --decode > secrets.env

- name: Generate local code
run: flutter pub run build_runner build --delete-conflicting-outputs

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ jobs:
run: flutter pub get

- name: Generate local code
run: flutter pub run build_runner build --delete-conflicting-outputs
run: dart run build_runner build --delete-conflicting-outputs

- name: Create secrets.env
run: echo "${{ secrets.SECRETS_ENV_BASE64 }}" | base64 --decode > secrets.env

- name: Generate Firebase Options
env:
Expand Down
2 changes: 2 additions & 0 deletions lib/core/constants/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
22 changes: 15 additions & 7 deletions lib/core/di/service_di.dart
Original file line number Diff line number Diff line change
@@ -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<FirebaseService>(() async {
final service = FirebaseService();
await service.init();
return service;
});

getIt.registerSingletonAsync<SecureStorage>(
() async => SecureStorage(storage: FlutterSecureStorage())
);
getIt.registerSingletonAsync<ProtectionService>(
() async => ProtectionService( secureStorage: getIt<SecureStorage>()),
dependsOn: [SecureStorage],
);
getIt.registerSingletonAsync<AppSecrets>(
() async => AppSecrets(
firebaseRemoteConfig: getIt<FirebaseService>().remoteConfig,
),
() async =>
AppSecrets(firebaseRemoteConfig: getIt<FirebaseService>().remoteConfig),
dependsOn: [FirebaseService],
);

getIt.registerSingletonAsync<SupabaseService>(
() async => SupabaseService(appSecrets: getIt<AppSecrets>()),
() async => SupabaseService(appSecrets: getIt<AppSecrets>()),
dependsOn: [AppSecrets],
);
}
}
84 changes: 84 additions & 0 deletions lib/core/security/protection_service.dart
Original file line number Diff line number Diff line change
@@ -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<void> init() {
final completer = Completer<void>();
const securityWindow = Duration(seconds: 2);

Future<void> 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;
}
}
5 changes: 5 additions & 0 deletions lib/core/service/supabase_service.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -19,6 +21,9 @@ class SupabaseService {
url: url,
anonKey: anonKey,
debug: kDebugMode,
authOptions: FlutterAuthClientOptions(
localStorage: SecureStorage(storage: FlutterSecureStorage()),
),
);

_supabaseClient = supabase.client;
Expand Down
33 changes: 33 additions & 0 deletions lib/data/repository/secure_storage.dart
Original file line number Diff line number Diff line change
@@ -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<void> initialize() async {}

@override
Future<String?> accessToken() async {
return storage.read(key: supabasePersistSessionKey);
}

@override
Future<bool> hasAccessToken() async {
return storage.containsKey(key: supabasePersistSessionKey);
}

@override
Future<void> persistSession(String persistSessionString) async {
return storage.write(
key: supabasePersistSessionKey, value: persistSessionString);
}

@override
Future<void> removePersistedSession() async {
return storage.delete(key: supabasePersistSessionKey);
}
}
5 changes: 5 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -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}');
Expand All @@ -13,7 +16,9 @@ void main() async {
}
});
initDI();

await getIt.allReady();
await getIt<ProtectionService>().init();

runApp(const MoneyApp());
}
5 changes: 5 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ dependencies:
pdf: ^3.11.3

# --- Utils & Internationalization ---

intl: any
uuid: ^4.5.1
firebase_core: ^4.4.0
firebase_crashlytics: ^5.0.7
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
Expand All @@ -72,6 +76,7 @@ flutter:
assets:
- assets/icons/
- assets/images/
- secrets.env

uses-material-design: true

Expand Down