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
4 changes: 2 additions & 2 deletions .github/workflows/login_client_flutter-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ jobs:
- name: Set up Dart
uses: dart-lang/setup-dart@v1
with:
sdk: 3.3
sdk: 3.7

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.19.x'
flutter-version: '3.29.x'
cache: true

- name: Publish
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/login_client_flutter-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
fail-fast: false
matrix:
include:
- version: '3.19.x'
- version: '3.29.x'

defaults:
run:
Expand Down
8 changes: 5 additions & 3 deletions packages/login_client_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Unreleased
# 3.1.0

- Bump `leancode_lint` dev dependency to `12.0.0`.
- Bump `custom_lint` dev dependency to `0.6.4`.
- Add configurable `FlutterSecureStorage` support to `FlutterSecureCredentialsStorage`. The constructor now accepts an optional `storage` parameter, allowing users to provide a custom `FlutterSecureStorage` instance with platform-specific configuration options (e.g., iOS KeyChain accessibility settings).
- Bump minimum Dart SDK version to `3.7.0`.
- Bump minimum Flutter version to `3.29.0`.
- Bump `leancode_lint` dev dependency to `16.0.0`.

# 3.0.0

Expand Down
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 @@ -16,6 +16,6 @@
///
/// See also:
/// - https://github.com/mogol/flutter_secure_storage
library login_client_flutter;
library;

export 'src/flutter_secure_credentials_storage.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:login_client/login_client.dart';

export 'package:flutter_secure_storage/flutter_secure_storage.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 = const FlutterSecureStorage(),
}) : _storage = storage;

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

@override
Future<Credentials?> read() async {
Expand Down
11 changes: 6 additions & 5 deletions packages/login_client_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: login_client_flutter
version: 3.0.0
version: 3.1.0
homepage: https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/login_client_flutter
repository: https://github.com/leancodepl/flutter_corelibrary
description: >-
flutter_secure_storage implementation of a CredentialsStorage
for the login_client package.

environment:
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.10.0'
sdk: '>=3.7.0 <4.0.0'
flutter: '>=3.29.0'

dependencies:
flutter:
Expand All @@ -17,5 +17,6 @@ dependencies:
login_client: ^3.0.0

dev_dependencies:
custom_lint: ^0.6.4
leancode_lint: ^12.0.0
flutter_test:
sdk: flutter
leancode_lint: ^16.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:login_client_flutter/login_client_flutter.dart';
// for testing credentials
// ignore: depend_on_referenced_packages
import 'package:oauth2/oauth2.dart';

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

@override
Future<String?> read({
required String key,
IOSOptions? iOptions,
AndroidOptions? aOptions,
LinuxOptions? lOptions,
WebOptions? webOptions,
MacOsOptions? mOptions,
WindowsOptions? wOptions,
}) async {
return _storage[key];
}

@override
Future<void> write({
required String key,
required String? value,
IOSOptions? iOptions,
AndroidOptions? aOptions,
LinuxOptions? lOptions,
WebOptions? webOptions,
MacOsOptions? mOptions,
WindowsOptions? wOptions,
}) async {
if (value == null) {
_storage.remove(key);
} else {
_storage[key] = value;
}
}

@override
Future<void> delete({
required String key,
IOSOptions? iOptions,
AndroidOptions? aOptions,
LinuxOptions? lOptions,
WebOptions? webOptions,
MacOsOptions? mOptions,
WindowsOptions? wOptions,
}) async {
_storage.remove(key);
}
}

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

expect(storage, isA<FlutterSecureCredentialsStorage>());
});

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

expect(storage, isA<FlutterSecureCredentialsStorage>());
});

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

expect(await storage.read(), null);

final credentials = Credentials('test_token');
await storage.save(credentials);

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

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