Skip to content

Commit

Permalink
Password Decryption Support
Browse files Browse the repository at this point in the history
  • Loading branch information
SAUL committed Dec 2, 2024
1 parent 6d0ebe1 commit 487a28a
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 10 deletions.
28 changes: 28 additions & 0 deletions src/main/kotlin/core/AppState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.screen.Screen
import core.security.MasterPasswordManager
import repository.user.User
import ui.screens.LoginScreen
import ui.screens.LoginSplashScreen
Expand Down Expand Up @@ -53,6 +54,33 @@ class AppState {
masterPassword = null
}

fun isMasterPasswordPresent(): Boolean {
return masterPassword != null
}

fun encryptString(text: String?): String {
return text?.let {
MasterPasswordManager.encryptString(
it,
MasterPasswordManager.getKey(
MasterPasswordManager.convertToUnsecureString(this.fetchMasterPassword()!!)
)
)
} ?: ""
}

fun decryptPassword(text: String?): String {
return text?.let {
MasterPasswordManager.decryptString(
it,
MasterPasswordManager.getKey(
MasterPasswordManager.convertToUnsecureString(this.fetchMasterPassword()!!)
)
)
} ?: ""
}


private val isAuthenticated: Boolean
get() = currentUser != null

Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/core/security/AuthenticationManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ class AuthenticationManager(
loadUserFromToken()
}

suspend fun login(username: String, password: String): Result<User> {
suspend fun login(username: String, password: String, masterPassword: String): Result<User> {
delay(200)
return userRepository.findByUsername(username).let { result ->
if (result is Result.Success && BCrypt.checkpw(password, result.data.password)) {
result.data.also {
appState.updateCurrentUser(it)
appState.initializeMasterPassword(masterPassword.toCharArray())
TokenManager.saveToken(jwtService.generateToken(it))
}.let { user ->
Result.Success(user)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ val viewModelModule = module {

factory { ForgotPasswordScreenModel(get()) }

factory { SecVaultScreenModel(get(), get(), get()) }
factory { SecVaultScreenModel(get(), get(), get(), get { parametersOf(SecVaultScreenModel::class.java) }) }

factory { PasswordMgntScreenModel(get(), get(), get(), get()) }

Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/ui/components/FormScreenComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ fun LoginScreenContent(

var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var masterPassword by remember { mutableStateOf("") }

LoginForm(
username = username,
onUsernameChange = { username = it },
password = password,
onPasswordChange = { password = it },
masterPassword = password,
onMasterPassword = { password = it },
masterPassword = masterPassword,
onMasterPassword = { masterPassword = it },
loginState = loginState,
onLoginClick = { screenModel.login(username, password) },
onLoginClick = { screenModel.login(username, password, masterPassword) },
onForgotPasswordClick = { navigator?.push(ForgotPasswordScreen()) },
onRegisterClick = {
if (navigator?.canPop == true) navigator.pop() else navigator?.push(RegisterScreen())
Expand Down
90 changes: 90 additions & 0 deletions src/main/kotlin/ui/components/dialog/MasterPasswordDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package ui.components.dialog

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.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.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ui.components.PasswordTextField
import ui.components.SecVaultDialog
import ui.theme.Font
import ui.theme.secondary
import ui.theme.tertiary
import viewmodel.SecVaultScreenModel

@Composable
fun MasterPasswordDialog(
viewModel: SecVaultScreenModel,
dialogState: MutableState<Boolean>
) {
var masterPassword by remember { mutableStateOf("") }

SecVaultDialog(
onDismissRequest = { dialogState.value = false },
modifier = Modifier.fillMaxWidth()
.width(50.dp)
.height(400.dp),
roundedSize = 20.dp,
backgroundColor = tertiary
) {

Column(
modifier = Modifier.fillMaxSize()
.fillMaxHeight()
.background(color = tertiary),
verticalArrangement = Arrangement.spacedBy(15.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "Enter your master password.",
color = Color.White,
fontFamily = Font.RussoOne,
fontWeight = FontWeight.Normal,
fontSize = 20.sp,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(10.dp))
PasswordTextField(
value = masterPassword,
onValueChange = { masterPassword = it },
label = "Master Password",
modifier = Modifier.height(40.dp).width(360.dp)
)
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Button(
enabled = true,
onClick = {
viewModel.setMasterPassword(masterPassword)
dialogState.value = false
},
modifier = Modifier.width(175.dp),
colors = ButtonColors(
containerColor = secondary,
contentColor = Color.White,
disabledContentColor = secondary,
disabledContainerColor = secondary
)
)
{
Text(
text = "Apply",
fontStyle = FontStyle.Normal,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
fontFamily = Font.RussoOne
)
}
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ fun PasswordCredentialForm(screenModel: SecVaultScreenModel) {
}

var password by remember(selectedCredential) {
mutableStateOf(selectedCredential.password?.password ?: "")
mutableStateOf(
selectedCredential.password?.password?.let {
screenModel.decryptPassword(it)
} ?: ""
)
}

var email by remember(selectedCredential) {
Expand Down
13 changes: 13 additions & 0 deletions src/main/kotlin/ui/screens/SecVaultScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.koinScreenModel
Expand All @@ -12,6 +14,7 @@ import com.dokar.sonner.Toaster
import com.dokar.sonner.rememberToasterState
import core.models.UiState
import ui.components.LoadingScreen
import ui.components.dialog.MasterPasswordDialog
import ui.components.secvault.SecVaultContentScreen
import ui.theme.tertiary
import viewmodel.SecVaultScreenModel
Expand All @@ -24,6 +27,7 @@ class SecVaultScreen : Screen {

val screenModel = koinScreenModel<SecVaultScreenModel>()
val secVaultState by screenModel.secVaultState.collectAsState()
val masterPasswordDialogState = remember { mutableStateOf(!screenModel.isMasterPasswordPresent()) }
val toaster = rememberToasterState()

LaunchedEffect(Unit) {
Expand Down Expand Up @@ -59,5 +63,14 @@ class SecVaultScreen : Screen {

is UiState.Idle -> {}
}

when {
masterPasswordDialogState.value -> {
MasterPasswordDialog(
dialogState = masterPasswordDialogState,
viewModel = screenModel
)
}
}
}
}
8 changes: 5 additions & 3 deletions src/main/kotlin/viewmodel/LoginScreenModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ class LoginScreenModel(
private val _loginState = MutableStateFlow<UiState<User>>(UiState.Idle)
val loginState: StateFlow<UiState<User>> = _loginState.asStateFlow()

fun login(username: String, password: String) {
fun login(username: String, password: String, masterPassword: String) {
screenModelScope.launch(dispatcher) {
_loginState.value = UiState.Loading
when (val result = authenticationManager.login(username, password)) {
is Result.Success -> _loginState.value = UiState.Success(result.data)
when (val result = authenticationManager.login(username, password, masterPassword)) {
is Result.Success -> {
_loginState.value = UiState.Success(result.data)
}
is Result.Error -> _loginState.value = UiState.Error(result.message)
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/viewmodel/PasswordMgntScreenModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class PasswordMgntScreenModel(
fun savePassword(id: UUID?, password: PasswordDto, formType: FormType) {
saveOrUpdate(
id = id,
dto = password,
dto = password.apply {
password.password = appState.encryptString(password.password)
},
formType = formType,
saveAction = passwordRepository::save,
updateAction = passwordRepository::update,
Expand Down
20 changes: 20 additions & 0 deletions src/main/kotlin/viewmodel/SecVaultScreenModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cafe.adriel.voyager.core.model.screenModelScope
import core.AppState
import core.models.*
import core.models.criteria.CredentialSearchCriteria
import core.security.MasterPasswordManager
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
Expand All @@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import org.slf4j.Logger
import repository.creditcard.CreditCard
import repository.creditcard.CreditCardRepository
import repository.creditcard.projection.CreditCardSummary
Expand All @@ -25,6 +27,7 @@ class SecVaultScreenModel(
private val passwordRepository: PasswordRepository,
private val creditCardRepository: CreditCardRepository,
private val appState: AppState,
private val logger: Logger,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ScreenModel {

Expand Down Expand Up @@ -133,6 +136,23 @@ class SecVaultScreenModel(
}
}

fun decryptPassword(text: String?): String {
try {
return appState.decryptPassword(text)
} catch (e: Exception) {
logger.error(e.message, e)
return "FUCK U"
}
}

fun setMasterPassword(masterPassword: String) {
appState.initializeMasterPassword(MasterPasswordManager.convertToSecureString(masterPassword))
}

fun isMasterPasswordPresent(): Boolean {
return appState.isMasterPasswordPresent()
}

private suspend fun loadPasswords(sort: CredentialSort) {
val criteria = CredentialSearchCriteria(appState.getAuthenticatedUser?.id?.value, sort)
_passwordItems.value = when (val passwords = passwordRepository.findSummaries(criteria)) {
Expand Down

0 comments on commit 487a28a

Please sign in to comment.