diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ff81f94..482892f 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,7 +1,6 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig plugins { @@ -94,6 +93,8 @@ kotlin { implementation(libs.androidx.activity.compose) implementation(libs.sqldelight.android.driver) + + implementation(libs.favre.bcrypt) } nativeMain.dependencies { @@ -110,6 +111,8 @@ kotlin { implementation(libs.sqldelight.sqlite.driver) + implementation(libs.favre.bcrypt) + implementation(libs.logback) } } diff --git a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/forms/SegmentedControlPreview.kt b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/forms/SegmentedControlPreview.kt new file mode 100644 index 0000000..7e5886e --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/forms/SegmentedControlPreview.kt @@ -0,0 +1,24 @@ +package net.codinux.banking.ui.forms + +import androidx.compose.runtime.* +import androidx.compose.ui.tooling.preview.Preview + +@Preview +@Composable +fun SegmentedControlPreview() { + SegmentedControl( + options = listOf("Option 1", "Option 2", "Option 3"), + selectedOption = "Option 1", + onOptionSelected = { } + ) +} + +@Preview +@Composable +fun SegmentedControlPreview_OnlyTwoOptions() { + SegmentedControl( + options = listOf("Option 1", "Option 2"), + selectedOption = "Option 2", + onOptionSelected = { } + ) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialogPreview.kt b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialogPreview.kt new file mode 100644 index 0000000..db63521 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialogPreview.kt @@ -0,0 +1,15 @@ +package net.codinux.banking.ui.screens + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import net.codinux.banking.ui.model.settings.AppAuthenticationMethod +import net.codinux.banking.ui.model.settings.AppSettings + +@Preview +@Composable +fun ProtectAppSettingsDialogPreview() { + val appSettings = AppSettings(AppAuthenticationMethod.Password) + + ProtectAppSettingsDialog(appSettings) { } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt new file mode 100644 index 0000000..04d4044 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/service/PasswordService.android.kt @@ -0,0 +1,13 @@ +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/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt index 787895f..ad9ebf6 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt @@ -7,6 +7,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.SaveAs +import androidx.compose.material.icons.outlined.Key import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope @@ -124,6 +125,15 @@ fun SideMenuContent() { drawerState.close() } } + + NavigationMenuItem(itemModifier, "Appzugang schützen", textColor, horizontalPadding = ItemHorizontalPadding, + icon = { Icon(Icons.Outlined.Key, "Appzugang durch Passwort oder Biometrieeingabe schützen", Modifier.size(iconSize), tint = textColor) }) { + uiState.showProtectAppSettingsScreen.value = true + + coroutineScope.launch { + drawerState.close() + } + } } } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt index efd3255..e6ce246 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt @@ -15,10 +15,13 @@ private val formatUtil = DI.formatUtil fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState() val showTransferMoneyDialogData by uiState.showTransferMoneyDialogData.collectAsState() + val showAccountTransactionDetailsScreenForId by uiState.showAccountTransactionDetailsScreenForId.collectAsState() val showBankSettingsScreenForBank by uiState.showBankSettingsScreenForBank.collectAsState() val showBankAccountSettingsScreenForAccount by uiState.showBankAccountSettingsScreenForAccount.collectAsState() + val showExportScreen by uiState.showExportScreen.collectAsState() + val showProtectAppSettingsScreen by uiState.showProtectAppSettingsScreen.collectAsState() val tanChallengeReceived by uiState.tanChallengeReceived.collectAsState() val bankingClientError by uiState.bankingClientErrorOccurred.collectAsState() @@ -35,6 +38,7 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { TransferMoneyDialog(data) { uiState.showTransferMoneyDialogData.value = null } } + showAccountTransactionDetailsScreenForId?.let { transactionId -> DI.bankingService.getTransaction(transactionId)?.let { transaction -> AccountTransactionDetailsScreen(transaction) { uiState.showAccountTransactionDetailsScreenForId.value = null } @@ -49,10 +53,15 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { BankAccountSettingsScreen(account) { uiState.showBankAccountSettingsScreenForAccount.value = null } } + if (showExportScreen) { ExportScreen { uiState.showExportScreen.value = false } } + if (showProtectAppSettingsScreen) { + ProtectAppSettingsDialog(uiState.appSettings.value) { uiState.showProtectAppSettingsScreen.value = false } + } + tanChallengeReceived?.let { tanChallengeReceived -> EnterTanDialog(tanChallengeReceived) { diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt index 2b3c965..c655760 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt @@ -3,6 +3,7 @@ package net.codinux.banking.ui.config import net.codinux.banking.client.model.BankAccountType import net.codinux.banking.client.model.tan.ActionRequiringTan import net.codinux.banking.ui.model.TransactionsGrouping +import net.codinux.banking.ui.model.settings.AppAuthenticationMethod object Internationalization { @@ -44,4 +45,10 @@ object Internationalization { BankAccountType.Other -> "Sonstige" } + fun translate(authenticationMethod: AppAuthenticationMethod): String = when (authenticationMethod) { + AppAuthenticationMethod.None -> "Ungeschützt" + AppAuthenticationMethod.Password -> "Passwort" + AppAuthenticationMethod.Biometric -> "Biometrie" + } + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt index 2833564..880d083 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp @Composable // try BasicSecureTextField -fun PasswordTextField(password: String = "", label: String = "Passwort", forceHidePassword: Boolean? = null, onEnterPressed: (() -> Unit)? = null, onChange: (String) -> Unit) { +fun PasswordTextField(password: String = "", label: String = "Passwort", modifier: Modifier = Modifier, keyboardOptions: KeyboardOptions? = null, forceHidePassword: Boolean? = null, onEnterPressed: (() -> Unit)? = null, onChange: (String) -> Unit) { var passwordVisible by remember { mutableStateOf(false) } @@ -29,7 +29,7 @@ fun PasswordTextField(password: String = "", label: String = "Passwort", forceHi value = password, onValueChange = { onChange(it) }, label = { Text(label) }, - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), trailingIcon = { val visibilityIcon = if (passwordVisible) { @@ -43,7 +43,7 @@ fun PasswordTextField(password: String = "", label: String = "Passwort", forceHi modifier = Modifier.size(24.dp).clickable { passwordVisible = !passwordVisible } ) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + keyboardOptions = keyboardOptions?.copy(keyboardType = KeyboardType.Password) ?: KeyboardOptions(keyboardType = KeyboardType.Password), onEnterPressed = onEnterPressed ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/SegmentedControl.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/SegmentedControl.kt new file mode 100644 index 0000000..32a23ea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/SegmentedControl.kt @@ -0,0 +1,68 @@ +package net.codinux.banking.ui.forms + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import net.codinux.banking.ui.config.Colors + +@Composable +fun SegmentedControl( + options: Collection, + selectedOption: T, + modifier: Modifier = Modifier, + color: Color = Colors.Accent, + cornerSize: Dp = 8.dp, + getOptionDisplayText: ((T) -> String)? = null, + onOptionSelected: (T) -> Unit +) { + + Row(horizontalArrangement = Arrangement.Center) { + Row(modifier.height(48.dp).border(2.dp, color, RoundedCornerShape(cornerSize))) { + options.forEachIndexed { index, option -> + val isSelected = option == selectedOption + val backgroundColor = if (isSelected) color else Color.Transparent + val textColor = if (isSelected) Color.White else color + + Box( + modifier = Modifier + .clickable { onOptionSelected(option) } + .fillMaxHeight() + .weight(1f) + .let { + if (index == 0) { + it.background(backgroundColor, RoundedCornerShape(topStart = cornerSize, bottomStart = cornerSize)) + } else if (index == options.size - 1) { + it.background(backgroundColor, RoundedCornerShape(topEnd = cornerSize, bottomEnd = cornerSize)) + } else { + it.background(backgroundColor) + } + } + .padding(vertical = 8.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = getOptionDisplayText?.invoke(option) ?: option.toString(), + color = textColor, + textAlign = TextAlign.Center + ) + } + + if (index < options.size - 1) { + Divider(Modifier.fillMaxHeight().width(1.dp), color = color) + } + } + } + } + +} diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/FullscreenViewBase.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/FullscreenViewBase.kt index 6043570..5dccdf1 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/FullscreenViewBase.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/FullscreenViewBase.kt @@ -23,6 +23,7 @@ fun FullscreenViewBase( title: String, confirmButtonTitle: String = "OK", confirmButtonEnabled: Boolean = true, + showButtonBar: Boolean = true, onClosed: () -> Unit, content: @Composable () -> Unit ) { @@ -46,19 +47,21 @@ fun FullscreenViewBase( content() } - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { -// TextButton(onClick = onClosed, Modifier.width(Style.DialogButtonWidth)) { -// Text("Abbrechen", color = Colors.CodinuxSecondaryColor) -// } + if (showButtonBar) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { +// TextButton(onClick = onClosed, Modifier.width(Style.DialogButtonWidth)) { +// Text("Abbrechen", color = Colors.CodinuxSecondaryColor) +// } // -// Spacer(Modifier.width(8.dp)) +// Spacer(Modifier.width(8.dp)) - TextButton( - modifier = Modifier.fillMaxWidth(), - enabled = confirmButtonEnabled, - onClick = { /* onConfirm?.invoke() ?: */ onClosed() } - ) { - Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center) + TextButton( + modifier = Modifier.fillMaxWidth(), + enabled = confirmButtonEnabled, + onClick = { /* onConfirm?.invoke() ?: */ onClosed() } + ) { + Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center) + } } } } 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 new file mode 100644 index 0000000..9661f6f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/ProtectAppSettingsDialog.kt @@ -0,0 +1,125 @@ +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 +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +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.config.Colors +import net.codinux.banking.ui.config.DI +import net.codinux.banking.ui.config.Internationalization +import net.codinux.banking.ui.extensions.verticalScroll +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 + + +private val buttonHeight = 50.dp + +@Composable +fun ProtectAppSettingsDialog(appSettings: AppSettings, onClosed: () -> Unit) { + val currentAuthenticationMethod = appSettings.authenticationMethod + + val isBiometricAuthenticationSupported = false + + val supportedAuthenticationMethods = buildList { + add(AppAuthenticationMethod.Password) + if (isBiometricAuthenticationSupported) { + add(AppAuthenticationMethod.Biometric) + } + add(AppAuthenticationMethod.None) + } + + + var selectedAuthenticationMethod by remember { mutableStateOf(if (appSettings.authenticationMethod == AppAuthenticationMethod.None) AppAuthenticationMethod.Password else appSettings.authenticationMethod) } + + var newPassword by remember { mutableStateOf("") } + + var confirmedNewPassword by remember { mutableStateOf("") } + + val isRequiredDataEntered by remember(newPassword, confirmedNewPassword) { + derivedStateOf { newPassword.isNotBlank() && newPassword == confirmedNewPassword } + } + + val coroutineScope = rememberCoroutineScope() + + + fun saveAppSettings(appSettings: AppSettings) { + coroutineScope.launch { + 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)) { + + SegmentedControl(supportedAuthenticationMethods, selectedAuthenticationMethod, Modifier.padding(bottom = 20.dp), getOptionDisplayText = { Internationalization.translate(it) }) { + selectedAuthenticationMethod = it + } + + Column(Modifier.weight(1f).verticalScroll()) { + Spacer(Modifier.weight(1f)) + + if (selectedAuthenticationMethod == AppAuthenticationMethod.None) { + Row(Modifier.fillMaxWidth()) { + if (currentAuthenticationMethod == AppAuthenticationMethod.None) { + Text("Appzugangsschutz ist bereits ungeschützt", fontSize = 18.sp, textAlign = TextAlign.Center) + } else { + Text("Möchten Sie den Appzugangsschutz wirklich entfernen?", fontSize = 18.sp, textAlign = TextAlign.Center) + } + } + } + + if (selectedAuthenticationMethod == AppAuthenticationMethod.Password) { + PasswordTextField(newPassword, "Neues Password", keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)) { newPassword = it } + + PasswordTextField(confirmedNewPassword, "Password bestätigen", Modifier.padding(top = 16.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)) { confirmedNewPassword = it } + } + + 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() }) { + Text("Appzugangsschutz entfernen", color = Color.White) + } + } else { + Button(modifier = Modifier.fillMaxWidth().height(buttonHeight), enabled = isRequiredDataEntered, + colors = ButtonDefaults.buttonColors(Colors.Accent), onClick = { setAppPasswordProtection() }) { + Text("Setzen", color = Color.White) + } + } + } + } + } + +} \ 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 new file mode 100644 index 0000000..e1d4254 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/PasswordService.kt @@ -0,0 +1,9 @@ +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/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt index 7957156..24b11a5 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt @@ -63,6 +63,8 @@ class UiState : ViewModel() { val showExportScreen = MutableStateFlow(false) + val showProtectAppSettingsScreen = MutableStateFlow(false) + val tanChallengeReceived = MutableStateFlow(null) 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 new file mode 100644 index 0000000..04d4044 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/net/codinux/banking/ui/service/PasswordService.desktop.kt @@ -0,0 +1,13 @@ +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/PasswordService.ios.kt new file mode 100644 index 0000000..2f4abb1 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/net/codinux/banking/ui/service/PasswordService.ios.kt @@ -0,0 +1,17 @@ +package net.codinux.banking.ui.service + +actual object PasswordService { + + // for iOS see e.g. + // https://medium.com/@mohamed.ma872/strengthening-mobile-app-security-pbkdf2-bcrypt-and-scrypt-for-android-and-ios-8089b0edbf76 + // https://github.com/felipeflorencio/BCryptSwift + + 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/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 new file mode 100644 index 0000000..73ad936 --- /dev/null +++ b/composeApp/src/jsMain/kotlin/net/codinux/banking/ui/service/PasswordService.js.kt @@ -0,0 +1,13 @@ +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 3cb90a1..2e0cc61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,8 @@ banking-client = "0.6.0" kcsv = "2.2.0" kotlinx-serializable = "1.7.1" +favre-bcrypt = "0.10.2" + klf = "1.6.1" logback = "1.5.7" @@ -36,6 +38,8 @@ kcsv = { group = "net.codinux.csv", name = "kcsv", version.ref = "kcsv" } coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } kotlinx-serializable = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serializable" } +favre-bcrypt = { group = "at.favre.lib", name = "bcrypt", version.ref = "favre-bcrypt" } + klf = { group = "net.codinux.log", name = "klf", version.ref = "klf" } logback = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }