From 9b5449f657844b3f154d706e8f50ca911784e850 Mon Sep 17 00:00:00 2001
From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
Date: Fri, 3 May 2024 16:14:49 -0400
Subject: [PATCH] Enforce Base32 key values (#82)
---
.../feature/edititem/EditItemScreen.kt | 2 ++
.../feature/edititem/EditItemViewModel.kt | 17 +++++++++++++++--
.../manualcodeentry/ManualCodeEntryScreen.kt | 5 ++++-
.../manualcodeentry/ManualCodeEntryViewModel.kt | 13 +++++++++++++
.../feature/qrcodescan/QrCodeScanViewModel.kt | 9 +--------
.../ui/platform/base/util/StringExtensions.kt | 8 ++++++++
.../components/field/BitwardenPasswordField.kt | 5 +++++
app/src/main/res/values/strings.xml | 6 +++++-
8 files changed, 53 insertions(+), 12 deletions(-)
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt
index fe8dce7869..a326412c87 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt
@@ -40,6 +40,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
+import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -255,6 +256,7 @@ fun EditItemContent(
value = viewState.itemData.totpCode,
onValueChange = onTotpCodeTextChange,
singleLine = true,
+ capitalization = KeyboardCapitalization.Characters,
)
}
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt
index 8cc67ffc66..d92c34b9e2 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt
@@ -1,6 +1,8 @@
package com.bitwarden.authenticator.ui.authenticator.feature.edititem
import android.os.Parcelable
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.toUpperCase
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.authenticator.R
@@ -18,6 +20,7 @@ import com.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.bitwarden.authenticator.ui.platform.base.util.Text
import com.bitwarden.authenticator.ui.platform.base.util.asText
import com.bitwarden.authenticator.ui.platform.base.util.concat
+import com.bitwarden.authenticator.ui.platform.base.util.isBase32
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -93,7 +96,17 @@ class EditItemViewModel @Inject constructor(
it.copy(
dialog = EditItemState.DialogState.Generic(
title = R.string.an_error_has_occurred.asText(),
- message = R.string.validation_field_required.asText(R.string.secret_key),
+ message = R.string.validation_field_required.asText(R.string.key),
+ )
+ )
+ }
+ return@onContent
+ } else if (!content.itemData.totpCode.isBase32()) {
+ mutableStateFlow.update {
+ it.copy(
+ dialog = EditItemState.DialogState.Generic(
+ title = R.string.an_error_has_occurred.asText(),
+ message = R.string.key_is_invalid.asText()
)
)
}
@@ -320,7 +333,7 @@ class EditItemViewModel @Inject constructor(
itemData = EditItemData(
refreshPeriod = AuthenticatorRefreshPeriodOption.fromSeconds(period)
?: AuthenticatorRefreshPeriodOption.THIRTY,
- totpCode = key,
+ totpCode = key.toUpperCase(Locale.current),
type = type,
username = accountName,
issuer = issuer,
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt
index baeea08db6..968b5a97fd 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt
@@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -40,6 +41,7 @@ import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicD
import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenLoadingDialog
import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState
+import com.bitwarden.authenticator.ui.platform.components.field.BitwardenPasswordField
import com.bitwarden.authenticator.ui.platform.components.field.BitwardenTextField
import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager
@@ -177,7 +179,7 @@ fun ManualCodeEntryScreen(
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(8.dp))
- BitwardenTextField(
+ BitwardenPasswordField(
singleLine = false,
label = stringResource(id = R.string.key),
value = state.code,
@@ -188,6 +190,7 @@ fun ManualCodeEntryScreen(
)
}
},
+ capitalization = KeyboardCapitalization.Characters,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt
index bf37490c5c..dc20111036 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt
@@ -11,6 +11,7 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.CreateIte
import com.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.bitwarden.authenticator.ui.platform.base.util.Text
import com.bitwarden.authenticator.ui.platform.base.util.asText
+import com.bitwarden.authenticator.ui.platform.base.util.isBase32
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -82,6 +83,17 @@ class ManualCodeEntryViewModel @Inject constructor(
return
}
+ if (!state.code.isBase32()) {
+ mutableStateFlow.update {
+ it.copy(
+ dialog = ManualCodeEntryState.DialogState.Error(
+ message = R.string.key_is_invalid.asText()
+ )
+ )
+ }
+ return
+ }
+
if (state.issuer.isBlank()) {
mutableStateFlow.update {
it.copy(
@@ -92,6 +104,7 @@ class ManualCodeEntryViewModel @Inject constructor(
}
return
}
+
viewModelScope.launch {
val result = authenticatorRepository.createItem(
AuthenticatorItemEntity(
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt
index 9ab99385c7..b8b84bfe9e 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt
@@ -5,6 +5,7 @@ import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRe
import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult
import com.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.bitwarden.authenticator.ui.platform.base.util.Text
+import com.bitwarden.authenticator.ui.platform.base.util.isBase32
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@@ -160,11 +161,3 @@ sealed class QrCodeScanAction {
*/
data object CameraSetupErrorReceive : QrCodeScanAction()
}
-
-/**
- * Checks if a string is using base32 digits.
- */
-private fun String.isBase32(): Boolean {
- val regex = ("^[A-Z2-7]+=*$").toRegex()
- return regex.matches(this)
-}
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt
index 53c8ea9ead..481f765421 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt
@@ -72,3 +72,11 @@ fun String.removeDiacritics(): String =
Normalizer.normalize(this, Normalizer.Form.NFKD),
"",
)
+
+/**
+ * Checks if a string is using base32 digits.
+ */
+fun String.isBase32(): Boolean {
+ val regex = ("^[A-Z2-7]+=*$").toRegex()
+ return regex.matches(this)
+}
diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt
index 6140482c8a..0f75ac961b 100644
--- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt
+++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt
@@ -23,6 +23,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
@@ -67,6 +68,7 @@ fun BitwardenPasswordField(
autoFocus: Boolean = false,
keyboardType: KeyboardType = KeyboardType.Password,
imeAction: ImeAction = ImeAction.Default,
+ capitalization: KeyboardCapitalization = KeyboardCapitalization.None,
) {
val focusRequester = remember { FocusRequester() }
OutlinedTextField(
@@ -83,6 +85,7 @@ fun BitwardenPasswordField(
singleLine = singleLine,
readOnly = readOnly,
keyboardOptions = KeyboardOptions(
+ capitalization = capitalization,
keyboardType = keyboardType,
imeAction = imeAction,
),
@@ -156,6 +159,7 @@ fun BitwardenPasswordField(
autoFocus: Boolean = false,
keyboardType: KeyboardType = KeyboardType.Password,
imeAction: ImeAction = ImeAction.Default,
+ capitalization: KeyboardCapitalization = KeyboardCapitalization.None,
) {
var showPassword by rememberSaveable { mutableStateOf(initialShowPassword) }
BitwardenPasswordField(
@@ -172,6 +176,7 @@ fun BitwardenPasswordField(
autoFocus = autoFocus,
keyboardType = keyboardType,
imeAction = imeAction,
+ capitalization = capitalization,
)
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 14f2739e72..45f2892b64 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -44,7 +44,6 @@
Number of digits
Save
The %1$s field is required.
- Secret key
%d seconds
Saving
Item saved
@@ -108,4 +107,9 @@
Key is required.
Name is required.
Submit crash logs
+ There was a problem importing your vault.
+ File Source
+ Import
+ Vault import successful
+ Key is invalid.