Skip to content

Commit b7d96a8

Browse files
committed
Merge pull request authpass#138 from kee-org/keevault-2025-a
# Conflicts: # lib/src/biometric_storage.dart
2 parents b3e5643 + 5103311 commit b7d96a8

6 files changed

Lines changed: 165 additions & 49 deletions

File tree

example/lib/main.dart

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -120,53 +120,92 @@ class MyAppState extends State<MyApp> {
120120
body: Column(
121121
children: [
122122
const Text('Methods:'),
123-
ElevatedButton(
124-
child: const Text('init'),
125-
onPressed: () async {
126-
_logger.finer('Initializing $baseName');
127-
final authStorageSupport =
128-
await _checkAuthenticate(_authStorageInitOptions);
129-
if (authStorageSupport == CanAuthenticateResponse.unsupported) {
130-
_logger.severe(
131-
'Unable to use authenticate. Unable to get storage.');
132-
return;
133-
}
134-
final supportsAuthenticated = authStorageSupport ==
135-
CanAuthenticateResponse.success ||
136-
authStorageSupport == CanAuthenticateResponse.statusUnknown;
137-
if (supportsAuthenticated) {
138-
_authStorage = await BiometricStorage().getStorage(
139-
'${baseName}_authenticated',
140-
options: _authStorageInitOptions,
141-
);
142-
}
143-
_storage = await BiometricStorage()
144-
.getStorage('${baseName}_unauthenticated',
145-
options: StorageFileInitOptions(
146-
authenticationRequired: false,
147-
));
148-
final supportsCustomPrompt =
149-
await _checkAuthenticate(_customPromptInitOptions);
150-
if (supportsCustomPrompt == CanAuthenticateResponse.success) {
151-
_customPrompt = await BiometricStorage()
152-
.getStorage('${baseName}_customPrompt',
153-
options: _customPromptInitOptions,
154-
promptInfo: const PromptInfo(
155-
iosPromptInfo: IosPromptInfo(
156-
saveTitle: 'Custom save title',
157-
accessTitle: 'Custom access title.',
158-
),
159-
androidPromptInfo: AndroidPromptInfo(
160-
title: 'Custom title',
161-
subtitle: 'Custom subtitle',
162-
description: 'Custom description',
163-
negativeButton: 'Nope!',
164-
),
165-
));
166-
}
167-
setState(() {});
168-
_logger.info('initiailzed $baseName');
169-
},
123+
Row(
124+
mainAxisAlignment: MainAxisAlignment.spaceAround,
125+
children: [
126+
ElevatedButton(
127+
child: const Text('init'),
128+
onPressed: () async {
129+
_logger.finer('Initializing $baseName');
130+
final authStorageSupport =
131+
await _checkAuthenticate(_authStorageInitOptions);
132+
if (authStorageSupport ==
133+
CanAuthenticateResponse.unsupported) {
134+
_logger.severe(
135+
'Unable to use authenticate. Unable to get storage.');
136+
return;
137+
}
138+
final supportsAuthenticated =
139+
authStorageSupport == CanAuthenticateResponse.success ||
140+
authStorageSupport ==
141+
CanAuthenticateResponse.statusUnknown;
142+
if (supportsAuthenticated) {
143+
_authStorage = await BiometricStorage().getStorage(
144+
'${baseName}_authenticated',
145+
options: _authStorageInitOptions,
146+
);
147+
}
148+
_storage = await BiometricStorage()
149+
.getStorage('${baseName}_unauthenticated',
150+
options: StorageFileInitOptions(
151+
authenticationRequired: false,
152+
));
153+
final supportsCustomPrompt =
154+
await _checkAuthenticate(_customPromptInitOptions);
155+
if (supportsCustomPrompt ==
156+
CanAuthenticateResponse.success) {
157+
_customPrompt = await BiometricStorage()
158+
.getStorage('${baseName}_customPrompt',
159+
options: _customPromptInitOptions,
160+
promptInfo: const PromptInfo(
161+
iosPromptInfo: IosPromptInfo(
162+
saveTitle: 'Custom save title',
163+
accessTitle: 'Custom access title.',
164+
),
165+
androidPromptInfo: AndroidPromptInfo(
166+
title: 'Custom title',
167+
subtitle: 'Custom subtitle',
168+
description: 'Custom description',
169+
negativeButton: 'Nope!',
170+
),
171+
));
172+
}
173+
setState(() {});
174+
_logger.info('initialized $baseName');
175+
},
176+
),
177+
ElevatedButton(
178+
child: const Text('Delete and dispose all'),
179+
onPressed: () async {
180+
_logger.info('Deleting and disposing all storages');
181+
try {
182+
if (_authStorage != null) {
183+
await _authStorage!.deleteAndDispose();
184+
_authStorage = null;
185+
_logger.finer(
186+
'Deleted and disposed authenticated storage');
187+
}
188+
if (_storage != null) {
189+
await _storage!.deleteAndDispose();
190+
_storage = null;
191+
_logger.finer(
192+
'Deleted and disposed unauthenticated storage');
193+
}
194+
if (_customPrompt != null) {
195+
await _customPrompt!.deleteAndDispose();
196+
_customPrompt = null;
197+
_logger.finer(
198+
'Deleted and disposed custom prompt storage');
199+
}
200+
setState(() {});
201+
_logger.info('All storages deleted and disposed');
202+
} catch (e) {
203+
_logger
204+
.severe('Error deleting and disposing storages: $e');
205+
}
206+
},
207+
),
208+
],
170209
),
171210
...?_appArmorButton(),
172211
...(_authStorage == null

lib/src/biometric_storage.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ abstract class BiometricStorage extends PlatformInterface {
303303
PromptInfo promptInfo, {
304304
bool forceBiometricAuthentication = false,
305305
});
306+
307+
@protected
308+
Future<void> dispose(
309+
String name,
310+
PromptInfo promptInfo,
311+
);
306312
}
307313

308314
class MethodChannelBiometricStorage extends BiometricStorage {
@@ -442,6 +448,14 @@ class MethodChannelBiometricStorage extends BiometricStorage {
442448
..._promptInfoForCurrentPlatform(promptInfo),
443449
}));
444450

451+
@override
452+
Future<void> dispose(String name, PromptInfo promptInfo) => _transformErrors(
453+
_channel.invokeMethod('dispose', <String, dynamic>{
454+
'name': name,
455+
..._promptInfoForCurrentPlatform(promptInfo),
456+
}),
457+
);
458+
445459
Map<String, dynamic> _promptInfoForCurrentPlatform(PromptInfo promptInfo) {
446460
// Don't expose Android configurations to other platforms
447461
if (Platform.isAndroid) {
@@ -538,4 +552,12 @@ class BiometricStorageFile {
538552
/// Delete the content of this storage.
539553
Future<void> delete({PromptInfo? promptInfo}) =>
540554
_plugin.delete(name, promptInfo ?? defaultPromptInfo);
555+
556+
/// Delete the content of this storage and dispose of the storage instance.
557+
/// After calling this method, this BiometricStorageFile instance should not be used.
558+
/// A subsequent call to BiometricStorage().getStorage() with the same name will create a new instance.
559+
Future<void> deleteAndDispose({PromptInfo? promptInfo}) async {
560+
await delete(promptInfo: promptInfo);
561+
await _plugin.dispose(name, promptInfo ?? defaultPromptInfo);
562+
}
541563
}

lib/src/biometric_storage_web.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,9 @@ class BiometricStoragePluginWeb extends BiometricStorage {
6161
}) async {
6262
web.window.localStorage.setItem(name, content);
6363
}
64+
65+
@override
66+
Future<void> dispose(String name, PromptInfo promptInfo) async {
67+
// Nothing to dispose for web implementation
68+
}
6469
}

lib/src/biometric_storage_win32.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,9 @@ class Win32BiometricStoragePlugin extends BiometricStorage {
150150
_logger.fine('free done');
151151
}
152152
}
153+
154+
@override
155+
Future<void> dispose(String name, PromptInfo promptInfo) async {
156+
// Nothing to dispose for Win32 implementation
157+
}
153158
}

macos/Classes/BiometricStorageImpl.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,14 @@ class BiometricStorageImpl {
9292
}
9393
result(true)
9494
} else if ("dispose" == call.method) {
95-
// nothing to dispose
96-
result(true)
95+
requiredArg("name") { name in
96+
guard let file = stores.removeValue(forKey: name) else {
97+
result(storageError(code: "NoSuchStorage", message: "Tried to dispose non existing storage.", details: nil))
98+
return
99+
}
100+
file.dispose()
101+
result(true)
102+
}
97103
} else if ("read" == call.method) {
98104
requiredArg("name") { name in
99105
requiredArg("forceBiometricAuthentication") { forceBiometricAuthentication in
@@ -203,6 +209,10 @@ class BiometricStorageFile {
203209
self.initOptions = initOptions
204210
self.storageError = storageError
205211
}
212+
213+
func dispose() {
214+
_context = nil
215+
}
206216

207217
private func baseQuery(_ result: @escaping StorageCallback) -> [String: Any]? {
208218
var query = [

test/biometric_storage_test.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,41 @@ void main() {
237237
});
238238
}
239239
});
240+
241+
test('deleteAndDispose forwards delete then dispose', () async {
242+
methodCallHandler = (MethodCall methodCall) async {
243+
switch (methodCall.method) {
244+
case 'init':
245+
case 'delete':
246+
case 'dispose':
247+
return true;
248+
}
249+
throw PlatformException(code: 'NotImplemented');
250+
};
251+
252+
final storage = await BiometricStorage().getStorage('dispose-secret');
253+
254+
await storage.deleteAndDispose();
255+
256+
expect(
257+
methodCalls.map((methodCall) => methodCall.method),
258+
<String>['init', 'delete', 'dispose'],
259+
);
260+
expect(methodCalls[1].arguments, <String, dynamic>{
261+
'name': 'dispose-secret',
262+
'iosPromptInfo': <String, dynamic>{
263+
'saveTitle': 'Unlock to save data',
264+
'accessTitle': 'Unlock to access data',
265+
},
266+
});
267+
expect(methodCalls[2].arguments, <String, dynamic>{
268+
'name': 'dispose-secret',
269+
'iosPromptInfo': <String, dynamic>{
270+
'saveTitle': 'Unlock to save data',
271+
'accessTitle': 'Unlock to access data',
272+
},
273+
});
274+
});
240275
});
241276

242277
group('platform error translation', () {

0 commit comments

Comments
 (0)