diff --git a/README.md b/README.md index 78fa266..a291c68 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Assure is a Kotlin library that makes biometric authentication quick and easy. ```gradle dependencies { - implementation 'com.afollestad.assure:core:0.1.0' + implementation 'com.afollestad.assure:core:0.2.0' } ``` @@ -85,12 +85,8 @@ and `Fragment` (from AndroidX). Examples are below.* ```kotlin val prompt: Prompt = // ... -try { - authenticate(prompt) { - // Do something - } -} catch (e: BiometricErrorException) { - // Handle error with `e.error` and `e.errorMessage` +authenticate(prompt) { error: BiometricErrorException? -> + // If `error` is null, else auth was successful. } ``` @@ -102,16 +98,13 @@ val credentials = Credentials("default") val prompt: Prompt = // ... val plainTextData: ByteArray = // ... -try { - authenticateForEncrypt( - credentials = credentials, - prompt = prompt - ) { - val encryptedData: ByteArray = encrypt(plainTextData) - // Use encryptedData - } -} catch (e: BiometricErrorException) { - // Handle error with `e.error` and `e.errorMessage` +authenticateForEncrypt( + credentials = credentials, + prompt = prompt +) { error: BiometricErrorException? -> + // Use `error.error` and `error.message` if it isn't null, else auth was successful. + val encryptedData: ByteArray = encrypt(plainTextData) + // Use encryptedData } ``` @@ -122,16 +115,13 @@ val credentials: Credentials = // ... val prompt: Prompt = // ... val encryptedData: ByteArray = // ... -try { - authenticateForDecrypt( - credentials = credentials, - prompt = prompt - ) { - val decryptedData: ByteArray = decrypt(encryptedData) - // Use decryptedData - } -} catch (e: BiometricErrorException) { - // Handle error with `e.error` and `e.errorMessage` +authenticateForDecrypt( + credentials = credentials, + prompt = prompt +) { error: BiometricErrorException? -> + // Use `error.error` and `error.message` if it isn't null, else auth was successful. + val decryptedData: ByteArray = decrypt(encryptedData) + // Use decryptedData } ``` @@ -209,7 +199,7 @@ interface Decryptor { ```gradle dependencies { - implementation 'com.afollestad.assure:coroutines:0.1.0' + implementation 'com.afollestad.assure:coroutines:0.2.0' } ``` @@ -281,7 +271,7 @@ try { ```gradle dependencies { - implementation 'com.afollestad.assure:rxjava:0.1.0' + implementation 'com.afollestad.assure:rxjava:0.2.0' } ``` @@ -298,11 +288,11 @@ functions \must happen within a `suspend` function or `CoroutineScope`. import com.afollestad.assure.rxjava.authenticate val disposable = authenticate(prompt) - .doOnErrorOf { exception -> - // Handle error with `exception.error` and `exception.errorMessage` + .doOnBiometricError { error -> + // Handle error with `error.error` and `error.errorMessage` } .subscribe { - // Do something + // Auth was successful, do something } // make sure you manage the subscription @@ -320,8 +310,8 @@ val prompt: Prompt = // ... val plainTextData: ByteArray = // ... val disposable = authenticateForEncrypt(credentials, prompt) - .doOnErrorOf { exception -> - // Handle error with `exception.error` and `exception.errorMessage` + .doOnBiometricError { error -> + // Handle error with `error.error` and `error.errorMessage` } .map { encryptor -> encryptor.encrypt(plainTextData) @@ -344,8 +334,8 @@ val prompt: Prompt = // ... val encryptedData: ByteArray = // ... val disposable = authenticateForDecrypt(credentials, prompt) - .doOnErrorOf { exception -> - // Handle error with `exception.error` and `exception.errorMessage` + .doOnBiometricError { error -> + // Handle error with `error.error` and `error.errorMessage` } .map { decryptor -> decryptor.decrypt(encryptedData) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 94464bc..d269267 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,4 @@ -0.1.0 +0.2.0 -Initial release. \ No newline at end of file +API simplifications. Check out the README. For callback based auth, errors are sent through +the callback rather tha thrown. diff --git a/core/src/main/java/com/afollestad/assure/Activities.kt b/core/src/main/java/com/afollestad/assure/Activities.kt index 1067a4e..d650948 100644 --- a/core/src/main/java/com/afollestad/assure/Activities.kt +++ b/core/src/main/java/com/afollestad/assure/Activities.kt @@ -39,7 +39,7 @@ import com.afollestad.assure.crypto.OnEncryptor */ fun FragmentActivity.authenticate( prompt: Prompt, - onAuthentication: () -> Unit + onAuthentication: OnAuthentication ) { val executor = ContextCompat.getMainExecutor(this) val callback = createAuthenticateCallback(onAuthentication) diff --git a/core/src/main/java/com/afollestad/assure/Core.kt b/core/src/main/java/com/afollestad/assure/Core.kt index be6e268..c34e15c 100644 --- a/core/src/main/java/com/afollestad/assure/Core.kt +++ b/core/src/main/java/com/afollestad/assure/Core.kt @@ -26,6 +26,8 @@ import androidx.lifecycle.OnLifecycleEvent import com.afollestad.assure.crypto.Credentials import com.afollestad.assure.crypto.Crypto import com.afollestad.assure.crypto.CryptoMode +import com.afollestad.assure.crypto.ErrorDecryptor +import com.afollestad.assure.crypto.ErrorEncryptor import com.afollestad.assure.crypto.OnDecryptor import com.afollestad.assure.crypto.OnEncryptor import com.afollestad.assure.crypto.RealDecryptor @@ -47,23 +49,25 @@ internal fun BiometricPrompt.cancelAuthOnPause( lifecycleOwner.lifecycle.addObserver(observer) } +typealias OnAuthentication = (error: BiometricErrorException?) -> Unit + internal fun createAuthenticateCallback( - callback: () -> Unit + callback: OnAuthentication ): BiometricPrompt.AuthenticationCallback { return object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError( errorCode: Int, errString: CharSequence ) { - throw BiometricErrorException(errorCode.toBiometricError(), errString.toString()) + callback(BiometricErrorException(errorCode.toBiometricError(), errString.toString())) } override fun onAuthenticationFailed() { - throw BiometricErrorException(UNKNOWN, "Unknown failure") + callback(BiometricErrorException(UNKNOWN, "Unknown failure")) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - callback() + callback(null) } } } @@ -77,18 +81,20 @@ internal fun createEncryptCallback( errorCode: Int, errString: CharSequence ) { - throw BiometricErrorException(errorCode.toBiometricError(), errString.toString()) + val exception = BiometricErrorException(errorCode.toBiometricError(), errString.toString()) + callback(ErrorEncryptor(exception), exception) } override fun onAuthenticationFailed() { - throw BiometricErrorException(UNKNOWN, "Unknown failure") + val exception = BiometricErrorException(UNKNOWN, "Unknown failure") + callback(ErrorEncryptor(exception), exception) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { result.cryptoObject?.let { credentials?.iv = it.cipher?.iv ?: error("Unable to get IV") } - callback(RealEncryptor(cryptoObject = result.cryptoObject)) + callback(RealEncryptor(cryptoObject = result.cryptoObject), null) } } } @@ -101,15 +107,17 @@ internal fun createDecryptCallback( errorCode: Int, errString: CharSequence ) { - throw BiometricErrorException(errorCode.toBiometricError(), errString.toString()) + val exception = BiometricErrorException(errorCode.toBiometricError(), errString.toString()) + callback(ErrorDecryptor(exception), exception) } override fun onAuthenticationFailed() { - throw BiometricErrorException(UNKNOWN, "Unknown failure") + val exception = BiometricErrorException(UNKNOWN, "Unknown failure") + callback(ErrorDecryptor(exception), exception) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - callback(RealDecryptor(cryptoObject = result.cryptoObject)) + callback(RealDecryptor(cryptoObject = result.cryptoObject), null) } } } diff --git a/core/src/main/java/com/afollestad/assure/Fragments.kt b/core/src/main/java/com/afollestad/assure/Fragments.kt index 6b7da6c..cc54aa3 100644 --- a/core/src/main/java/com/afollestad/assure/Fragments.kt +++ b/core/src/main/java/com/afollestad/assure/Fragments.kt @@ -39,7 +39,7 @@ import com.afollestad.assure.crypto.OnEncryptor */ fun Fragment.authenticate( prompt: Prompt, - onAuthentication: () -> Unit + onAuthentication: OnAuthentication ) { val activity = requireActivity() val executor = ContextCompat.getMainExecutor(activity) diff --git a/core/src/main/java/com/afollestad/assure/crypto/Decryptor.kt b/core/src/main/java/com/afollestad/assure/crypto/Decryptor.kt index aca1385..f68ac49 100644 --- a/core/src/main/java/com/afollestad/assure/crypto/Decryptor.kt +++ b/core/src/main/java/com/afollestad/assure/crypto/Decryptor.kt @@ -20,9 +20,10 @@ package com.afollestad.assure.crypto import android.util.Base64 import androidx.annotation.CheckResult import androidx.biometric.BiometricPrompt +import com.afollestad.assure.BiometricErrorException import java.nio.charset.Charset -typealias OnDecryptor = Decryptor.() -> Unit +typealias OnDecryptor = Decryptor.(error: BiometricErrorException?) -> Unit /** * A class that can decrypt data. @@ -54,6 +55,14 @@ interface Decryptor { } } +internal class ErrorDecryptor internal constructor( + private val biometricErrorException: BiometricErrorException +) : Decryptor { + override fun decrypt(data: ByteArray): ByteArray { + throw biometricErrorException + } +} + internal class RealDecryptor internal constructor( private val cryptoObject: BiometricPrompt.CryptoObject? = null ) : Decryptor { diff --git a/core/src/main/java/com/afollestad/assure/crypto/Encryptor.kt b/core/src/main/java/com/afollestad/assure/crypto/Encryptor.kt index 6f1016d..ef5036c 100644 --- a/core/src/main/java/com/afollestad/assure/crypto/Encryptor.kt +++ b/core/src/main/java/com/afollestad/assure/crypto/Encryptor.kt @@ -20,9 +20,10 @@ package com.afollestad.assure.crypto import android.util.Base64 import androidx.annotation.CheckResult import androidx.biometric.BiometricPrompt +import com.afollestad.assure.BiometricErrorException import java.nio.charset.Charset -typealias OnEncryptor = Encryptor.() -> Unit +typealias OnEncryptor = Encryptor.(error: BiometricErrorException?) -> Unit /** * A class that can encrypt data. @@ -53,6 +54,14 @@ interface Encryptor { } } +internal class ErrorEncryptor internal constructor( + private val biometricErrorException: BiometricErrorException +) : Encryptor { + override fun encrypt(data: ByteArray): ByteArray { + throw biometricErrorException + } +} + internal class RealEncryptor internal constructor( private val cryptoObject: BiometricPrompt.CryptoObject? = null ) : Encryptor { diff --git a/coroutines/src/main/java/com/afollestad/assure/coroutines/ActivitiesCoroutines.kt b/coroutines/src/main/java/com/afollestad/assure/coroutines/ActivitiesCoroutines.kt index 723355c..02bb5f2 100644 --- a/coroutines/src/main/java/com/afollestad/assure/coroutines/ActivitiesCoroutines.kt +++ b/coroutines/src/main/java/com/afollestad/assure/coroutines/ActivitiesCoroutines.kt @@ -42,12 +42,9 @@ suspend fun FragmentActivity.authenticate( prompt: Prompt ) { return suspendCoroutine { continuation -> - try { - authenticate(prompt) { - continuation.resume(Unit) - } - } catch (t: Throwable) { - continuation.resumeWithException(t) + authenticate(prompt) { error -> + error?.let { continuation.resumeWithException(it) } + ?: continuation.resume(Unit) } } } @@ -67,15 +64,12 @@ suspend fun FragmentActivity.authenticateForEncrypt( prompt: Prompt ): Encryptor { return suspendCoroutine { continuation -> - try { - authenticateForEncrypt( - credentials = credentials, - prompt = prompt - ) { - continuation.resume(this) - } - } catch (t: Throwable) { - continuation.resumeWithException(t) + authenticateForEncrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { continuation.resumeWithException(it) } + ?: continuation.resume(this) } } } @@ -95,15 +89,12 @@ suspend fun FragmentActivity.authenticateForDecrypt( prompt: Prompt ): Decryptor { return suspendCoroutine { continuation -> - try { - authenticateForDecrypt( - credentials = credentials, - prompt = prompt - ) { - continuation.resume(this) - } - } catch (t: Throwable) { - continuation.resumeWithException(t) + authenticateForDecrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { continuation.resumeWithException(it) } + ?: continuation.resume(this) } } } diff --git a/coroutines/src/main/java/com/afollestad/assure/coroutines/FragmentsCoroutines.kt b/coroutines/src/main/java/com/afollestad/assure/coroutines/FragmentsCoroutines.kt index e5aadb2..b483d18 100644 --- a/coroutines/src/main/java/com/afollestad/assure/coroutines/FragmentsCoroutines.kt +++ b/coroutines/src/main/java/com/afollestad/assure/coroutines/FragmentsCoroutines.kt @@ -42,12 +42,9 @@ suspend fun Fragment.authenticate( prompt: Prompt ) { return suspendCoroutine { continuation -> - try { - authenticate(prompt) { - continuation.resume(Unit) - } - } catch (t: Throwable) { - continuation.resumeWithException(t) + authenticate(prompt) { error -> + error?.let { continuation.resumeWithException(it) } + ?: continuation.resume(Unit) } } } @@ -67,15 +64,12 @@ suspend fun Fragment.authenticateForEncrypt( prompt: Prompt ): Encryptor { return suspendCoroutine { continuation -> - try { - authenticateForEncrypt( - credentials = credentials, - prompt = prompt - ) { - continuation.resume(this) - } - } catch (t: Throwable) { - continuation.resumeWithException(t) + authenticateForEncrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { continuation.resumeWithException(it) } + ?: continuation.resume(this) } } } @@ -95,15 +89,12 @@ suspend fun Fragment.authenticateForDecrypt( prompt: Prompt ): Decryptor { return suspendCoroutine { continuation -> - try { - authenticateForDecrypt( - credentials = credentials, - prompt = prompt - ) { - continuation.resume(this) - } - } catch (t: Throwable) { - continuation.resumeWithException(t) + authenticateForDecrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { continuation.resumeWithException(it) } + ?: continuation.resume(this) } } } diff --git a/dependencies.gradle b/dependencies.gradle index 08c2ade..9613362 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,8 +2,8 @@ ext.versions = [ min_sdk: 23, compile_sdk: 29, build_tools: "29.0.0", - publish_version: "0.1.0", - publish_version_code: 1 + publish_version: "0.2.0", + publish_version_code: 2 ] ext.deps = [ diff --git a/rxjava/src/main/java/com/afollestad/assure/rxjava/ActivitiesRx.kt b/rxjava/src/main/java/com/afollestad/assure/rxjava/ActivitiesRx.kt index fffbfd7..94ef4af 100644 --- a/rxjava/src/main/java/com/afollestad/assure/rxjava/ActivitiesRx.kt +++ b/rxjava/src/main/java/com/afollestad/assure/rxjava/ActivitiesRx.kt @@ -42,12 +42,9 @@ fun FragmentActivity.authenticate( prompt: Prompt ): Completable { return Completable.create { emitter -> - try { - authenticate(prompt) { - emitter.onComplete() - } - } catch (t: Throwable) { - emitter.onError(t) + authenticate(prompt) { error -> + error?.let { emitter.onError(error) } + ?: emitter.onComplete() } } } @@ -67,15 +64,12 @@ fun FragmentActivity.authenticateForEncrypt( prompt: Prompt ): Single { return Single.create { emitter -> - try { - authenticateForEncrypt( - credentials = credentials, - prompt = prompt - ) { - emitter.onSuccess(this) - } - } catch (t: Throwable) { - emitter.onError(t) + authenticateForEncrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { emitter.onError(error) } + ?: emitter.onSuccess(this) } } } @@ -95,15 +89,12 @@ fun FragmentActivity.authenticateForDecrypt( prompt: Prompt ): Single { return Single.create { emitter -> - try { - authenticateForDecrypt( - credentials = credentials, - prompt = prompt - ) { - emitter.onSuccess(this) - } - } catch (t: Throwable) { - emitter.onError(t) + authenticateForDecrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { emitter.onError(error) } + ?: emitter.onSuccess(this) } } } diff --git a/rxjava/src/main/java/com/afollestad/assure/rxjava/FragmentsRx.kt b/rxjava/src/main/java/com/afollestad/assure/rxjava/FragmentsRx.kt index 0a3e029..3b920b2 100644 --- a/rxjava/src/main/java/com/afollestad/assure/rxjava/FragmentsRx.kt +++ b/rxjava/src/main/java/com/afollestad/assure/rxjava/FragmentsRx.kt @@ -42,12 +42,9 @@ fun Fragment.authenticate( prompt: Prompt ): Completable { return Completable.create { emitter -> - try { - authenticate(prompt) { - emitter.onComplete() - } - } catch (t: Throwable) { - emitter.onError(t) + authenticate(prompt) { error -> + error?.let { emitter.onError(error) } + ?: emitter.onComplete() } } } @@ -67,15 +64,12 @@ fun Fragment.authenticateForEncrypt( prompt: Prompt ): Single { return Single.create { emitter -> - try { - authenticateForEncrypt( - credentials = credentials, - prompt = prompt - ) { - emitter.onSuccess(this) - } - } catch (t: Throwable) { - emitter.onError(t) + authenticateForEncrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { emitter.onError(it) } + ?: emitter.onSuccess(this) } } } @@ -95,15 +89,12 @@ fun Fragment.authenticateForDecrypt( prompt: Prompt ): Single { return Single.create { emitter -> - try { - authenticateForDecrypt( - credentials = credentials, - prompt = prompt - ) { - emitter.onSuccess(this) - } - } catch (t: Throwable) { - emitter.onError(t) + authenticateForDecrypt( + credentials = credentials, + prompt = prompt + ) { error -> + error?.let { emitter.onError(it) } + ?: emitter.onSuccess(this) } } } diff --git a/rxjava/src/main/java/com/afollestad/assure/rxjava/Singles.kt b/rxjava/src/main/java/com/afollestad/assure/rxjava/Singles.kt index 848dc2e..995353a 100644 --- a/rxjava/src/main/java/com/afollestad/assure/rxjava/Singles.kt +++ b/rxjava/src/main/java/com/afollestad/assure/rxjava/Singles.kt @@ -17,33 +17,26 @@ package com.afollestad.assure.rxjava +import com.afollestad.assure.BiometricErrorException import io.reactivex.Single import io.reactivex.functions.Consumer /** - * Calls the shared consumer with the error sent via onError for each - * SingleObserver that subscribes to the current Single. Filters errors sent through - * to the type of [T]. + * Similar to [Single.doOnError] but only invokes [onError] for [BiometricErrorException]. * * @author Aidan Follestad (@afollestad) */ -inline fun Single<*>.doOnErrorOf(onError: Consumer) = +fun Single.doOnBiometricError(onError: Consumer) = doOnError { throwable -> - if (T::class.java.isAssignableFrom(throwable::class.java)) { - onError.accept(throwable as T) + if (throwable is BiometricErrorException) { + onError.accept(throwable) } } /** - * Calls the shared consumer with the error sent via onError for each - * SingleObserver that subscribes to the current Single. Filters errors sent through - * to the type of [T]. + * Similar to [Single.doOnError] but only invokes [onError] for [BiometricErrorException]. * * @author Aidan Follestad (@afollestad) */ -inline fun Single<*>.doOnErrorOf(noinline onError: (T) -> Unit) = - doOnError { throwable -> - if (T::class.java.isAssignableFrom(throwable::class.java)) { - onError(throwable as T) - } - } +fun Single.doOnBiometricError(onError: (error: BiometricErrorException) -> Unit) = + doOnBiometricError(Consumer { onError(it) })