Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Add fields to manual OTP configuration screen
Browse files Browse the repository at this point in the history
  • Loading branch information
rafi612 committed Feb 28, 2024
1 parent 28bcd3f commit d678cc4
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ import dev.medzik.librepass.android.ui.screens.settings.account.SettingsAccountD
import dev.medzik.librepass.android.ui.screens.vault.CipherAddScreen
import dev.medzik.librepass.android.ui.screens.vault.CipherEditScreen
import dev.medzik.librepass.android.ui.screens.vault.CipherViewScreen
import dev.medzik.librepass.android.ui.screens.vault.OtpConfigure
import dev.medzik.librepass.android.ui.screens.vault.PasswordGeneratorScreen
import dev.medzik.librepass.android.ui.screens.vault.SearchScreen
import dev.medzik.librepass.android.ui.screens.vault.TotpConfigure
import dev.medzik.librepass.android.ui.screens.vault.VaultScreen

enum class Argument : NavArgument {
Expand Down Expand Up @@ -201,7 +201,7 @@ enum class Screen(
),
TotpConfigure(
customScaffold = true,
composable = { TotpConfigure(it) }
composable = { OtpConfigure(it) }
),
PasswordGenerator(
topBar = {
Expand Down Expand Up @@ -314,7 +314,7 @@ fun LibrePassNavigation(viewModel: LibrePassViewModel = hiltViewModel()) {
viewModel.credentialRepository.get() ?: return Screen.Welcome.getRoute()

// if user secrets are not set, show unlock screen
if (viewModel.vault.aesKey.size == 0)
if (viewModel.vault.aesKey.isEmpty())
return Screen.Unlock.getRoute()

// else where the user secrets are set, show vault screen
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.medzik.librepass.android.ui.components

import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
Expand All @@ -15,9 +16,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.gson.Gson
import dev.medzik.android.components.SecondaryText
Expand All @@ -44,12 +43,13 @@ fun CipherEditFieldsLogin(
?.getLiveData<String>("password")?.observeForever {
cipherData = cipherData.copy(password = it)
}
// observe totp uri from navController
// used to get totp uri from totp configuration screen
// observe otp uri from navController
// used to get otp uri from otp configuration screen
navController
.currentBackStackEntry
?.savedStateHandle
?.getLiveData<String>("totpUri")?.observeForever {
?.getLiveData<String>("otpUri")?.observeForever {
Log.d("OTP_OBSERVABLE", "Received URI: $it")
cipherData = cipherData.copy(twoFactor = it)
}
// observe for cipher from backstack
Expand Down Expand Up @@ -174,34 +174,6 @@ fun CipherEditFieldsLogin(
modifier = Modifier.padding(top = 8.dp)
)

// val checkOtp =
// !cipher.loginData?.twoFactor.isNullOrEmpty() &&
// runCatching {
// TOTPGenerator.fromURI(URI(cipher.loginData?.twoFactor)).now()
// }.isFailure
//
// TextInputFieldBase(
// label = stringResource(R.string.AuthenticationKey),
// modifier =
// Modifier
// .fillMaxWidth()
// .padding(vertical = 4.dp),
// value = cipherData.twoFactor,
// isError = checkOtp,
// onValueChange = { cipherData = cipherData.copy(twoFactor = it) }
// )
//
// if (checkOtp) {
// Text(
// text = stringResource(R.string.Error_InvalidURI),
// color = MaterialTheme.colorScheme.error,
// fontSize = 12.sp,
// modifier =
// Modifier
// .padding(horizontal = 6.dp),
// )
// }

Button(
onClick = {
navController.navigate(screen = Screen.TotpConfigure)
Expand All @@ -216,15 +188,16 @@ fun CipherEditFieldsLogin(
}

if (!cipher.loginData?.twoFactor.isNullOrEmpty()) {
Text(
text = stringResource(R.string.TotpWasConfigured),
fontSize = 14.sp,
textAlign = TextAlign.Center,
Button(
onClick = { cipherData = cipherData.copy(twoFactor = null) },
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
)
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
) {
Text(stringResource(R.string.DeleteTotp))
}
}

SecondaryText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,28 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import dev.medzik.android.components.ComboBoxDropdown
import dev.medzik.android.components.rememberMutable
import dev.medzik.librepass.android.R
import dev.medzik.librepass.android.ui.LibrePassViewModel
import dev.medzik.librepass.android.ui.components.Permission
import dev.medzik.librepass.android.ui.components.QrCodeScanner
import dev.medzik.librepass.android.ui.components.TextInputFieldBase
import dev.medzik.librepass.android.ui.components.TopBar
import dev.medzik.librepass.android.ui.components.TopBarBackIcon
import dev.medzik.otp.OTPParameters
import dev.medzik.otp.OTPParser
import dev.medzik.otp.OTPType
import dev.medzik.otp.TOTPGenerator

@Composable
fun TotpConfigure(
fun OtpConfigure(
navController: NavController,
viewModel: LibrePassViewModel = hiltViewModel()
) {
var totpCode by rememberMutable("")

Scaffold(
topBar = {
TopBar(
Expand All @@ -50,10 +49,10 @@ fun TotpConfigure(
) { innerPadding ->
Column(
modifier =
Modifier
.padding(innerPadding)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
Modifier
.padding(innerPadding)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
var qrScanning by rememberMutable(true)

Expand All @@ -62,9 +61,9 @@ fun TotpConfigure(
text = stringResource(R.string.ScanQrCode),
fontSize = 24.sp,
modifier =
Modifier
.fillMaxSize()
.padding(16.dp),
Modifier
.fillMaxSize()
.padding(16.dp),
textAlign = TextAlign.Center
)

Expand All @@ -89,7 +88,7 @@ fun TotpConfigure(
totpUri = it

if (isTotpValid) {
navController.previousBackStackEntry!!.savedStateHandle["totpUri"] = totpUri
navController.previousBackStackEntry!!.savedStateHandle["otpUri"] = totpUri
navController.popBackStack()
}
}
Expand All @@ -116,9 +115,9 @@ fun TotpConfigure(
fontSize = 18.sp,
color = MaterialTheme.colorScheme.error,
modifier =
Modifier
.fillMaxSize()
.padding(16.dp),
Modifier
.fillMaxSize()
.padding(16.dp),
textAlign = TextAlign.Center
)
}
Expand All @@ -129,53 +128,127 @@ fun TotpConfigure(
qrScanning = false
},
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
Modifier
.fillMaxWidth()
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
) {
Text(stringResource(R.string.EnterKeyManually))
}
} else {

var totpSecret by rememberMutable("")

var digits by rememberMutable("6")
var type by rememberMutable(OTPType.TOTP)
var algorithm by rememberMutable(OTPParameters.Algorithm.SHA256)

var period by rememberMutable("30")
var counter by rememberMutable("0")

TextInputFieldBase(
label = stringResource(R.string.TotpKey),
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
value = totpCode,
onValueChange = { totpCode = it }
Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
value = totpSecret,
onValueChange = { totpSecret = it }
)

ComboBoxDropdown(
values = OTPType.entries.toTypedArray(),
value = type,
modifier = Modifier.fillMaxWidth(),
label = {
Text(stringResource(R.string.Type))
},
onValueChange = { type = it }
)

ComboBoxDropdown(
values = OTPParameters.Algorithm.entries.toTypedArray(),
value = algorithm,
modifier = Modifier.fillMaxWidth(),
label = {
Text(stringResource(R.string.Algorithm))
},
onValueChange = { algorithm = it }
)

TextInputFieldBase(
label = stringResource(R.string.Digits),
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
keyboardType = KeyboardType.Number,
value = digits,
onValueChange = { digits = it}
)

TextInputFieldBase(
label = if (type == OTPType.TOTP) stringResource(R.string.Period) else stringResource(R.string.Counter),
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
keyboardType = KeyboardType.Number,
value = (if (type == OTPType.TOTP) period else counter).toString(),
onValueChange = {
if (type == OTPType.TOTP)
period = it
else
counter = it
}
)

var otpUri by rememberMutable("")
val otpCodeError = runCatching {
otpUri = calculateOtpCode(totpSecret,type,digits.toInt(),period.toInt(),counter.toLong())
}.isSuccess

Button(
onClick = { qrScanning = true },
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
Modifier
.fillMaxWidth()
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
) {
Text(stringResource(R.string.ScanQrCode))
}

Button(
onClick = {
// TODO: add building URI from totpCode

// remove remember
// val totpCodeValue = totpCode
// navController.previousBackStackEntry!!.savedStateHandle["totpUri"] = totpCodeValue
navController.previousBackStackEntry!!.savedStateHandle["otpUri"] = otpUri
navController.popBackStack()
},
enabled = totpSecret.isNotEmpty() && otpCodeError,
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
Modifier
.fillMaxWidth()
.padding(horizontal = 60.dp)
.padding(top = 8.dp)
) {
Text(stringResource(R.string.Save))
}
}
}
}
}

fun calculateOtpCode(secret: String,type: OTPType,digits: Int,period: Int,counter: Long): String {
val otpBuilder = OTPParameters.builder()
.type(type)
.digits(OTPParameters.Digits.valueOf(digits))
.secret(OTPParameters.Secret(secret))
.label(OTPParameters.Label(""))

when (type) {
OTPType.TOTP -> otpBuilder.period(OTPParameters.Period.valueOf(period))
OTPType.HOTP -> otpBuilder.counter(OTPParameters.Counter(counter))
}

return otpBuilder.build().buildOTPAuthURL()
}
7 changes: 6 additions & 1 deletion app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,10 @@
<string name="TotpConfigure">Skonfiguruj TOTP</string>
<string name="ScanQrCode">Zeskanuj kod QR</string>
<string name="EnterKeyManually">Wprowadź klucz manualnie</string>
<string name="TotpWasConfigured">TOTP został skonfigurowany</string>
<string name="DeleteTotp">Usuń TOTP</string>
<string name="Type">Typ</string>
<string name="Algorithm">Algorytm</string>
<string name="Digits">Cyfry</string>
<string name="Period">Okres</string>
<string name="Counter">Licznik</string>
</resources>
7 changes: 6 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,10 @@
<string name="TotpConfigure">Configure TOTP</string>
<string name="ScanQrCode">Scan QR code</string>
<string name="EnterKeyManually">Enter key manually</string>
<string name="TotpWasConfigured">TOTP is already configured</string>
<string name="DeleteTotp">Delete TOTP</string>
<string name="Type">Type</string>
<string name="Algorithm">Algorithm</string>
<string name="Digits">Digits</string>
<string name="Period">Period</string>
<string name="Counter">Counter</string>
</resources>
Loading

0 comments on commit d678cc4

Please sign in to comment.