Skip to content

Commit 321a584

Browse files
[local_auth] Adopt structured errors - platform interface (#10023)
Platform interface portion of #9981 - Adds the new `LocalAuthException` class for structured errors. - Deprecates `AuthenticationOptions.useErrorDialogs` Part of: - flutter/flutter#113687 - flutter/flutter#175125 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 15acade commit 321a584

File tree

7 files changed

+167
-16
lines changed

7 files changed

+167
-16
lines changed

packages/local_auth/local_auth_platform_interface/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
## NEXT
1+
## 1.1.0
22

3+
* Adds `LocalAuthException` to allow for consistent, structured exceptions
4+
across platform implementations.
35
* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.
46

57
## 1.0.10

packages/local_auth/local_auth_platform_interface/lib/local_auth_platform_interface.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
77
import 'default_method_channel_platform.dart';
88
import 'types/types.dart';
99

10-
export 'package:local_auth_platform_interface/types/types.dart';
10+
export 'types/types.dart';
1111

1212
/// The interface that implementations of local_auth must implement.
1313
///
@@ -40,7 +40,13 @@ abstract class LocalAuthPlatform extends PlatformInterface {
4040
/// Authenticates the user with biometrics available on the device while also
4141
/// allowing the user to use device authentication - pin, pattern, passcode.
4242
///
43-
/// Returns true if the user successfully authenticated, false otherwise.
43+
/// Returns true if the user successfully authenticated. Returns false if
44+
/// the authentication completes, but the user failed the challenge with no
45+
/// further effects. Platform implementations should throw a
46+
/// [LocalAuthException] for any other outcome, such as errors, cancelation,
47+
/// or lockout. This may mean that for some platforms, the implementation will
48+
/// never return false (e.g., if the only standard outcomes are success,
49+
/// cancelation, or temporary lockout due to too many retries).
4450
///
4551
/// [localizedReason] is the message to show to user while prompting them
4652
/// for authentication. This is typically along the lines of: 'Please scan
@@ -50,11 +56,6 @@ abstract class LocalAuthPlatform extends PlatformInterface {
5056
/// customize messages in the dialogs.
5157
///
5258
/// Provide [options] for configuring further authentication related options.
53-
///
54-
/// Throws a [PlatformException] if there were technical problems with local
55-
/// authentication (e.g. lack of relevant hardware). This might throw
56-
/// [PlatformException] with error code [otherOperatingSystem] on the iOS
57-
/// simulator.
5859
Future<bool> authenticate({
5960
required String localizedReason,
6061
required Iterable<AuthMessages> authMessages,
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart';
6+
7+
/// An exception thrown by the plugin when there is authentication failure, or
8+
/// some other error.
9+
@immutable
10+
class LocalAuthException implements Exception {
11+
/// Creates a new exception with the given information.
12+
const LocalAuthException({
13+
required this.code,
14+
this.description,
15+
this.details,
16+
});
17+
18+
/// The type of failure.
19+
final LocalAuthExceptionCode code;
20+
21+
/// A human-readable description of the failure.
22+
final String? description;
23+
24+
/// Any additional details about the failure.
25+
final Object? details;
26+
27+
@override
28+
String toString() =>
29+
'${objectRuntimeType(this, 'LocalAuthException')}(code ${code.name}, $description, $details)';
30+
}
31+
32+
/// Types of [LocalAuthException]s, as indicated by [LocalAuthException.code].
33+
///
34+
/// Adding new values to this enum in the future will *not* be considered a
35+
/// breaking change, so clients should not assume they can exhaustively match
36+
/// exception codes. Clients should always include a default or other fallback.
37+
enum LocalAuthExceptionCode {
38+
/// An authentication operation is already in progress, and has not completed.
39+
///
40+
/// A new authentication cannot be started while the Future for a previous
41+
/// authentication is still outstanding.
42+
authInProgress,
43+
44+
/// UI needs to be displayed, but could not be.
45+
///
46+
/// For example, this can be returned on Android if a call tries to show UI
47+
/// when no Activity is available.
48+
uiUnavailable,
49+
50+
/// The operation was canceled by the user.
51+
userCanceled,
52+
53+
/// The operation was canceled due to a device-specific timeout.
54+
timeout,
55+
56+
/// The operation was canceled by a system event.
57+
///
58+
/// For example, on mobile this may be returned if the application is
59+
/// backgrounded during authentication.
60+
systemCanceled,
61+
62+
/// The device has no credentials configured.
63+
///
64+
/// For example, on mobile this would be returned if the device has no
65+
/// enrolled biometrics and no fallback authentication mechanism set such as
66+
/// a passcode, pin, or pattern.
67+
noCredentialsSet,
68+
69+
/// The device is capable of biometric authentication, but no biometrics are
70+
/// enrolled.
71+
noBiometricsEnrolled,
72+
73+
/// The device does not have biometric hardware.
74+
noBiometricHardware,
75+
76+
/// The device has, or can have, biometric hardware, but none is currently
77+
/// available.
78+
///
79+
/// Examples include:
80+
/// - Hardware that is currently in use by another application.
81+
/// - Devices that have previously paired with bluetooth biometric hardware,
82+
/// but are not currently paired to it.
83+
///
84+
/// Devices that could have removable hardware attached may return either this
85+
/// or [noBiometricHardware] depending on the platform implementation.
86+
/// Platforms should generally only return this code if the system provides
87+
/// information indicating that the device has previously had such hardware.
88+
biometricHardwareTemporarilyUnavailable,
89+
90+
/// Authentication has temporarily been locked out, and should be re-attempted
91+
/// later.
92+
///
93+
/// For example, devices may return this error after too many failed
94+
/// authentication attempts.
95+
temporaryLockout,
96+
97+
/// Biometric authentication has been locked until some other authentication
98+
/// has succeeded.
99+
///
100+
/// Applications that do not require biometric authentication should generally
101+
/// handle this error by re-attempting authentication with fallback to
102+
/// non-biometrics allowed. Applications that require biometrics should
103+
/// prompt users to resolve the lockout.
104+
biometricLockout,
105+
106+
/// The user indicated via system-provided UI that they want to use a fallback
107+
/// authentication option instead of biometrics.
108+
///
109+
/// Whether this can be returned depends on the platform implementation and
110+
/// the authentication configuration options. Applications should generally
111+
/// handle this error by offering the user an alternate authentication option.
112+
userRequestedFallback,
113+
114+
/// The authentication attempt failed due to some device-level error.
115+
///
116+
/// The [LocalAuthException.description] should contain more details about the
117+
/// error.
118+
deviceError,
119+
120+
/// The authentication attempt failed due to some unknown or unexpected error.
121+
///
122+
/// The [LocalAuthException.description] should contain more details about the
123+
/// error.
124+
unknownError,
125+
}

packages/local_auth/local_auth_platform_interface/lib/types/auth_options.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class AuthenticationOptions {
2121
/// take the user to settings to add one. Anything that is not user fixable,
2222
/// such as no biometric sensor on device, will still result in
2323
/// a [PlatformException].
24+
// This parameter still exists for backwards compatibility with local_auth
25+
// 2.x, but implementers targeting local_auth 3.x or later should ignore it,
26+
// as it will always be false.
2427
final bool useErrorDialogs;
2528

2629
/// Used when the application goes into background for any reason while the

packages/local_auth/local_auth_platform_interface/lib/types/types.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
export 'auth_exception.dart';
56
export 'auth_messages.dart';
67
export 'auth_options.dart';
78
export 'biometric_type.dart';

packages/local_auth/local_auth_platform_interface/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/local_auth/lo
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
55
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
66
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
7-
version: 1.0.10
7+
version: 1.1.0
88

99
environment:
1010
sdk: ^3.7.0

packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ void main() {
114114
localizedReason: 'Insecure',
115115
options: const AuthenticationOptions(
116116
sensitiveTransaction: false,
117-
useErrorDialogs: false,
118117
biometricOnly: true,
119118
),
120119
);
@@ -123,7 +122,7 @@ void main() {
123122
'authenticate',
124123
arguments: <String, dynamic>{
125124
'localizedReason': 'Insecure',
126-
'useErrorDialogs': false,
125+
'useErrorDialogs': true,
127126
'stickyAuth': false,
128127
'sensitiveTransaction': false,
129128
'biometricOnly': true,
@@ -157,24 +156,44 @@ void main() {
157156
await localAuthentication.authenticate(
158157
authMessages: <AuthMessages>[],
159158
localizedReason: 'Insecure',
160-
options: const AuthenticationOptions(
161-
sensitiveTransaction: false,
162-
useErrorDialogs: false,
163-
),
159+
options: const AuthenticationOptions(sensitiveTransaction: false),
164160
);
165161
expect(log, <Matcher>[
166162
isMethodCall(
167163
'authenticate',
168164
arguments: <String, dynamic>{
169165
'localizedReason': 'Insecure',
170-
'useErrorDialogs': false,
166+
'useErrorDialogs': true,
171167
'stickyAuth': false,
172168
'sensitiveTransaction': false,
173169
'biometricOnly': false,
174170
},
175171
),
176172
]);
177173
});
174+
175+
test(
176+
'legacy useErrorDialogs is passed for backward compatibility.',
177+
() async {
178+
await localAuthentication.authenticate(
179+
authMessages: <AuthMessages>[],
180+
localizedReason: 'Insecure',
181+
options: const AuthenticationOptions(useErrorDialogs: false),
182+
);
183+
expect(log, <Matcher>[
184+
isMethodCall(
185+
'authenticate',
186+
arguments: <String, dynamic>{
187+
'localizedReason': 'Insecure',
188+
'useErrorDialogs': false,
189+
'stickyAuth': false,
190+
'sensitiveTransaction': true,
191+
'biometricOnly': false,
192+
},
193+
),
194+
]);
195+
},
196+
);
178197
});
179198
});
180199
}

0 commit comments

Comments
 (0)