From ef1ed09ec688ce27b8a2c3c957515f4dc7ab0596 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 28 Aug 2026 20:53:13 -0700 Subject: A name's proof is its key, not a flag: takenByCoinosUser stops trusting `migrated` (which let poppy get squatted) — an existing coinos.io name yields only to a claim signed by that account's own nostr key; the squatted record, its DNS entry and its offer are evicted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi --- names/server.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/names/server.js b/names/server.js index e49da51..e713ff4 100644 --- a/names/server.js +++ b/names/server.js @@ -572,15 +572,18 @@ const npubPrefixOwner = (name) => (/^npub1[a-z0-9]+$/.test(name) ? name : null); // coinos.io names that belong to existing coinos users are reserved for // them until the migration gives those users a way to claim their own. -async function takenByCoinosUser(domain, name) { +async function takenByCoinosUser(domain, name, claimantPubkey = null) { if (domain !== 'coinos.io') return false; try { const r = await fetch(`https://coinos.io/api/users/${encodeURIComponent(name)}`); if (r.status !== 200) return false; - // A migrated account has handed its name over: the old site keeps the - // account and its history, but receiving belongs to us now. const u = await r.json().catch(() => null); - return !(u && u.migrated); + if (!u) return false; + // An existing coinos.io account owns its name. The `migrated` flag once + // gated this and proved meaningless (poppy got squatted through it) — + // identity is the proof: only a claim SIGNED by the old account's own + // nostr key may take the name over. + return !(u.npub && claimantPubkey && u.npub === npubEncode(claimantPubkey)); } catch { return true; } // can't verify → refuse rather than squat } @@ -1044,7 +1047,7 @@ Bun.serve({ if (existing && existing.pubkey !== auth.pubkey && existing.manager !== auth.pubkey) { return json({ error: 'name is taken' }, 409); } - if (!existing && await takenByCoinosUser(domain, name)) return json({ error: 'name is taken' }, 409); + if (!existing && await takenByCoinosUser(domain, name, auth.pubkey)) return json({ error: 'name is taken' }, 409); // an npub-shaped name belongs to that identity alone if (npubPrefixOwner(name)) { const managerOk = existing && existing.manager === auth.pubkey; -- cgit v1.2.3