Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
21 changes: 21 additions & 0 deletions packages/login_client_flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,33 @@
## Usage

```dart
// Default usage
final loginClient = LoginClient(
credentialsStorage: const FlutterSecureCredentialsStorage(),
// ...
);
```

### Custom Configuration

You can provide a custom `FlutterSecureStorage` instance to configure platform-specific options:

```dart
final loginClient = LoginClient(
credentialsStorage: const FlutterSecureCredentialsStorage(
storage: FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
iOptions: IOSOptions(
accessibility: KeychainAccessibility.first_unlock_this_device,
),
),
),
// ...
);
```

## Android `javax.crypto.BadPaddingException`

Exclude Flutter Secure Storage from Android full backup.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,22 @@ import 'package:login_client/login_client.dart';
/// A `flutter_secure_storage` implementation of the [CredentialsStorage].
class FlutterSecureCredentialsStorage implements CredentialsStorage {
/// Creates the [CredentialsStorage].
const FlutterSecureCredentialsStorage();
///
/// The optional [storage] parameter allows you to provide a custom
/// [FlutterSecureStorage] instance with specific configuration options.
/// If not provided, a default instance will be used.
const FlutterSecureCredentialsStorage({FlutterSecureStorage? storage})
: _storage = storage;

static const _key = 'login_client_flutter_credentials';
FlutterSecureStorage get _storage => const FlutterSecureStorage();
final FlutterSecureStorage? _storage;

FlutterSecureStorage get _secureStorage =>
_storage ?? const FlutterSecureStorage();

@override
Future<Credentials?> read() async {
final json = await _storage.read(key: _key);
final json = await _secureStorage.read(key: _key);
if (json == null) {
return null;
}
Expand All @@ -39,11 +47,11 @@ class FlutterSecureCredentialsStorage implements CredentialsStorage {

@override
Future<void> save(Credentials credentials) {
return _storage.write(key: _key, value: credentials.toJson());
return _secureStorage.write(key: _key, value: credentials.toJson());
}

@override
Future<void> clear() {
return _storage.delete(key: _key);
return _secureStorage.delete(key: _key);
}
}
2 changes: 2 additions & 0 deletions packages/login_client_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ dependencies:
login_client: ^3.0.0

dev_dependencies:
flutter_test:
sdk: flutter
custom_lint: ^0.6.4
leancode_lint: ^12.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:login_client_flutter/login_client_flutter.dart';
import 'package:oauth2/oauth2.dart';

// Mock FlutterSecureStorage for testing
class MockFlutterSecureStorage extends FlutterSecureStorage {
final Map<String, String> _storage = {};

const MockFlutterSecureStorage() : super();

@override
Future<String?> read({required String key}) async {
return _storage[key];
}

@override
Future<void> write({required String key, required String value}) async {
_storage[key] = value;
}

@override
Future<void> delete({required String key}) async {
_storage.remove(key);
}
}

void main() {
group('FlutterSecureCredentialsStorage', () {
test('uses default storage when none provided', () {
const storage = FlutterSecureCredentialsStorage();

// This test ensures the constructor works with no parameters
expect(storage, isA<FlutterSecureCredentialsStorage>());
});

test('accepts custom storage instance', () {
const customStorage = MockFlutterSecureStorage();
const storage = FlutterSecureCredentialsStorage(storage: customStorage);

// This test ensures the constructor works with custom storage
expect(storage, isA<FlutterSecureCredentialsStorage>());
});

test('works with custom storage - read/write/clear operations', () async {
const customStorage = MockFlutterSecureStorage();
const storage = FlutterSecureCredentialsStorage(storage: customStorage);

// Test initial read returns null
expect(await storage.read(), null);

// Test saving and reading credentials
final credentials = Credentials('test_token');
await storage.save(credentials);

final readCredentials = await storage.read();
expect(readCredentials?.accessToken, equals('test_token'));

// Test clearing
await storage.clear();
expect(await storage.read(), null);
});

test('maintains backward compatibility with default constructor', () async {
// This test verifies that existing code continues to work
const storage = FlutterSecureCredentialsStorage();

// Should be able to perform operations without error
expect(storage.read(), completes);
});
});
}