Enforce Base32 key values (#82)

This commit is contained in:
Patrick Honkonen
2024-05-03 16:14:49 -04:00
committed by GitHub
parent 6da31f797d
commit 9b5449f657
8 changed files with 53 additions and 12 deletions

View File

@@ -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,
)
}

View File

@@ -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,

View File

@@ -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),

View File

@@ -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(

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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,
)
}

View File

@@ -44,7 +44,6 @@
<string name="number_of_digits">Number of digits</string>
<string name="save">Save</string>
<string name="validation_field_required">The %1$s field is required.</string>
<string name="secret_key">Secret key</string>
<string name="refresh_period_seconds" tools:ignore="PluralsCandidate">%d seconds</string>
<string name="saving">Saving</string>
<string name="item_saved">Item saved</string>
@@ -108,4 +107,9 @@
<string name="key_is_required">Key is required.</string>
<string name="name_is_required">Name is required.</string>
<string name="submit_crash_logs">Submit crash logs</string>
<string name="import_vault_failure">There was a problem importing your vault.</string>
<string name="file_source">File Source</string>
<string name="import_vault">Import</string>
<string name="import_success">Vault import successful</string>
<string name="key_is_invalid">Key is invalid.</string>
</resources>