mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: prompt user to regenerate compromised keys (#2131)
This commit is contained in:
parent
86905942de
commit
3ef504c567
5 changed files with 109 additions and 18 deletions
|
|
@ -206,13 +206,19 @@ class UIViewModel @Inject constructor(
|
|||
private val _lastTraceRouteTime = MutableStateFlow<Long?>(null)
|
||||
val lastTraceRouteTime: StateFlow<Long?> = _lastTraceRouteTime.asStateFlow()
|
||||
|
||||
val clientNotification: StateFlow<MeshProtos.ClientNotification?> = radioConfigRepository.clientNotification
|
||||
fun clearClientNotification(notification: MeshProtos.ClientNotification) {
|
||||
radioConfigRepository.clearClientNotification()
|
||||
meshServiceNotifications.clearClientNotification(notification)
|
||||
}
|
||||
|
||||
data class AlertData(
|
||||
val title: String,
|
||||
val message: String? = null,
|
||||
val html: String? = null,
|
||||
val onConfirm: (() -> Unit)? = null,
|
||||
val onDismiss: (() -> Unit)? = null,
|
||||
val choices: Map<String, () -> Unit> = emptyMap()
|
||||
val choices: Map<String, () -> Unit> = emptyMap(),
|
||||
)
|
||||
|
||||
private val _currentAlert: MutableStateFlow<AlertData?> = MutableStateFlow(null)
|
||||
|
|
@ -224,7 +230,7 @@ class UIViewModel @Inject constructor(
|
|||
html: String? = null,
|
||||
onConfirm: (() -> Unit)? = {},
|
||||
dismissable: Boolean = true,
|
||||
choices: Map<String, () -> Unit> = emptyMap()
|
||||
choices: Map<String, () -> Unit> = emptyMap(),
|
||||
) {
|
||||
_currentAlert.value =
|
||||
AlertData(
|
||||
|
|
@ -238,7 +244,7 @@ class UIViewModel @Inject constructor(
|
|||
onDismiss = {
|
||||
if (dismissable) dismissAlert()
|
||||
},
|
||||
choices = choices
|
||||
choices = choices,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +291,8 @@ class UIViewModel @Inject constructor(
|
|||
private val onlyDirect = MutableStateFlow(preferences.getBoolean("only-direct", false))
|
||||
|
||||
private val onlyFavorites = MutableStateFlow(preferences.getBoolean("only-favorites", false))
|
||||
private val showWaypointsOnMap = MutableStateFlow(preferences.getBoolean("show-waypoints-on-map", true))
|
||||
private val showWaypointsOnMap =
|
||||
MutableStateFlow(preferences.getBoolean("show-waypoints-on-map", true))
|
||||
private val showPrecisionCircleOnMap =
|
||||
MutableStateFlow(preferences.getBoolean("show-precision-circle-on-map", true))
|
||||
|
||||
|
|
@ -441,18 +448,6 @@ class UIViewModel @Inject constructor(
|
|||
)
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
radioConfigRepository.clientNotification.filterNotNull().onEach { notification ->
|
||||
showAlert(
|
||||
title = app.getString(R.string.client_notification),
|
||||
message = notification.message,
|
||||
onConfirm = {
|
||||
radioConfigRepository.clearClientNotification()
|
||||
meshServiceNotifications.clearClientNotification(notification)
|
||||
},
|
||||
dismissable = false
|
||||
)
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
radioConfigRepository.localConfigFlow.onEach { config ->
|
||||
_localConfig.value = config
|
||||
}.launchIn(viewModelScope)
|
||||
|
|
@ -663,7 +658,8 @@ class UIViewModel @Inject constructor(
|
|||
showSnackbar(R.string.channel_invalid)
|
||||
}
|
||||
|
||||
val latestStableFirmwareRelease = firmwareReleaseRepository.stableRelease.mapNotNull { it?.asDeviceVersion() }
|
||||
val latestStableFirmwareRelease =
|
||||
firmwareReleaseRepository.stableRelease.mapNotNull { it?.asDeviceVersion() }
|
||||
|
||||
/**
|
||||
* Called immediately after activity observes requestChannelUrl
|
||||
|
|
|
|||
|
|
@ -148,6 +148,30 @@ fun MainScreen(
|
|||
}
|
||||
}
|
||||
|
||||
val clientNotification by viewModel.clientNotification.collectAsStateWithLifecycle()
|
||||
clientNotification?.let { notification ->
|
||||
var message = notification.message
|
||||
val compromisedKeys =
|
||||
if (notification.hasLowEntropyKey() || notification.hasDuplicatedPublicKey()) {
|
||||
message = stringResource(R.string.compromised_keys)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
SimpleAlertDialog(
|
||||
title = R.string.client_notification,
|
||||
text = {
|
||||
Text(text = message)
|
||||
},
|
||||
onConfirm = {
|
||||
if (compromisedKeys) {
|
||||
navController.navigate(RadioConfigRoutes.Security)
|
||||
}
|
||||
viewModel.clearClientNotification(notification)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val traceRouteResponse by viewModel.tracerouteResponse.observeAsState()
|
||||
traceRouteResponse?.let { response ->
|
||||
SimpleAlertDialog(
|
||||
|
|
|
|||
|
|
@ -952,6 +952,7 @@ fun TracerouteActionButton(
|
|||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun NodeActionButton(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
enabled: Boolean,
|
||||
icon: ImageVector? = null,
|
||||
|
|
@ -964,7 +965,7 @@ fun NodeActionButton(
|
|||
onClick()
|
||||
},
|
||||
enabled = enabled,
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp)
|
||||
.height(48.dp),
|
||||
|
|
|
|||
|
|
@ -18,9 +18,15 @@
|
|||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Warning
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -30,6 +36,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ConfigProtos.Config.SecurityConfig
|
||||
|
|
@ -42,6 +49,7 @@ import com.geeksville.mesh.ui.common.components.EditListPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.node.NodeActionButton
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.util.encodeToString
|
||||
|
||||
|
|
@ -78,6 +86,17 @@ fun SecurityConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var securityInput by rememberSaveable { mutableStateOf(securityConfig) }
|
||||
|
||||
var showKeyGenerationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
PrivateKeyRegenerateDialog(
|
||||
showKeyGenerationDialog = showKeyGenerationDialog,
|
||||
config = securityInput,
|
||||
onConfirm = { newConfig ->
|
||||
securityInput = newConfig
|
||||
showKeyGenerationDialog = false
|
||||
},
|
||||
onDismiss = { showKeyGenerationDialog = false }
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
|
@ -122,6 +141,18 @@ fun SecurityConfigItemList(
|
|||
)
|
||||
}
|
||||
|
||||
item {
|
||||
NodeActionButton(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
title = stringResource(R.string.regenerate_private_key),
|
||||
enabled = enabled,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = {
|
||||
showKeyGenerationDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditListPreference(
|
||||
title = stringResource(R.string.admin_key),
|
||||
|
|
@ -200,6 +231,42 @@ fun SecurityConfigItemList(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PrivateKeyRegenerateDialog(
|
||||
showKeyGenerationDialog: Boolean,
|
||||
config: SecurityConfig,
|
||||
onConfirm: (SecurityConfig) -> Unit,
|
||||
onDismiss: () -> Unit = {},
|
||||
) {
|
||||
var securityInput by rememberSaveable { mutableStateOf(config) }
|
||||
if (showKeyGenerationDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(text = stringResource(R.string.regenerate_private_key)) },
|
||||
text = { Text(text = stringResource(R.string.regenerate_keys_confirmation)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
securityInput = securityInput.copy {
|
||||
clearPrivateKey()
|
||||
}
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
) {
|
||||
Text(stringResource(R.string.okay))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SecurityConfigPreview() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue