From 6760fe24372cd8a868a0f02426e94d1b33648bfc Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 25 Apr 2023 23:03:52 +0600 Subject: [PATCH 1/3] chore: add flutter_secure_storage and configure --- .github/workflows/spotube-release-binary.yml | 2 +- CONTRIBUTION.md | 6 +-- android/app/src/main/AndroidManifest.xml | 35 +++++++++++--- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + linux/packaging/deb/make_config.yaml | 2 + linux/packaging/rpm/make_config.yaml | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Runner/DebugProfile.entitlements | 24 +++++----- macos/Runner/Release.entitlements | 20 ++++---- pubspec.lock | 48 +++++++++++++++++++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 ++ windows/flutter/generated_plugins.cmake | 1 + 14 files changed, 120 insertions(+), 31 deletions(-) diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index 92e88f60..327eeb18 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -99,7 +99,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update -y - sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libunwind-dev locate patchelf + sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libunwind-dev locate patchelf libsecret-1-0 libjsoncpp1 libsecret-1-dev libjsoncpp-dev - name: Install AppImage Tool run: | diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 125d1cae..9f9f7bc0 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -120,9 +120,9 @@ Do the following: - Download the latest Flutter SDK (>=2.15.1) & enable desktop support - Install Development dependencies in linux - - `libgstreamer1.0-dev` & `libgstreamer-plugins-base1.0-dev` (for Debian/Ubuntu) - - `gstreamer`, `gst-libav`, `gst-plugins-base` & `gst-plugins-good` (for Arch/Manjaro) - - `gstreamer1-devel gstreamer1-plugins-base-tools gstreamer1-doc gstreamer1-plugins-base-devel gstreamer1-plugins-good gstreamer1-plugins-good-extras` (for Fedora) + - `libsecret-1-0 libjsoncpp1 libsecret-1-dev libjsoncpp-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev` (for Debian/Ubuntu) + - `libsecret jsoncpp gstreamer gst-libav gst-plugins-base gst-plugins-good` (for Arch/Manjaro) + - `libsecret libsecret-devel jsoncpp gstreamer1-devel gstreamer1-plugins-base-tools gstreamer1-doc gstreamer1-plugins-base-devel gstreamer1-plugins-good gstreamer1-plugins-good-extras` (for Fedora) - Clone the Repo & Run `flutter pub get` in the Terminal - Create a `secrets.json` in root of the project. The structure should be similar to the following example: diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dbb1709e..523f66b9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,13 +14,34 @@ - - - - + + + + diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 9b97ba1c..aa4a749d 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) catcher_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "CatcherPlugin"); catcher_plugin_register_with_registrar(catcher_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index bb62b362..1b330ca5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux catcher + flutter_secure_storage_linux screen_retriever url_launcher_linux window_manager diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index 63fa3b07..27dbd3d4 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -12,6 +12,8 @@ installed_size: 24400 dependencies: - libgstreamer1.0-dev - libgstreamer-plugins-base1.0-dev + - libsecret-1-0 + - libjsoncpp1 essential: false icon: assets/spotube-logo.png diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index f60e3509..60e37a55 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -11,6 +11,8 @@ requires: - gstreamer1-plugins-base-tools - gstreamer1-plugins-base-devel - gstreamer1-plugins-good + - jsoncpp + - libsecret display_name: Spotube diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5b79119c..f5ba69bb 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import audio_session import audioplayers_darwin import catcher import device_info_plus +import flutter_secure_storage_macos import macos_ui import package_info_plus import path_provider_foundation @@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index 3ba6c126..e016b40d 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -1,14 +1,16 @@ - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.client - - com.apple.security.network.server - - - + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + + \ No newline at end of file diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 7a2230dc..cdf193e5 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -1,12 +1,14 @@ - - com.apple.security.app-sandbox - - com.apple.security.network.client - - com.apple.security.network.server - - - + + com.apple.security.app-sandbox + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + + \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index f1e24c15..f2572733 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -708,6 +708,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.72.1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "083add01847fc1c80a07a08e1ed6927e9acd9618a35e330239d4422cd2a58c50" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: b3773190e385a3c8a382007893d678ae95462b3c2279e987b55d140d3b0cb81b + url: "https://pub.dev" + source: hosted + version: "1.0.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "42938e70d4b872e856e678c423cc0e9065d7d294f45bc41fc1981a4eb4beaffe" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee + url: "https://pub.dev" + source: hosted + version: "2.0.0" flutter_svg: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index fd1eb273..0287943c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: flutter_hooks: ^0.18.2+1 flutter_inappwebview: ^5.7.2+3 flutter_riverpod: ^2.1.1 + flutter_secure_storage: ^8.0.0 flutter_svg: ^1.1.6 fuzzywuzzy: ^0.2.0 go_router: ^6.0.2 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4215e258..552e8d72 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); CatcherPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("CatcherPlugin")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 8633ddfa..aedb0000 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows catcher + flutter_secure_storage_windows permission_handler_windows screen_retriever url_launcher_windows From b110d834561ac53129ac9ec80238c014c84832ec Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 25 Apr 2023 23:29:54 +0600 Subject: [PATCH 2/3] feat: cache encryption for sensitive data --- lib/provider/authentication_provider.dart | 2 +- lib/utils/persisted_state_notifier.dart | 54 +++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/lib/provider/authentication_provider.dart b/lib/provider/authentication_provider.dart index 057ddda2..c09cb199 100644 --- a/lib/provider/authentication_provider.dart +++ b/lib/provider/authentication_provider.dart @@ -79,7 +79,7 @@ class AuthenticationNotifier bool get isLoggedIn => state != null; - AuthenticationNotifier() : super(null, "authentication"); + AuthenticationNotifier() : super(null, "authentication", encrypted: true); Timer? _refreshTimer; diff --git a/lib/utils/persisted_state_notifier.dart b/lib/utils/persisted_state_notifier.dart index 78219051..f509f439 100644 --- a/lib/utils/persisted_state_notifier.dart +++ b/lib/utils/persisted_state_notifier.dart @@ -1,22 +1,70 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; +import 'package:spotube/utils/primitive_utils.dart'; + +const secureStorage = FlutterSecureStorage(); abstract class PersistedStateNotifier extends StateNotifier { final String cacheKey; + final bool encrypted; FutureOr onInit() {} PersistedStateNotifier( super.state, - this.cacheKey, - ) { + this.cacheKey, { + this.encrypted = false, + }) { _load().then((_) => onInit()); } Future _load() async { - final box = await Hive.openLazyBox("spotube_cache"); + final LazyBox box; + + if (encrypted) { + String? boxName = + await secureStorage.read(key: "oss.krtirtho.spotube.box_name"); + + if (boxName == null) { + await secureStorage.write( + key: "oss.krtirtho.spotube.box_name", + value: ".spotube-${PrimitiveUtils.uuid.v4()}", + ); + boxName = + await secureStorage.read(key: "oss.krtirtho.spotube.box_name"); + } else { + boxName = ".spotube-$boxName"; + } + + final rawKey = + await secureStorage.read(key: "oss.krtirtho.spotube.$boxName"); + + Uint8List? encryptionKey = + rawKey == null ? null : base64Url.decode(rawKey); + + if (encryptionKey == null) { + await secureStorage.write( + key: "oss.krtirtho.spotube.$boxName", + value: base64UrlEncode(Hive.generateSecureKey()), + ); + encryptionKey = base64Url.decode( + (await secureStorage.read(key: "oss.krtirtho.spotube.$boxName"))!, + ); + } + + box = await Hive.openLazyBox( + boxName!, + encryptionCipher: HiveAesCipher(encryptionKey), + ); + } else { + box = await Hive.openLazyBox("spotube_cache"); + } + final json = await box.get(cacheKey); if (json != null) { From dca8bcd7c673764714723a0e6b83117e834806d5 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 26 Apr 2023 00:35:32 +0600 Subject: [PATCH 3/3] chore: initialize boxes in main --- lib/main.dart | 8 ++- lib/utils/persisted_state_notifier.dart | 77 ++++++++++++------------- pubspec.yaml | 2 +- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 81c0ec94..a9d18b57 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,9 +27,11 @@ import 'package:spotube/services/audio_player.dart'; import 'package:spotube/services/pocketbase.dart'; import 'package:spotube/services/youtube.dart'; import 'package:spotube/themes/light_theme.dart'; +import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; import 'package:window_size/window_size.dart'; +import 'package:path_provider/path_provider.dart'; void main(List rawArgs) async { final parser = ArgParser(); @@ -70,7 +72,11 @@ void main(List rawArgs) async { WidgetsFlutterBinding.ensureInitialized(); MetadataGod.initialize(); - await QueryClient.initialize(cachePrefix: "oss.krtirtho.spotube"); + await QueryClient.initialize( + cachePrefix: "oss.krtirtho.spotube", + cacheDir: (await getApplicationSupportDirectory()).path, + ); + await PersistedStateNotifier.initializeBoxes(); Hive.registerAdapter(CacheTrackAdapter()); Hive.registerAdapter(CacheTrackEngagementAdapter()); Hive.registerAdapter(CacheTrackSkipSegmentAdapter()); diff --git a/lib/utils/persisted_state_notifier.dart b/lib/utils/persisted_state_notifier.dart index f509f439..60b8d2fd 100644 --- a/lib/utils/persisted_state_notifier.dart +++ b/lib/utils/persisted_state_notifier.dart @@ -1,13 +1,19 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:spotube/utils/primitive_utils.dart'; -const secureStorage = FlutterSecureStorage(); +const secureStorage = FlutterSecureStorage( + aOptions: AndroidOptions( + encryptedSharedPreferences: true, + ), +); + +const kKeyBoxName = "spotube_box_name"; +String getBoxKey(String boxName) => "spotube_box_$boxName"; abstract class PersistedStateNotifier extends StateNotifier { final String cacheKey; @@ -23,48 +29,38 @@ abstract class PersistedStateNotifier extends StateNotifier { _load().then((_) => onInit()); } - Future _load() async { - final LazyBox box; + static late LazyBox _box; + static late LazyBox _encryptedBox; - if (encrypted) { - String? boxName = - await secureStorage.read(key: "oss.krtirtho.spotube.box_name"); + static Future initializeBoxes() async { + String? boxName = await secureStorage.read(key: kKeyBoxName); - if (boxName == null) { - await secureStorage.write( - key: "oss.krtirtho.spotube.box_name", - value: ".spotube-${PrimitiveUtils.uuid.v4()}", - ); - boxName = - await secureStorage.read(key: "oss.krtirtho.spotube.box_name"); - } else { - boxName = ".spotube-$boxName"; - } - - final rawKey = - await secureStorage.read(key: "oss.krtirtho.spotube.$boxName"); - - Uint8List? encryptionKey = - rawKey == null ? null : base64Url.decode(rawKey); - - if (encryptionKey == null) { - await secureStorage.write( - key: "oss.krtirtho.spotube.$boxName", - value: base64UrlEncode(Hive.generateSecureKey()), - ); - encryptionKey = base64Url.decode( - (await secureStorage.read(key: "oss.krtirtho.spotube.$boxName"))!, - ); - } - - box = await Hive.openLazyBox( - boxName!, - encryptionCipher: HiveAesCipher(encryptionKey), - ); - } else { - box = await Hive.openLazyBox("spotube_cache"); + if (boxName == null) { + boxName = "spotube-${PrimitiveUtils.uuid.v4()}"; + await secureStorage.write(key: kKeyBoxName, value: boxName); } + String? encryptionKey = await secureStorage.read(key: getBoxKey(boxName)); + + if (encryptionKey == null) { + encryptionKey = base64Url.encode(Hive.generateSecureKey()); + await secureStorage.write( + key: getBoxKey(boxName), + value: encryptionKey, + ); + } + + _encryptedBox = await Hive.openLazyBox( + boxName, + encryptionCipher: HiveAesCipher(base64Url.decode(encryptionKey)), + ); + + _box = await Hive.openLazyBox("spotube_cache"); + } + + LazyBox get box => encrypted ? _encryptedBox : _box; + + Future _load() async { final json = await box.get(cacheKey); if (json != null) { @@ -95,7 +91,6 @@ abstract class PersistedStateNotifier extends StateNotifier { } void save() async { - final box = await Hive.openLazyBox("spotube_cache"); box.put(cacheKey, toJson()); } diff --git a/pubspec.yaml b/pubspec.yaml index 0287943c..f17e8ca1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: file_picker: ^5.2.2 fl_query: ^1.0.0-alpha.2 fl_query_hooks: ^1.0.0-alpha.2 - fluent_ui: ^4.3.0 + fluent_ui: 4.3.0 fluentui_system_icons: ^1.1.189 flutter: sdk: flutter