feat(relayauth): user toggle for trusting follows' outboxes on reads

Add relayAuthTrustFollowsForReads (local AccountSettings flag, persisted
via LocalPreferences, default off) and wire it into the ledger's
readTrustEnabled input. The relay-auth settings screen shows a switch —
"Also trust when reading their posts" — under the Trusted-follows policy,
so downloading a followed author's outbox auto-authenticates only when
the user opts in; writes (DMs/notifications) remain auto-trusted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZjmYpgHP4pf79Sav5QT8a
This commit is contained in:
Claude
2026-07-10 22:40:10 +00:00
parent 70b23b6999
commit 39dc053bf7
5 changed files with 40 additions and 0 deletions
@@ -162,6 +162,7 @@ private object PrefKeys {
const val ALWAYS_ON_NOTIFICATION_SERVICE = "always_on_notification_service"
const val DEFAULT_RELAY_AUTH_POLICY = "default_relay_auth_policy"
const val RELAY_GROUP_VIEW_MODE = "relay_group_view_mode"
const val RELAY_AUTH_TRUST_FOLLOWS_FOR_READS = "relay_auth_trust_follows_for_reads"
const val SPLIT_NOTIFICATIONS_ENABLED = "split_notifications_enabled"
const val SHOW_MESSAGES_IN_NOTIFICATIONS = "show_messages_in_notifications"
@@ -517,6 +518,7 @@ object LocalPreferences {
putBoolean(PrefKeys.ALWAYS_ON_NOTIFICATION_SERVICE, settings.alwaysOnNotificationService.value)
putString(PrefKeys.DEFAULT_RELAY_AUTH_POLICY, settings.defaultRelayAuthPolicy.value.name)
putString(PrefKeys.RELAY_GROUP_VIEW_MODE, settings.relayGroupViewMode.value.name)
putBoolean(PrefKeys.RELAY_AUTH_TRUST_FOLLOWS_FOR_READS, settings.relayAuthTrustFollowsForReads.value)
putBoolean(PrefKeys.SPLIT_NOTIFICATIONS_ENABLED, settings.splitNotificationsEnabled.value)
putBoolean(PrefKeys.SHOW_MESSAGES_IN_NOTIFICATIONS, settings.showMessagesInNotifications.value)
// Any account that reaches a save has its notification filter in its
@@ -638,6 +640,7 @@ object LocalPreferences {
?.let { runCatching { RelayAuthPolicy.valueOf(it) }.getOrNull() }
?: RelayAuthPolicy.IF_IN_MY_LIST
val relayGroupViewMode = RelayGroupViewMode.fromName(getString(PrefKeys.RELAY_GROUP_VIEW_MODE, null))
val relayAuthTrustFollowsForReads = getBoolean(PrefKeys.RELAY_AUTH_TRUST_FOLLOWS_FOR_READS, false)
val splitNotificationsEnabled = getBoolean(PrefKeys.SPLIT_NOTIFICATIONS_ENABLED, false)
val showMessagesInNotifications = getBoolean(PrefKeys.SHOW_MESSAGES_IN_NOTIFICATIONS, true)
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
@@ -847,6 +850,7 @@ object LocalPreferences {
alwaysOnNotificationService = MutableStateFlow(alwaysOnNotificationService),
defaultRelayAuthPolicy = MutableStateFlow(defaultRelayAuthPolicy),
relayGroupViewMode = MutableStateFlow(relayGroupViewMode),
relayAuthTrustFollowsForReads = MutableStateFlow(relayAuthTrustFollowsForReads),
splitNotificationsEnabled = MutableStateFlow(splitNotificationsEnabled),
showMessagesInNotifications = MutableStateFlow(showMessagesInNotifications),
backupUserMetadata = latestUserMetadataResolved,
@@ -275,6 +275,7 @@ class AccountSettings(
val callsEnabled: MutableStateFlow<Boolean> = MutableStateFlow(true),
val defaultRelayAuthPolicy: MutableStateFlow<RelayAuthPolicy> = MutableStateFlow(RelayAuthPolicy.IF_IN_MY_LIST),
val relayGroupViewMode: MutableStateFlow<RelayGroupViewMode> = MutableStateFlow(RelayGroupViewMode.DEFAULT),
val relayAuthTrustFollowsForReads: MutableStateFlow<Boolean> = MutableStateFlow(false),
) : EphemeralChatRepository,
RelayGroupRepository,
PublicChatListRepository {
@@ -1524,6 +1525,13 @@ class AccountSettings(
saveAccountSettings()
}
}
fun changeRelayAuthTrustFollowsForReads(enabled: Boolean) {
if (relayAuthTrustFollowsForReads.value != enabled) {
relayAuthTrustFollowsForReads.tryEmit(enabled)
saveAccountSettings()
}
}
}
@Serializable
@@ -61,6 +61,7 @@ fun RelayAuthSubscription(
// Any follow list (kind 3, follow sets, etc.) counts as trusting the counterparty
// enough to reveal our identity to a relay that serves them.
isFollowed = { pubkey -> pubkey in account.allFollows.flow.value.authors },
readTrustEnabled = { account.settings.relayAuthTrustFollowsForReads.value },
)
}
@@ -38,6 +38,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -154,6 +155,30 @@ fun RelayAuthSettingsScreen(
}
}
if (globalPolicy == RelayAuthPolicy.TRUSTED_FOLLOWS) {
val trustReads by account.settings.relayAuthTrustFollowsForReads.collectAsState()
Row(
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(R.string.relay_auth_trust_reads),
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = stringResource(R.string.relay_auth_trust_reads_desc),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Switch(
checked = trustReads,
onCheckedChange = { account.settings.changeRelayAuthTrustFollowsForReads(it) },
)
}
}
Spacer(Modifier.height(8.dp))
HorizontalDivider()
Spacer(Modifier.height(8.dp))
+2
View File
@@ -827,6 +827,8 @@
<string name="relay_auth_policy_if_in_my_list_desc">Only authenticate with relays in your relay list</string>
<string name="relay_auth_policy_trusted_follows">My relays and people I follow</string>
<string name="relay_auth_policy_trusted_follows_desc">Also authenticate with relays that serve people you follow, such as sending a message to a friend. You\'ll be asked about anyone else.</string>
<string name="relay_auth_trust_reads">Also trust when reading their posts</string>
<string name="relay_auth_trust_reads_desc">Log in automatically to download posts from people you follow, not just to message or notify them.</string>
<string name="relay_auth_prompt_title">Log in to this relay?</string>
<string name="relay_auth_prompt_message">This relay asks you to log in before it will:</string>
<string name="relay_auth_reason_send_dm">Send your private message to:</string>