Require passphrase confirmation for autobackup setting and encrypted export
A typo in a write-only passphrase field encrypts every backup with a phrase the user cannot reproduce — unrecoverable by design. Both the settings field and the manual-export dialog now demand a matching repeat before accepting. Import is unchanged (wrong passphrase there just fails loudly). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3ce003d336
commit
0dd48445e1
1 changed files with 35 additions and 1 deletions
|
|
@ -62,6 +62,7 @@ fun SettingsScreen(nav: Nav) {
|
|||
var accessKey by remember { mutableStateOf(settings.s3AccessKey ?: "") }
|
||||
var secretKey by remember { mutableStateOf(settings.s3SecretKey ?: "") }
|
||||
var autoPassphrase by remember { mutableStateOf(settings.autoBackupPassphrase ?: "") }
|
||||
var autoPassphraseConfirm by remember { mutableStateOf(settings.autoBackupPassphrase ?: "") }
|
||||
var festUrl by remember { mutableStateOf(settings.festUrl ?: "") }
|
||||
|
||||
// Export/import passphrase dialogs hold the pending document URI.
|
||||
|
|
@ -134,8 +135,24 @@ fun SettingsScreen(nav: Nav) {
|
|||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
// Confirmation field: a typo here would encrypt every backup with a
|
||||
// passphrase the user can't reproduce — unrecoverable by design.
|
||||
val passphraseMismatch = autoPassphrase != autoPassphraseConfirm
|
||||
OutlinedTextField(
|
||||
value = autoPassphraseConfirm, onValueChange = { autoPassphraseConfirm = it },
|
||||
label = { Text("Gjenta passordfrase") }, singleLine = true,
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
isError = passphraseMismatch,
|
||||
supportingText = { if (passphraseMismatch) Text("Passordfrasene er ikke like") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
if (passphraseMismatch) {
|
||||
toast("Passordfrasene er ikke like")
|
||||
return@Button
|
||||
}
|
||||
settings.s3Endpoint = endpoint.trim().ifBlank { null }
|
||||
settings.s3Region = region.trim().ifBlank { null }
|
||||
settings.s3Bucket = bucket.trim().ifBlank { null }
|
||||
|
|
@ -144,6 +161,7 @@ fun SettingsScreen(nav: Nav) {
|
|||
settings.autoBackupPassphrase = autoPassphrase.ifBlank { null }
|
||||
toast("Lagret")
|
||||
},
|
||||
enabled = !passphraseMismatch,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Lagre backupinnstillinger") }
|
||||
|
||||
|
|
@ -214,6 +232,7 @@ fun SettingsScreen(nav: Nav) {
|
|||
title = "Krypter eksport",
|
||||
text = "Skriv en passordfrase (lagres ikke). La stå tom for ukryptert JSON.",
|
||||
allowEmpty = true,
|
||||
requireConfirmation = true, // a typo here = an unrecoverable export
|
||||
onDismiss = { exportUri = null },
|
||||
) { passphrase ->
|
||||
exportUri = null
|
||||
|
|
@ -306,10 +325,13 @@ private fun PassphraseDialog(
|
|||
title: String,
|
||||
text: String,
|
||||
allowEmpty: Boolean,
|
||||
requireConfirmation: Boolean = false,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (CharArray) -> Unit,
|
||||
) {
|
||||
var value by remember { mutableStateOf("") }
|
||||
var repeated by remember { mutableStateOf("") }
|
||||
val mismatch = requireConfirmation && value != repeated
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(title) },
|
||||
|
|
@ -324,12 +346,24 @@ private fun PassphraseDialog(
|
|||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
)
|
||||
if (requireConfirmation) {
|
||||
OutlinedTextField(
|
||||
value = repeated,
|
||||
onValueChange = { repeated = it },
|
||||
label = { Text("Gjenta passordfrase") },
|
||||
singleLine = true,
|
||||
isError = mismatch,
|
||||
supportingText = { if (mismatch) Text("Passordfrasene er ikke like") },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onConfirm(value.toCharArray()) },
|
||||
enabled = allowEmpty || value.isNotEmpty(),
|
||||
enabled = (allowEmpty || value.isNotEmpty()) && !mismatch,
|
||||
) { Text("OK") }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Avbryt") } },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue