diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7a502..5b187a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.1-dev.1 + +* Add option for iOS/MacOS to allow non-biometric authentication (`darwinBiometricOnly`) #101 + * Improve [canAuthenticate] to differentiate between no available biometry and no available + user code. + ## 5.0.0+4 * Add topics to pubspec.yaml diff --git a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt index 176d842..aa8e56d 100644 --- a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt +++ b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt @@ -45,6 +45,7 @@ enum class CanAuthenticateResponse(val code: Int) { ErrorNoBiometricEnrolled(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED), ErrorNoHardware(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE), ErrorStatusUnknown(BiometricManager.BIOMETRIC_STATUS_UNKNOWN), + ErrorPasscodeNotSet(-99), ; override fun toString(): String { @@ -317,6 +318,12 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { } private fun canAuthenticate(): CanAuthenticateResponse { + val credentialsResponse = biometricManager.canAuthenticate(DEVICE_CREDENTIAL); + logger.debug { "canAuthenticate for DEVICE_CREDENTIAL: $credentialsResponse" } + if (credentialsResponse == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { + return CanAuthenticateResponse.Success + } + val response = biometricManager.canAuthenticate( BIOMETRIC_STRONG or BIOMETRIC_WEAK ) diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec index 8ce4394..29758b7 100644 --- a/example/ios/Flutter/Flutter.podspec +++ b/example/ios/Flutter/Flutter.podspec @@ -1,6 +1,6 @@ # -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# This is a generated file; do not edit or check into version control. +# This podspec is NOT to be published. It is only used as a local source! +# This is a generated file; do not edit or check into version control. # Pod::Spec.new do |s| diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 4bc6990..3366bad 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -19,4 +19,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index e9dcbda..cf61e3c 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -163,7 +163,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -208,10 +208,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -244,6 +246,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6..b52b2e6 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/example/pubspec.lock b/example/pubspec.lock index 16218e1..3cfe29d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -15,7 +15,7 @@ packages: path: ".." relative: true source: path - version: "5.0.0" + version: "5.0.0+4" boolean_selector: dependency: transitive description: diff --git a/lib/src/biometric_storage.dart b/lib/src/biometric_storage.dart index 5646966..c53f7cf 100644 --- a/lib/src/biometric_storage.dart +++ b/lib/src/biometric_storage.dart @@ -18,6 +18,9 @@ enum CanAuthenticateResponse { errorNoBiometricEnrolled, errorNoHardware, + /// Passcode is not set (iOS/MacOS) or no user credentials (on macos). + errorPasscodeNotSet, + /// Used on android if the status is unknown. /// https://developer.android.com/reference/androidx/biometric/BiometricManager#BIOMETRIC_STATUS_UNKNOWN statusUnknown, @@ -31,6 +34,7 @@ const _canAuthenticateMapping = { 'ErrorHwUnavailable': CanAuthenticateResponse.errorHwUnavailable, 'ErrorNoBiometricEnrolled': CanAuthenticateResponse.errorNoBiometricEnrolled, 'ErrorNoHardware': CanAuthenticateResponse.errorNoHardware, + 'ErrorPasscodeNotSet': CanAuthenticateResponse.errorPasscodeNotSet, 'ErrorUnknown': CanAuthenticateResponse.unsupported, 'ErrorStatusUnknown': CanAuthenticateResponse.statusUnknown, }; @@ -83,6 +87,7 @@ class StorageFileInitOptions { this.authenticationValidityDurationSeconds = -1, this.authenticationRequired = true, this.androidBiometricOnly = true, + this.darwinBiometricOnly = true, }); final int authenticationValidityDurationSeconds; @@ -102,11 +107,17 @@ class StorageFileInitOptions { /// https://github.com/authpass/biometric_storage/issues/12#issuecomment-902508609 final bool androidBiometricOnly; + /// Only for iOS and macOS: + /// Uses `.biometryCurrentSet` if true, `.userPresence` otherwise. + /// https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/1392879-userpresence + final bool darwinBiometricOnly; + Map toJson() => { 'authenticationValidityDurationSeconds': authenticationValidityDurationSeconds, 'authenticationRequired': authenticationRequired, 'androidBiometricOnly': androidBiometricOnly, + 'darwinBiometricOnly': darwinBiometricOnly, }; } diff --git a/macos/Classes/BiometricStorageImpl.swift b/macos/Classes/BiometricStorageImpl.swift index 971e3c8..373b19e 100644 --- a/macos/Classes/BiometricStorageImpl.swift +++ b/macos/Classes/BiometricStorageImpl.swift @@ -16,9 +16,11 @@ class InitOptions { init(params: [String: Any]) { authenticationValidityDurationSeconds = params["authenticationValidityDurationSeconds"] as? Int authenticationRequired = params["authenticationRequired"] as? Bool + darwinBiometricOnly = params["darwinBiometricOnly"] as? Bool } let authenticationValidityDurationSeconds: Int! let authenticationRequired: Bool! + let darwinBiometricOnly: Bool! } class IOSPromptInfo { @@ -136,7 +138,9 @@ class BiometricStorageImpl { case .touchIDNotAvailable: result("ErrorHwUnavailable") break; - case .passcodeNotSet: fallthrough + case .passcodeNotSet: + result("ErrorPasscodeNotSet") + break; case .touchIDNotEnrolled: result("ErrorNoBiometricEnrolled") break; @@ -194,10 +198,14 @@ class BiometricStorageFile { private func accessControl(_ result: @escaping StorageCallback) -> SecAccessControl? { let accessControlFlags: SecAccessControlCreateFlags - if #available(iOS 11.3, *) { - accessControlFlags = .biometryCurrentSet + if initOptions.darwinBiometricOnly { + if #available(iOS 11.3, *) { + accessControlFlags = .biometryCurrentSet + } else { + accessControlFlags = .touchIDCurrentSet + } } else { - accessControlFlags = .touchIDCurrentSet + accessControlFlags = .userPresence } // access = SecAccessControlCreateWithFlags(nil, diff --git a/pubspec.yaml b/pubspec.yaml index f2267e6..c68e742 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: biometric_storage description: | Secure Storage: Encrypted data store optionally secured by biometric lock with support for iOS, Android, MacOS. Partial support for Linux, Windows and web (localStorage). -version: 5.0.0+4 +version: 5.0.1-dev.1 homepage: https://github.com/authpass/biometric_storage/ environment: