From 9412f6b7f086109c17d6958fbaea1393e80dd2a8 Mon Sep 17 00:00:00 2001 From: dankito Date: Wed, 18 Sep 2024 17:01:46 +0200 Subject: [PATCH] Implemented biometric authentication on Android --- composeApp/build.gradle.kts | 1 + .../src/androidMain/AndroidManifest.xml | 1 + .../net/codinux/banking/ui/MainActivity.kt | 7 +- .../service/AuthenticationService.android.kt | 29 ++++++++ .../service/BiometricAuthenticationService.kt | 66 +++++++++++++++++++ .../src/androidMain/res/values/strings.xml | 2 + .../BiometricAuthenticationButton.kt | 23 +++++++ .../banking/ui/model/AuthenticationResult.kt | 17 +++++ .../codinux/banking/ui/screens/LoginScreen.kt | 40 ++++++++++- .../ui/screens/ProtectAppSettingsDialog.kt | 44 ++++++------- .../ui/service/AuthenticationService.kt | 16 +++++ .../banking/ui/service/PasswordService.kt | 9 --- .../service/AuthenticationService.desktop.kt} | 10 ++- .../ui/service/PasswordService.desktop.kt | 13 ---- ...ce.ios.kt => AuthenticationService.ios.kt} | 11 +++- .../ui/service/AuthenticationService.js.kt | 22 +++++++ .../banking/ui/service/PasswordService.js.kt | 13 ---- gradle/libs.versions.toml | 2 + 18 files changed, 262 insertions(+), 64 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.android.kt create mode 100644 composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/BiometricAuthenticationService.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/authentification/BiometricAuthenticationButton.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/AuthenticationResult.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.kt delete mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/PasswordService.kt rename composeApp/src/{androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt => desktopMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.desktop.kt} (52%) delete mode 100644 composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/PasswordService.desktop.kt rename composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/{PasswordService.ios.kt => AuthenticationService.ios.kt} (57%) create mode 100644 composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.js.kt delete mode 100644 composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/PasswordService.js.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 482892f..ef0a751 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -91,6 +91,7 @@ kotlin { androidMain.dependencies { implementation(compose.preview) implementation(libs.androidx.activity.compose) + implementation(libs.androidx.biometric) implementation(libs.sqldelight.android.driver) diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index 2b8ba1b..e2bd569 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -2,6 +2,7 @@ + Unit) { + if (biometricAuthenticationService != null) { + biometricAuthenticationService!!.authenticate(null, authenticationResult) + } else { + authenticationResult(AuthenticationResult(false, "Biometrics is not supported")) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/BiometricAuthenticationService.kt b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/BiometricAuthenticationService.kt new file mode 100644 index 0000000..439f47b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/BiometricAuthenticationService.kt @@ -0,0 +1,66 @@ +package net.codinux.banking.ui.service + +import android.os.Build +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK +import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import net.codinux.banking.ui.R +import net.codinux.banking.ui.model.AuthenticationResult +import javax.crypto.Cipher + +class BiometricAuthenticationService( + private val activity: FragmentActivity +) { + + private val biometricManager: BiometricManager = BiometricManager.from(activity) + + private val allowedAuthenticators = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) BIOMETRIC_STRONG + else BIOMETRIC_STRONG or BIOMETRIC_WEAK + + + val supportsBiometricAuthentication: Boolean by lazy { + biometricManager.canAuthenticate(allowedAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS + } + + + fun authenticate(cipher: Cipher?, authenticationResult: (AuthenticationResult) -> Unit) { + val executor = ContextCompat.getMainExecutor(this.activity) + + val biometricPrompt = BiometricPrompt(activity, executor, + object : BiometricPrompt.AuthenticationCallback() { + + override fun onAuthenticationError(errorCode: Int, errorString: CharSequence) { + super.onAuthenticationError(errorCode, errorString) + authenticationResult(AuthenticationResult(false, errorString.toString())) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + + authenticationResult(AuthenticationResult(true)) + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + authenticationResult(AuthenticationResult(false)) + } + }) + + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(this.activity.getString(R.string.activity_login_authenticate_with_biometrics_prompt)) + //.setSubtitle() // TODO: add subtitle? + //.setNegativeButtonText(this.activity.getString(android.R.string.cancel)) // is not allowed when device credentials are allowed + .setAllowedAuthenticators(allowedAuthenticators) + .build() + + if (cipher == null) { + biometricPrompt.authenticate(promptInfo) + } else { + biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher)) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml index cf8f569..8ab7bcb 100644 --- a/composeApp/src/androidMain/res/values/strings.xml +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -1,3 +1,5 @@ Bankmeister + + Authentifizieren Sich sich um die App zu entsperren \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/authentification/BiometricAuthenticationButton.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/authentification/BiometricAuthenticationButton.kt new file mode 100644 index 0000000..9ea7547 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/authentification/BiometricAuthenticationButton.kt @@ -0,0 +1,23 @@ +package net.codinux.banking.ui.composables.authentification + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Fingerprint +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import net.codinux.banking.ui.model.AuthenticationResult +import net.codinux.banking.ui.service.AuthenticationService + +@Composable +fun BiometricAuthenticationButton(authenticationResult: (AuthenticationResult) -> Unit) { + + Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.Center) { + Button({ AuthenticationService.authenticateWithBiometrics(authenticationResult) }, enabled = AuthenticationService.supportsBiometricAuthentication) { + Icon(Icons.Outlined.Fingerprint, "Sich mittels Biometrie authentifizieren", Modifier.size(84.dp)) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/AuthenticationResult.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/AuthenticationResult.kt new file mode 100644 index 0000000..749026a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/AuthenticationResult.kt @@ -0,0 +1,17 @@ +package net.codinux.banking.ui.model + +class AuthenticationResult( + val successful: Boolean, + val error: String? = null +) { + + override fun toString(): String { + return if (successful) { + "Successful" + } + else { + "Error occurred: $error" + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt index a7ec3d6..f609562 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt @@ -16,10 +16,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import bankmeister.composeapp.generated.resources.* import bankmeister.composeapp.generated.resources.Res +import net.codinux.banking.ui.composables.authentification.BiometricAuthenticationButton import net.codinux.banking.ui.forms.PasswordTextField +import net.codinux.banking.ui.model.AuthenticationResult import net.codinux.banking.ui.model.settings.AppAuthenticationMethod import net.codinux.banking.ui.model.settings.AppSettings -import net.codinux.banking.ui.service.PasswordService +import net.codinux.banking.ui.service.AuthenticationService import org.jetbrains.compose.resources.imageResource @Composable @@ -30,9 +32,21 @@ fun LoginScreen(appSettings: AppSettings, onLoginSuccess: () -> Unit) { var showError by remember { mutableStateOf(false) } + fun successfullyLoggedIn() { + onLoginSuccess() + } + fun checkPassword() { - if (appSettings.hashedPassword != null && PasswordService.checkPassword(password, appSettings.hashedPassword!!)) { - onLoginSuccess() + if (appSettings.hashedPassword != null && AuthenticationService.checkPassword(password, appSettings.hashedPassword!!)) { + successfullyLoggedIn() + } else { + showError = true + } + } + + fun checkBiometricLoginResult(result: AuthenticationResult) { + if (result.successful) { + successfullyLoggedIn() } else { showError = true } @@ -74,6 +88,26 @@ fun LoginScreen(appSettings: AppSettings, onLoginSuccess: () -> Unit) { Text("Login", color = Color.White) } } + + if (appSettings.authenticationMethod == AppAuthenticationMethod.Biometric) { + if (showError) { + Text("Biometrische Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.", color = MaterialTheme.colors.error, fontSize = 18.sp, + textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp).padding(horizontal = 16.dp)) + } + + BiometricAuthenticationButton { + checkBiometricLoginResult(it) + } + } + } + } + + + LaunchedEffect(appSettings.authenticationMethod) { + if (appSettings.authenticationMethod == AppAuthenticationMethod.Biometric) { + AuthenticationService.authenticateWithBiometrics { result -> + checkBiometricLoginResult(result) + } } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialog.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialog.kt index 9661f6f..b1a9a4d 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialog.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialog.kt @@ -1,9 +1,7 @@ package net.codinux.banking.ui.screens import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -13,6 +11,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch +import net.codinux.banking.ui.composables.authentification.BiometricAuthenticationButton import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.Internationalization @@ -21,7 +20,7 @@ import net.codinux.banking.ui.forms.PasswordTextField import net.codinux.banking.ui.forms.SegmentedControl import net.codinux.banking.ui.model.settings.AppAuthenticationMethod import net.codinux.banking.ui.model.settings.AppSettings -import net.codinux.banking.ui.service.PasswordService +import net.codinux.banking.ui.service.AuthenticationService private val buttonHeight = 50.dp @@ -30,7 +29,7 @@ private val buttonHeight = 50.dp fun ProtectAppSettingsDialog(appSettings: AppSettings, onClosed: () -> Unit) { val currentAuthenticationMethod = appSettings.authenticationMethod - val isBiometricAuthenticationSupported = false + val isBiometricAuthenticationSupported = AuthenticationService.supportsBiometricAuthentication val supportedAuthenticationMethods = buildList { add(AppAuthenticationMethod.Password) @@ -47,35 +46,30 @@ fun ProtectAppSettingsDialog(appSettings: AppSettings, onClosed: () -> Unit) { var confirmedNewPassword by remember { mutableStateOf("") } + var hasAuthenticatedWithBiometric by remember { mutableStateOf(false) } + val isRequiredDataEntered by remember(newPassword, confirmedNewPassword) { - derivedStateOf { newPassword.isNotBlank() && newPassword == confirmedNewPassword } + derivedStateOf { + (selectedAuthenticationMethod == AppAuthenticationMethod.Password && newPassword.isNotBlank() && newPassword == confirmedNewPassword) + || (selectedAuthenticationMethod == AppAuthenticationMethod.Biometric && hasAuthenticatedWithBiometric) + } } val coroutineScope = rememberCoroutineScope() - fun saveAppSettings(appSettings: AppSettings) { + fun saveNewAppProtection() { coroutineScope.launch { + appSettings.authenticationMethod = selectedAuthenticationMethod + appSettings.hashedPassword = if (selectedAuthenticationMethod == AppAuthenticationMethod.Password) AuthenticationService.hashPassword(newPassword) + else null + DI.bankingService.saveAppSettings(appSettings) onClosed() } } - fun setAppPasswordProtection() { - appSettings.authenticationMethod = AppAuthenticationMethod.Password - appSettings.hashedPassword = PasswordService.hashPassword(newPassword) - - saveAppSettings(appSettings) - } - - fun removeAppProtection() { - appSettings.authenticationMethod = AppAuthenticationMethod.None - appSettings.hashedPassword = null - - saveAppSettings(appSettings) - } - FullscreenViewBase("Appzugang schützen", showButtonBar = false, onClosed = onClosed) { Column(Modifier.fillMaxSize().padding(8.dp)) { @@ -103,18 +97,24 @@ fun ProtectAppSettingsDialog(appSettings: AppSettings, onClosed: () -> Unit) { PasswordTextField(confirmedNewPassword, "Password bestätigen", Modifier.padding(top = 16.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)) { confirmedNewPassword = it } } + if (selectedAuthenticationMethod == AppAuthenticationMethod.Biometric) { + BiometricAuthenticationButton { result -> + hasAuthenticatedWithBiometric = result.successful + } + } + Spacer(Modifier.weight(1f)) } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { if (selectedAuthenticationMethod == AppAuthenticationMethod.None) { Button(modifier = Modifier.fillMaxWidth().height(buttonHeight), enabled = currentAuthenticationMethod != AppAuthenticationMethod.None, - colors = ButtonDefaults.buttonColors(Colors.DestructiveColor), onClick = { removeAppProtection() }) { + colors = ButtonDefaults.buttonColors(Colors.DestructiveColor), onClick = { saveNewAppProtection() }) { Text("Appzugangsschutz entfernen", color = Color.White) } } else { Button(modifier = Modifier.fillMaxWidth().height(buttonHeight), enabled = isRequiredDataEntered, - colors = ButtonDefaults.buttonColors(Colors.Accent), onClick = { setAppPasswordProtection() }) { + colors = ButtonDefaults.buttonColors(Colors.Accent), onClick = { saveNewAppProtection() }) { Text("Setzen", color = Color.White) } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.kt new file mode 100644 index 0000000..42134c9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.kt @@ -0,0 +1,16 @@ +package net.codinux.banking.ui.service + +import net.codinux.banking.ui.model.AuthenticationResult + +expect object AuthenticationService { + + fun hashPassword(password: String): String + + fun checkPassword(password: String, hashedPassword: String): Boolean + + + val supportsBiometricAuthentication: Boolean + + fun authenticateWithBiometrics(authenticationResult: (AuthenticationResult) -> Unit) + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/PasswordService.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/PasswordService.kt deleted file mode 100644 index e1d4254..0000000 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/PasswordService.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.codinux.banking.ui.service - -expect object PasswordService { - - fun hashPassword(password: String): String - - fun checkPassword(password: String, hashedPassword: String): Boolean - -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt b/composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.desktop.kt similarity index 52% rename from composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt rename to composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.desktop.kt index 04d4044..6c05ae6 100644 --- a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt +++ b/composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.desktop.kt @@ -1,8 +1,9 @@ package net.codinux.banking.ui.service import at.favre.lib.crypto.bcrypt.BCrypt +import net.codinux.banking.ui.model.AuthenticationResult -actual object PasswordService { +actual object AuthenticationService { actual fun hashPassword(password: String): String = BCrypt.withDefaults().hashToString(12, password.toCharArray()) @@ -10,4 +11,11 @@ actual object PasswordService { actual fun checkPassword(password: String, hashedPassword: String): Boolean = BCrypt.verifyer().verify(password.toCharArray(), hashedPassword).verified + + actual val supportsBiometricAuthentication = false + + actual fun authenticateWithBiometrics(authenticationResult: (AuthenticationResult) -> Unit) { + authenticationResult(AuthenticationResult(false, "Biometrics is not supported")) + } + } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/PasswordService.desktop.kt b/composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/PasswordService.desktop.kt deleted file mode 100644 index 04d4044..0000000 --- a/composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/PasswordService.desktop.kt +++ /dev/null @@ -1,13 +0,0 @@ -package net.codinux.banking.ui.service - -import at.favre.lib.crypto.bcrypt.BCrypt - -actual object PasswordService { - - actual fun hashPassword(password: String): String = - BCrypt.withDefaults().hashToString(12, password.toCharArray()) - - actual fun checkPassword(password: String, hashedPassword: String): Boolean = - BCrypt.verifyer().verify(password.toCharArray(), hashedPassword).verified - -} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/PasswordService.ios.kt b/composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.ios.kt similarity index 57% rename from composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/PasswordService.ios.kt rename to composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.ios.kt index 2f4abb1..f39e2d0 100644 --- a/composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/PasswordService.ios.kt +++ b/composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.ios.kt @@ -1,6 +1,8 @@ package net.codinux.banking.ui.service -actual object PasswordService { +import net.codinux.banking.ui.model.AuthenticationResult + +actual object AuthenticationService { // for iOS see e.g. // https://medium.com/@mohamed.ma872/strengthening-mobile-app-security-pbkdf2-bcrypt-and-scrypt-for-android-and-ios-8089b0edbf76 @@ -14,4 +16,11 @@ actual object PasswordService { return password == hashedPassword // TODO } + + actual val supportsBiometricAuthentication = false // TODO + + actual fun authenticateWithBiometrics(authenticationResult: (AuthenticationResult) -> Unit) { + authenticationResult(AuthenticationResult(false, "Biometrics is not implemented yet")) + } + } \ No newline at end of file diff --git a/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.js.kt b/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.js.kt new file mode 100644 index 0000000..642100c --- /dev/null +++ b/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/AuthenticationService.js.kt @@ -0,0 +1,22 @@ +package net.codinux.banking.ui.service + +import net.codinux.banking.ui.model.AuthenticationResult + +actual object AuthenticationService { + + actual fun hashPassword(password: String): String { + return password // TODO + } + + actual fun checkPassword(password: String, hashedPassword: String): Boolean { + return password == hashedPassword // TODO + } + + + actual val supportsBiometricAuthentication = false + + actual fun authenticateWithBiometrics(authenticationResult: (AuthenticationResult) -> Unit) { + authenticationResult(AuthenticationResult(false, "Biometrics is not supported")) + } + +} \ No newline at end of file diff --git a/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/PasswordService.js.kt b/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/PasswordService.js.kt deleted file mode 100644 index 73ad936..0000000 --- a/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/PasswordService.js.kt +++ /dev/null @@ -1,13 +0,0 @@ -package net.codinux.banking.ui.service - -actual object PasswordService { - - actual fun hashPassword(password: String): String { - return password // TODO - } - - actual fun checkPassword(password: String, hashedPassword: String): Boolean { - return password == hashedPassword // TODO - } - -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e0cc61..4cd09b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ androidx-core-ktx = "1.13.1" androidx-espresso-core = "3.6.1" androidx-lifecycle = "2.8.0" androidx-material = "1.12.0" +androidx-biometric = "1.1.0" androidx-test-junit = "1.2.1" compose-plugin = "1.6.11" @@ -59,6 +60,7 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidx-biometric" } kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }