fix(profiles): never treat placeholder kind-0 as a real display name
An early build auto-published the empty-label default 'My Profile' as new accounts' kind-0 metadata. That poisoned kind-0 then propagated back: pairing-time enrichment and the background backfill both copied it over the name the user typed, so profiles like npub1p437… display 'My Profile' forever and the loop can never resolve a 'real' name. - network_display_name(): shared resolver that rejects blank AND placeholder names fetched from the network (unit-tested) - create_profile / import_profile: never auto-publish a generic placeholder as kind-0; placeholders stay local until the user names the profile, and import falls back to the shortened npub - backfill + pairing enrichment now use the shared resolver
This commit is contained in:
parent
e8d2abbd1b
commit
83f594074b
3 changed files with 56 additions and 15 deletions
|
|
@ -423,12 +423,7 @@ pub async fn serve() -> Result<(), AppError> {
|
||||||
row.nip05 = meta.nip05.clone();
|
row.nip05 = meta.nip05.clone();
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
let real_name = meta
|
let real_name = profiles::network_display_name(&meta);
|
||||||
.display_name
|
|
||||||
.as_deref()
|
|
||||||
.or(meta.name.as_deref())
|
|
||||||
.map(str::trim)
|
|
||||||
.filter(|name| !name.is_empty());
|
|
||||||
if let Some(name) = real_name {
|
if let Some(name) = real_name {
|
||||||
if profiles::is_generic_pairing_label(&row.label) {
|
if profiles::is_generic_pairing_label(&row.label) {
|
||||||
row.label = name.to_string();
|
row.label = name.to_string();
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,14 @@ pub fn create_profile(
|
||||||
|
|
||||||
// Publish kind 0 metadata event so other clients can see the username/display name.
|
// Publish kind 0 metadata event so other clients can see the username/display name.
|
||||||
// Best-effort: relay failures here never block profile creation.
|
// Best-effort: relay failures here never block profile creation.
|
||||||
|
//
|
||||||
|
// Generic placeholders are never published: an early build auto-published
|
||||||
|
// the empty-label default "My Profile" as the account's kind-0, and that
|
||||||
|
// poisoned metadata then became the "real" name every client (including
|
||||||
|
// this app's own backfill) resolved forever. A placeholder stays local
|
||||||
|
// until the user names the profile, and only then goes on the network.
|
||||||
let relay_urls = relays::enabled_urls(settings);
|
let relay_urls = relays::enabled_urls(settings);
|
||||||
if !relay_urls.is_empty() {
|
if !relay_urls.is_empty() && !is_generic_pairing_label(&label) {
|
||||||
publish_metadata_blocking(&keys, &label, None, None, relay_urls);
|
publish_metadata_blocking(&keys, &label, None, None, relay_urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +148,9 @@ pub fn import_profile(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|m| m.display_name.as_deref().or(m.name.as_deref()))
|
.and_then(|m| m.display_name.as_deref().or(m.name.as_deref()))
|
||||||
.filter(|name| !name.trim().is_empty())
|
.filter(|name| !name.trim().is_empty())
|
||||||
|
// Placeholder kind-0 (see create_profile) is not a name; fall
|
||||||
|
// back to the shortened npub instead of importing "My Profile".
|
||||||
|
.filter(|name| !is_generic_pairing_label(name.trim()))
|
||||||
.map(str::to_string)
|
.map(str::to_string)
|
||||||
.unwrap_or_else(|| shorten_npub(&keys.public_key()))
|
.unwrap_or_else(|| shorten_npub(&keys.public_key()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -162,7 +171,7 @@ pub fn import_profile(
|
||||||
});
|
});
|
||||||
|
|
||||||
let relay_urls = relays::enabled_urls(settings);
|
let relay_urls = relays::enabled_urls(settings);
|
||||||
if !relay_urls.is_empty() {
|
if !relay_urls.is_empty() && !is_generic_pairing_label(&label) {
|
||||||
publish_metadata_blocking(
|
publish_metadata_blocking(
|
||||||
&keys,
|
&keys,
|
||||||
&label,
|
&label,
|
||||||
|
|
@ -566,6 +575,23 @@ pub fn is_generic_pairing_label(label: &str) -> bool {
|
||||||
GENERIC_PAIRING_LABELS.contains(&label)
|
GENERIC_PAIRING_LABELS.contains(&label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The real display name carried by fetched kind-0 metadata, if any.
|
||||||
|
///
|
||||||
|
/// Prefers `display_name` over `name`, rejects blanks, and rejects generic
|
||||||
|
/// placeholders: early builds published the empty-label default "My Profile"
|
||||||
|
/// as kind-0, so a placeholder arriving FROM the network is pollution, not a
|
||||||
|
/// name, and must never be copied over a profile row's label.
|
||||||
|
pub fn network_display_name(metadata: &Metadata) -> Option<String> {
|
||||||
|
metadata
|
||||||
|
.display_name
|
||||||
|
.as_deref()
|
||||||
|
.or(metadata.name.as_deref())
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.filter(|name| !is_generic_pairing_label(name))
|
||||||
|
.map(str::to_string)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create or refresh the vault profile for a remote (NIP-46) identity.
|
/// Create or refresh the vault profile for a remote (NIP-46) identity.
|
||||||
///
|
///
|
||||||
/// When a NIP-46 client connection is established the identity lives on the
|
/// When a NIP-46 client connection is established the identity lives on the
|
||||||
|
|
@ -959,6 +985,26 @@ mod tests {
|
||||||
assert!(!is_generic_pairing_label("amber"));
|
assert!(!is_generic_pairing_label("amber"));
|
||||||
assert!(!is_generic_pairing_label(""));
|
assert!(!is_generic_pairing_label(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn network_display_name_rejects_placeholder_kind0() {
|
||||||
|
// The exact pollution case: an early build published "My Profile" as
|
||||||
|
// the account's kind-0. Resolution must treat it as "no name found",
|
||||||
|
// never as the profile's display name.
|
||||||
|
let polluted = Metadata::new()
|
||||||
|
.name("My Profile")
|
||||||
|
.display_name("My Profile");
|
||||||
|
assert_eq!(network_display_name(&polluted), None);
|
||||||
|
|
||||||
|
let blank = Metadata::new().name(" ").display_name("");
|
||||||
|
assert_eq!(network_display_name(&blank), None);
|
||||||
|
|
||||||
|
let real = Metadata::new().name("satoshi").display_name("Satoshi ✦");
|
||||||
|
assert_eq!(network_display_name(&real).as_deref(), Some("Satoshi ✦"));
|
||||||
|
|
||||||
|
let name_only = Metadata::new().name("satoshi");
|
||||||
|
assert_eq!(network_display_name(&name_only).as_deref(), Some("satoshi"));
|
||||||
|
}
|
||||||
use crate::vault::{KdfParams, Vault, VaultCrypto};
|
use crate::vault::{KdfParams, Vault, VaultCrypto};
|
||||||
|
|
||||||
fn populated_vault() -> Vault {
|
fn populated_vault() -> Vault {
|
||||||
|
|
|
||||||
|
|
@ -2583,13 +2583,13 @@ impl Nip46ClientSigner {
|
||||||
}
|
}
|
||||||
// Upgrade the generic pairing label to the real display
|
// Upgrade the generic pairing label to the real display
|
||||||
// name only while the row still carries the label we set
|
// name only while the row still carries the label we set
|
||||||
// during pairing — a user rename always wins.
|
// during pairing — a user rename always wins. A
|
||||||
let real_name = meta
|
// placeholder coming FROM the network is never a name
|
||||||
.display_name
|
// (network_display_name rejects it): early builds
|
||||||
.as_deref()
|
// published the empty-label default "My Profile" as
|
||||||
.or(meta.name.as_deref())
|
// kind-0, and copying that over the name the user typed
|
||||||
.map(str::trim)
|
// at pairing is how display names got lost.
|
||||||
.filter(|name| !name.is_empty());
|
let real_name = profiles::network_display_name(&meta);
|
||||||
if let Some(name) = real_name {
|
if let Some(name) = real_name {
|
||||||
if row.label == pairing_label {
|
if row.label == pairing_label {
|
||||||
row.label = name.to_string();
|
row.label = name.to_string();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue