From 565ddb090f7a1fdd173f4dde6fcb3f6eb98cb530 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 28 Jun 2025 14:57:56 -0700 Subject: [PATCH] Implement password protection --- ios/Podfile.lock | 13 ++++++ lib/l10n/app_en.arb | 2 + lib/main_screen.dart | 25 ++++++----- lib/password_service.dart | 57 +++++++++++++++++++++++++ lib/settings_screen.dart | 33 +++++++++++++++ pubspec.lock | 88 +++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 7 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 lib/password_service.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 309e13c..e1b2ad1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -95,6 +95,8 @@ PODS: - flutter_background_geolocation (4.16.11): - CocoaLumberjack (~> 3.8.5) - Flutter + - flutter_secure_storage (6.0.0): + - Flutter - GoogleAppMeasurement (11.13.0): - GoogleAppMeasurement/AdIdSupport (= 11.13.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.1) @@ -153,6 +155,9 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) @@ -173,7 +178,9 @@ DEPENDENCIES: - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) - flutter_background_geolocation (from `.symlinks/plugins/flutter_background_geolocation/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - quick_actions_ios (from `.symlinks/plugins/quick_actions_ios/ios`) - rate_my_app (from `.symlinks/plugins/rate_my_app/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -213,8 +220,12 @@ EXTERNAL SOURCES: :path: Flutter flutter_background_geolocation: :path: ".symlinks/plugins/flutter_background_geolocation/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/darwin" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" quick_actions_ios: :path: ".symlinks/plugins/quick_actions_ios/ios" rate_my_app: @@ -241,11 +252,13 @@ SPEC CHECKSUMS: FirebaseSessions: eaa8ec037e7793769defe4201c20bd4d976f9677 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_background_geolocation: defe705fe7c50b1be772e0d298ed3765fa17c022 + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 GoogleAppMeasurement: 0dfca1a4b534d123de3945e28f77869d10d0d600 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 quick_actions_ios: 4b07fb49d8d8f3518d7565fbb7a91014067a7d82 diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d445cfd..44afffb 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -28,7 +28,9 @@ "trackingLabel": "Continuous tracking", "motionLabel": "Active movement", "advancedLabel": "Advanced settings", + "passwordLabel": "Password", "optimizationMessage": "To ensure reliable tracking, please disable battery optimization for this app.", + "passwordError": "Wrong password", "startAction": "Start service", "stopAction": "Stop service", "sosAction": "Send SOS" diff --git a/lib/main_screen.dart b/lib/main_screen.dart index 97ef1da..c7693fc 100644 --- a/lib/main_screen.dart +++ b/lib/main_screen.dart @@ -1,6 +1,7 @@ import 'dart:developer' as developer; import 'package:flutter/material.dart'; +import 'package:traccar_client/password_service.dart'; import 'package:traccar_client/preferences.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; @@ -93,12 +94,14 @@ class _MainScreenState extends State { contentPadding: EdgeInsets.zero, title: Text(AppLocalizations.of(context)!.trackingLabel), value: trackingEnabled, - onChanged: (bool value) { - if (value) { - bg.BackgroundGeolocation.start(); - _checkBatteryOptimizations(context); - } else { - bg.BackgroundGeolocation.stop(); + onChanged: (bool value) async { + if (await PasswordService.authenticate(context) && mounted) { + if (value) { + bg.BackgroundGeolocation.start(); + _checkBatteryOptimizations(context); + } else { + bg.BackgroundGeolocation.stop(); + } } }, ), @@ -166,10 +169,12 @@ class _MainScreenState extends State { children: [ FilledButton.tonal( onPressed: () async { - await Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())); - setState(() { - stopDetection = Preferences.instance.getBool(Preferences.stopDetection); - }); + if (await PasswordService.authenticate(context) && mounted) { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())); + setState(() { + stopDetection = Preferences.instance.getBool(Preferences.stopDetection); + }); + } }, child: Text(AppLocalizations.of(context)!.settingsButton), ), diff --git a/lib/password_service.dart b/lib/password_service.dart new file mode 100644 index 0000000..45512d0 --- /dev/null +++ b/lib/password_service.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:traccar_client/l10n/app_localizations.dart'; + +class PasswordService { + static final FlutterSecureStorage _secureStorage = const FlutterSecureStorage(); + static const String _passwordKey = 'password'; + + static Future authenticate(BuildContext context) async { + if (!await _secureStorage.containsKey(key: _passwordKey)) return true; + final controller = TextEditingController(); + bool? result; + if (context.mounted) { + result = await showDialog( + context: context, + builder: (context) => AlertDialog( + content: TextField( + controller: controller, + autofocus: true, + obscureText: true, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.passwordLabel), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(AppLocalizations.of(context)!.cancelButton), + ), + TextButton( + onPressed: () async { + final password = await _secureStorage.read(key: _passwordKey); + if (context.mounted) { + Navigator.pop(context, password == controller.text); + } + }, + child: Text(AppLocalizations.of(context)!.okButton), + ), + ], + ), + ); + } + if (result != true && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.passwordError)), + ); + return false; + } + return result == true; + } + + static Future setPassword(String password) async { + if (password.isNotEmpty) { + await _secureStorage.write(key: _passwordKey, value: password); + } else { + await _secureStorage.delete(key: _passwordKey); + } + } +} diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index 8376c01..fb3de4a 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; +import 'package:traccar_client/password_service.dart'; import 'package:traccar_client/qr_code_screen.dart'; import 'package:wakelock_partial_android/wakelock_partial_android.dart'; @@ -83,6 +84,33 @@ class _SettingsScreenState extends State { } } + Future _changePassword() async { + final controller = TextEditingController(); + final result = await showDialog( + context: context, + builder: (context) => AlertDialog( + content: TextField( + controller: controller, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.passwordLabel), + obscureText: true, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(AppLocalizations.of(context)!.cancelButton), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(AppLocalizations.of(context)!.saveButton), + ), + ], + ), + ); + if (result == true) { + await PasswordService.setPassword(controller.text); + } + } + Widget _buildListTile(String title, String key, bool isInt) { String? value; if (isInt) { @@ -201,6 +229,11 @@ class _SettingsScreenState extends State { setState(() {}); }, ), + if (advanced) + ListTile( + title: Text(AppLocalizations.of(context)!.passwordLabel), + onTap: _changePassword, + ), ], ), ); diff --git a/pubspec.lock b/pubspec.lock index 1f5ef9c..9519c4d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -203,6 +203,54 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -221,6 +269,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -293,6 +349,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -514,6 +594,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + url: "https://pub.dev" + source: hosted + version: "5.14.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ca0a7ed..89591fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: shared_preferences_android: ^2.4.10 wakelock_partial_android: ^1.0.0 mobile_scanner: ^7.0.1 + flutter_secure_storage: ^9.2.4 dev_dependencies: flutter_test: