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
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,25 @@ import 'package:unleash_proxy_client_flutter/id_generator.dart';

import '../../../utils/constants.dart';

/// `encryptedSharedPreferences` is deliberately absent: v10 ignores the parameter
/// outright and migrates any Jetpack EncryptedSharedPreferences data to its own ciphers
/// on first access. If that migration throws it falls back to the old store rather than
/// discarding anything, which is why the defaults below are left alone —
/// `resetOnError: true` (erases on unrecoverable errors, matching the reasoning behind
/// [kMinimumCredentialsTTL]) and `migrateWithBackup: false`. Do **not** set
/// `migrateWithBackup: true` here: it skips the direct ESP migration in favour of a
/// backup-protected path that only runs when a cipher key-mismatch is detected, which a
/// user whose data is still in EncryptedSharedPreferences may never trigger.
///
/// `sharedPreferencesName` is deprecated in favour of `storageNamespace` and removed in
/// v11. They are **not** interchangeable: switching moves the wrapped-key prefs
/// (`FlutterSecureKeyStorage` -> `FlutterSecureKeyStorage:auth`) and the KeyStore alias
/// (suffix `.auth`) while leaving the ciphertext where it is, and nothing migrates
/// across that change. Swapping it blind would leave every Android user with data they
/// cannot decrypt, which `resetOnError` then erases. That migration needs its own
/// release, on v10, before v11 removes this parameter.
AndroidOptions _getAndroidSecureStorageOptions() => const AndroidOptions(
encryptedSharedPreferences: true, // https://github.com/juliansteenbakker/flutter_secure_storage/issues/354
// ignore: deprecated_member_use
sharedPreferencesName: 'auth',
);

Expand Down
2 changes: 1 addition & 1 deletion bccm_core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies:
extended_image: ^10.1.0
flutter_local_notifications: ^17.2.3
flutter_riverpod: ^2.5.3
flutter_secure_storage: ^9.2.2
flutter_secure_storage: ^10.3.1
freezed_annotation: ^3.1.0
graphql: ^5.2.1
graphql_flutter: ^5.3.0
Expand Down
17 changes: 14 additions & 3 deletions bccm_core/test/auth_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,20 @@ MockFlutterSecureStorage storageWithSession({String? accessToken, String? userPr
/// the app actually read — see the note on `_getIOSSecureStorageOptions`.
final kExpectedIOSOptions = const IOSOptions(accessibility: KeychainAccessibility.first_unlock_this_device).params;

/// The app's own encrypted store, not the plugin's default one. Changing either
/// value orphans every credential already on disk.
final kExpectedAndroidOptions = const AndroidOptions(encryptedSharedPreferences: true, sharedPreferencesName: 'auth').params;
/// The app's own encrypted store, not the plugin's default one. Changing this
/// orphans every credential already on disk.
///
/// Two specific ways to break it:
/// - Dropping `sharedPreferencesName` moves the data prefs from `auth` to the plugin
/// default `FlutterSecureStorage`.
/// - Swapping it for `storageNamespace` keeps the data prefs but moves the wrapped-key
/// prefs and the KeyStore alias, so the ciphertext is found and cannot be decrypted.
///
/// Neither is migrated by the plugin. `resetOnError` (on by default) then erases.
final kExpectedAndroidOptions = const AndroidOptions(
// ignore: deprecated_member_use
sharedPreferencesName: 'auth',
).params;

/// `params` hands back plain [Map]s, and Map doesn't implement value equality —
/// a `Set` of them dedupes by identity, so every element always looks distinct.
Expand Down
44 changes: 26 additions & 18 deletions bccm_core/test/auth_test.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ class _FakeWebOptions_8 extends _i1.SmartFake implements _i3.WebOptions {
: super(parent, parentInvocation);
}

class _FakeMacOsOptions_9 extends _i1.SmartFake implements _i3.MacOsOptions {
_FakeMacOsOptions_9(Object parent, Invocation parentInvocation)
class _FakeAppleOptions_9 extends _i1.SmartFake implements _i3.AppleOptions {
_FakeAppleOptions_9(Object parent, Invocation parentInvocation)
: super(parent, parentInvocation);
}

Expand Down Expand Up @@ -228,14 +228,22 @@ class MockFlutterSecureStorage extends _i1.Mock
) as _i3.WebOptions);

@override
_i3.MacOsOptions get mOptions => (super.noSuchMethod(
_i3.AppleOptions get mOptions => (super.noSuchMethod(
Invocation.getter(#mOptions),
returnValue: _FakeMacOsOptions_9(this, Invocation.getter(#mOptions)),
returnValueForMissingStub: _FakeMacOsOptions_9(
returnValue: _FakeAppleOptions_9(this, Invocation.getter(#mOptions)),
returnValueForMissingStub: _FakeAppleOptions_9(
this,
Invocation.getter(#mOptions),
),
) as _i3.MacOsOptions);
) as _i3.AppleOptions);

@override
Map<String, List<_i7.ValueChanged<String?>>> get getListeners =>
(super.noSuchMethod(
Invocation.getter(#getListeners),
returnValue: <String, List<_i7.ValueChanged<String?>>>{},
returnValueForMissingStub: <String, List<_i7.ValueChanged<String?>>>{},
) as Map<String, List<_i7.ValueChanged<String?>>>);

@override
void registerListener({
Expand Down Expand Up @@ -275,11 +283,11 @@ class MockFlutterSecureStorage extends _i1.Mock
_i6.Future<void> write({
required String? key,
required String? value,
_i3.IOSOptions? iOptions,
_i3.AppleOptions? iOptions,
_i3.AndroidOptions? aOptions,
_i3.LinuxOptions? lOptions,
_i3.WebOptions? webOptions,
_i3.MacOsOptions? mOptions,
_i3.AppleOptions? mOptions,
_i3.WindowsOptions? wOptions,
}) => (super.noSuchMethod(
Invocation.method(#write, [], {
Expand All @@ -299,11 +307,11 @@ class MockFlutterSecureStorage extends _i1.Mock
@override
_i6.Future<String?> read({
required String? key,
_i3.IOSOptions? iOptions,
_i3.AppleOptions? iOptions,
_i3.AndroidOptions? aOptions,
_i3.LinuxOptions? lOptions,
_i3.WebOptions? webOptions,
_i3.MacOsOptions? mOptions,
_i3.AppleOptions? mOptions,
_i3.WindowsOptions? wOptions,
}) => (super.noSuchMethod(
Invocation.method(#read, [], {
Expand All @@ -322,11 +330,11 @@ class MockFlutterSecureStorage extends _i1.Mock
@override
_i6.Future<bool> containsKey({
required String? key,
_i3.IOSOptions? iOptions,
_i3.AppleOptions? iOptions,
_i3.AndroidOptions? aOptions,
_i3.LinuxOptions? lOptions,
_i3.WebOptions? webOptions,
_i3.MacOsOptions? mOptions,
_i3.AppleOptions? mOptions,
_i3.WindowsOptions? wOptions,
}) => (super.noSuchMethod(
Invocation.method(#containsKey, [], {
Expand All @@ -345,11 +353,11 @@ class MockFlutterSecureStorage extends _i1.Mock
@override
_i6.Future<void> delete({
required String? key,
_i3.IOSOptions? iOptions,
_i3.AppleOptions? iOptions,
_i3.AndroidOptions? aOptions,
_i3.LinuxOptions? lOptions,
_i3.WebOptions? webOptions,
_i3.MacOsOptions? mOptions,
_i3.AppleOptions? mOptions,
_i3.WindowsOptions? wOptions,
}) => (super.noSuchMethod(
Invocation.method(#delete, [], {
Expand All @@ -367,11 +375,11 @@ class MockFlutterSecureStorage extends _i1.Mock

@override
_i6.Future<Map<String, String>> readAll({
_i3.IOSOptions? iOptions,
_i3.AppleOptions? iOptions,
_i3.AndroidOptions? aOptions,
_i3.LinuxOptions? lOptions,
_i3.WebOptions? webOptions,
_i3.MacOsOptions? mOptions,
_i3.AppleOptions? mOptions,
_i3.WindowsOptions? wOptions,
}) => (super.noSuchMethod(
Invocation.method(#readAll, [], {
Expand All @@ -390,11 +398,11 @@ class MockFlutterSecureStorage extends _i1.Mock

@override
_i6.Future<void> deleteAll({
_i3.IOSOptions? iOptions,
_i3.AppleOptions? iOptions,
_i3.AndroidOptions? aOptions,
_i3.LinuxOptions? lOptions,
_i3.WebOptions? webOptions,
_i3.MacOsOptions? mOptions,
_i3.AppleOptions? mOptions,
_i3.WindowsOptions? wOptions,
}) => (super.noSuchMethod(
Invocation.method(#deleteAll, [], {
Expand Down
Loading