diff options
| author | Adam Soltys <[email protected]> | 2026-10-09 21:55:11 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-09 21:55:11 -0700 |
| commit | 3d6bf81a19ca080d3710893b1e65f5aa3f01315f (patch) | |
| tree | 71a102a6440a3d0b730f635010f723b5f624f212 /names/server.js | |
| parent | faec1f84ed1e9e8ee78055796a8ebc92936abe9d (diff) | |
| download | coinosv3-3d6bf81a19ca080d3710893b1e65f5aa3f01315f.tar.gz coinosv3-3d6bf81a19ca080d3710893b1e65f5aa3f01315f.zip | |
Names: letters and numbers only, 2–24 — coinos v2's rule. The registrar took a-z, 0-9 and . _ - (1–30), so bitcoin-education and a dozen others got through. A NEW claim must now match /^[a-z0-9]{2,24}$/ (v2's /^[\p{L}\p{N}]{2,24}$/u, held to ASCII since a name is a DNS label); a name already held (19 with . _ -, plus "f" and "g") is grandfathered — its owner can still update it — as are v2 migrations carrying their own name and npub-shaped defaults. /name/<n> answers invalid: true for a free name that breaks the rule; the claim form says "Letters and numbers only" as it's typed, and the suggested name from a nostr profile is stripped to letters and digits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
Diffstat (limited to 'names/server.js')
| -rw-r--r-- | names/server.js | 13 |
1 files changed, 11 insertions, 2 deletions
diff --git a/names/server.js b/names/server.js index 463ab8e..aff4e54 100644 --- a/names/server.js +++ b/names/server.js @@ -57,6 +57,13 @@ const RESERVED = new Set([ 'user', 'users', 'coinos', 'nostr', 'ark', 'test', 'dev', 'security', 'abuse', ]); const NAME_RE = /^[a-z0-9][a-z0-9._-]{0,29}$/; +// What a NEW name may be: letters and digits, 2–24 — coinos v2's rule +// (/^[\p{L}\p{N}]{2,24}$/u there), held to ASCII because a name is a DNS +// label here. NAME_RE stays the wider test for names that already exist +// (a dozen claimed before the rule: bitcoin-education, barefoot.strider…), +// v2 migrations carrying their own name, and npub-shaped defaults. +const NEW_NAME_RE = /^[a-z0-9]{2,24}$/; +const NEW_NAME_ERR = 'names can only have letters and numbers (2–24)'; let state = (() => { try { return JSON.parse(readFileSync(STATE, 'utf8')); } catch { return { names: {} }; } @@ -1228,8 +1235,9 @@ Bun.serve({ const granted = !!g && (keys.includes(g.pubkey) || (!!g.manager && keys.includes(g.manager))); return json({ name: m[1], domain, taken: true, pubkey: rec.pubkey, manager: rec.manager || null, uri: rec.uri, noffer: rec.noffer?.raw || null, lno: rec.lno || null, granted }); } - const reserved = RESERVED.has(m[1]) || !NAME_RE.test(m[1]) || await takenByCoinosUser(domain, m[1]); - return json({ name: m[1], domain, taken: reserved, reserved }); + const invalid = !NEW_NAME_RE.test(m[1]) && !npubPrefixOwner(m[1]); + const reserved = RESERVED.has(m[1]) || !NAME_RE.test(m[1]) || invalid || await takenByCoinosUser(domain, m[1]); + return json({ name: m[1], domain, taken: reserved, reserved, ...(invalid ? { invalid: true } : {}) }); } // Which name(s) does a wallet key own? Lets an imported seed find its @@ -1659,6 +1667,7 @@ Bun.serve({ if (existing && existing.pubkey !== auth.pubkey && existing.manager !== auth.pubkey) { return json({ error: 'name is taken' }, 409); } + if (!existing && !granted && !npubPrefixOwner(name) && !NEW_NAME_RE.test(name)) return json({ error: NEW_NAME_ERR }, 400); if (!existing && !granted && 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)) { |
