Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions app/src/main/java/org/session/libsignal/utilities/Validation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,24 @@ object PublicKeyValidation {
private val HEX_CHARACTERS = "0123456789ABCDEFabcdef".toSet()
private val INVALID_PREFIXES = setOf(IdPrefix.GROUP, IdPrefix.BLINDED, IdPrefix.BLINDEDV2)

fun isValid(candidate: String, isPrefixRequired: Boolean = true): Boolean = hasValidLength(candidate) && isValidHexEncoding(candidate) && (!isPrefixRequired || IdPrefix.fromValue(candidate) != null)
fun isValid(candidate: String, isPrefixRequired: Boolean = true): Boolean {
if (!hasValidLength(candidate)) return false

val prefix = IdPrefix.fromValue(candidate)

// Handle invalid Account ID conditions
// Case 1: Standard prefix "05" but not valid hex
if (prefix == IdPrefix.STANDARD && !isValidHexEncoding(candidate)) return false

// Case 2: Blinded or Group IDs should never be accepted as valid Account IDs
if (prefix in INVALID_PREFIXES) return false

// Standard validity rules
return isValidHexEncoding(candidate) &&
(!isPrefixRequired || prefix != null)
}

fun hasValidPrefix(candidate: String) = IdPrefix.fromValue(candidate) !in INVALID_PREFIXES
private fun hasValidLength(candidate: String) = candidate.length == 66
fun hasValidLength(candidate: String) = candidate.length == 66
private fun isValidHexEncoding(candidate: String) = HEX_CHARACTERS.containsAll(candidate.toSet())
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
Expand All @@ -39,6 +41,7 @@ import org.thoughtcrime.securesms.home.startconversation.newmessage.State
import org.thoughtcrime.securesms.openUrl
import org.thoughtcrime.securesms.ui.NavigationAction
import org.thoughtcrime.securesms.ui.ObserveAsEvents
import org.thoughtcrime.securesms.ui.OpenURLAlertDialog
import org.thoughtcrime.securesms.ui.UINavigator
import org.thoughtcrime.securesms.ui.components.BaseBottomSheet
import org.thoughtcrime.securesms.ui.horizontalSlideComposable
Expand Down Expand Up @@ -152,13 +155,17 @@ fun StartConversationNavHost(
val viewModel = hiltViewModel<NewMessageViewModel>()
val uiState by viewModel.state.collectAsState(State())

val helpUrl = "https://getsession.org/account-ids"

LaunchedEffect(Unit) {
scope.launch {
viewModel.success.collect {
context.startActivity(ConversationActivityV2.createIntent(
context,
address = it.address
))
context.startActivity(
ConversationActivityV2.createIntent(
context,
address = it.address
)
)

onClose()
}
Expand All @@ -169,10 +176,16 @@ fun StartConversationNavHost(
uiState,
viewModel.qrErrors,
viewModel,
onBack = { scope.launch { navigator.navigateUp() }},
onBack = { scope.launch { navigator.navigateUp() } },
onClose = onClose,
onHelp = { activity?.openUrl("https://sessionapp.zendesk.com/hc/en-us/articles/4439132747033-How-do-Account-ID-usernames-work") }
onHelp = { viewModel.onCommand(NewMessageViewModel.Commands.ShowUrlDialog) }
)
if (uiState.showUrlDialog) {
OpenURLAlertDialog(
url = helpUrl,
onDismissRequest = { viewModel.onCommand(NewMessageViewModel.Commands.DismissUrlDialog) }
)
}
}

// Create Group
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private fun EnterAccountId(
.fillMaxWidth(),
style = LocalType.current.small,
color = LocalColors.current.textSecondary,
iconRes = R.drawable.ic_circle_help,
iconRes = R.drawable.ic_square_arrow_up_right,
onClick = onHelp
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.squareup.phrase.Phrase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
Expand All @@ -19,9 +20,11 @@ import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.toAddress
import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
import org.session.libsession.utilities.upsertContact
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.PublicKeyValidation
import org.thoughtcrime.securesms.preferences.SettingsViewModel
import org.thoughtcrime.securesms.ui.GetString
import java.net.IDN
import javax.inject.Inject
Expand Down Expand Up @@ -69,10 +72,20 @@ class NewMessageViewModel @Inject constructor(
}
}

if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
onUnvalidatedPublicKey(publicKey = idOrONS)
if (PublicKeyValidation.hasValidLength(idOrONS)) {
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
onUnvalidatedPublicKey(idOrONS)
} else {
_state.update {
it.copy(
isTextErrorColor = true,
error = GetString(R.string.accountIdErrorInvalid),
loading = false
)
}
}
} else {
resolveONS(ons = idOrONS)
resolveONS(idOrONS)
}
}

Expand Down Expand Up @@ -122,7 +135,6 @@ class NewMessageViewModel @Inject constructor(
if (address is Address.Standard) {
viewModelScope.launch { _success.emit(Success(address)) }
}

}

private fun onUnvalidatedPublicKey(publicKey: String) {
Expand All @@ -134,18 +146,45 @@ class NewMessageViewModel @Inject constructor(
}

private fun Exception.toMessage() = when (this) {
is SnodeAPI.Error.Generic -> application.getString(R.string.onsErrorNotRecognized)
else -> application.getString(R.string.onsErrorUnableToSearch)
is SnodeAPI.Error.Generic -> application.getString(R.string.errorUnregisteredOns)
else -> Phrase.from(application, R.string.errorNoLookupOns)
.put(APP_NAME_KEY, application.getString(R.string.app_name))
.format().toString()
}

fun onCommand(commands: Commands) {
when (commands) {
is Commands.ShowUrlDialog -> {
_state.update { it.copy(showUrlDialog = true) }
}

is Commands.DismissUrlDialog -> {
_state.update {
it.copy(
showUrlDialog = false
)
}
}
}
}

sealed interface Commands {
data object ShowUrlDialog : Commands
data object DismissUrlDialog : Commands
}
}

data class State(
val newMessageIdOrOns: String = "",
val isTextErrorColor: Boolean = false,
val error: GetString? = null,
val loading: Boolean = false
val loading: Boolean = false,
val showUrlDialog : Boolean = false
) {
val isNextButtonEnabled: Boolean get() = newMessageIdOrOns.isNotBlank()
}




data class Success(val address: Address.Standard)