Allow dot in usernames per NIP-05 local-part spec
Extend usernameRE to [a-z0-9_.-], preserve dots in SanitizeForUsername, and add tests for validation, sanitization, and nip05 sync precedence. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -42,8 +42,9 @@ var (
|
|||||||
ErrUsernameTaken = errors.New("username taken")
|
ErrUsernameTaken = errors.New("username taken")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Username rules: 1-30 chars, [a-z0-9_-], lowercase, must start with alnum.
|
// Username rules: 1-30 chars, [a-z0-9_.-], lowercase, must start with alnum.
|
||||||
var usernameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,29}$`)
|
// Dot is allowed per NIP-05 local-part spec.
|
||||||
|
var usernameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9_.-]{0,29}$`)
|
||||||
|
|
||||||
func ValidateUsername(name string, reserved []string) error {
|
func ValidateUsername(name string, reserved []string) error {
|
||||||
name = strings.ToLower(strings.TrimSpace(name))
|
name = strings.ToLower(strings.TrimSpace(name))
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ func TestValidateUsername(t *testing.T) {
|
|||||||
{"alice", true},
|
{"alice", true},
|
||||||
{"al-ice_42", true},
|
{"al-ice_42", true},
|
||||||
{"a", true},
|
{"a", true},
|
||||||
|
{"alice.bob", true},
|
||||||
|
{"alice.smith.42", true},
|
||||||
{"", false},
|
{"", false},
|
||||||
{"-alice", false},
|
{"-alice", false},
|
||||||
{"_alice", false},
|
{"_alice", false},
|
||||||
|
{".alice", false},
|
||||||
{"thisusernameiswaytoolongtobevalid12345", false},
|
{"thisusernameiswaytoolongtobevalid12345", false},
|
||||||
{"admin", false},
|
{"admin", false},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SanitizeForUsername coerces an arbitrary profile string into a candidate
|
// SanitizeForUsername coerces an arbitrary profile string into a candidate
|
||||||
// that matches usernameRE: lowercase ASCII alphanumerics, `_`, and `-`,
|
// that matches usernameRE: lowercase ASCII alphanumerics, `_`, `-`, and `.`,
|
||||||
// length <= 30, with an alphanumeric first character. Returns "" when no
|
// length <= 30, with an alphanumeric first character. Returns "" when no
|
||||||
// usable handle can be derived.
|
// usable handle can be derived.
|
||||||
func SanitizeForUsername(s string) string {
|
func SanitizeForUsername(s string) string {
|
||||||
@@ -22,7 +22,7 @@ func SanitizeForUsername(s string) string {
|
|||||||
case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'):
|
case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'):
|
||||||
b.WriteRune(r)
|
b.WriteRune(r)
|
||||||
prevSep = false
|
prevSep = false
|
||||||
case r == '-' || r == '_':
|
case r == '-' || r == '_' || r == '.':
|
||||||
if b.Len() == 0 {
|
if b.Len() == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,9 @@ func SanitizeForUsername(s string) string {
|
|||||||
prevSep = true
|
prevSep = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out := strings.TrimRight(b.String(), "_-")
|
out := strings.TrimRight(b.String(), "_-.")
|
||||||
if len(out) > 30 {
|
if len(out) > 30 {
|
||||||
out = strings.TrimRight(out[:30], "_-")
|
out = strings.TrimRight(out[:30], "_-.")
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ func TestSanitizeForUsername(t *testing.T) {
|
|||||||
{"alice_", "alice"},
|
{"alice_", "alice"},
|
||||||
{"alice--bob", "alice-bob"},
|
{"alice--bob", "alice-bob"},
|
||||||
{"alice__bob", "alice_bob"},
|
{"alice__bob", "alice_bob"},
|
||||||
|
{"alice.bob", "alice.bob"},
|
||||||
|
{"alice..bob", "alice.bob"},
|
||||||
|
{".alice", "alice"},
|
||||||
|
{"alice.", "alice"},
|
||||||
{" ", ""},
|
{" ", ""},
|
||||||
{"", ""},
|
{"", ""},
|
||||||
{"!!!", ""},
|
{"!!!", ""},
|
||||||
@@ -59,6 +63,11 @@ func TestCandidateFromMetadataPrecedence(t *testing.T) {
|
|||||||
name: "alice", nip05: "preferred@azzamo.net", dom: domain,
|
name: "alice", nip05: "preferred@azzamo.net", dom: domain,
|
||||||
want: "preferred",
|
want: "preferred",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "nip05 local part with dot is preserved",
|
||||||
|
name: "alice", nip05: "alice.smith@azzamo.net", dom: domain,
|
||||||
|
want: "alice.smith",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "nip05 ignored when domain differs",
|
desc: "nip05 ignored when domain differs",
|
||||||
name: "alice", nip05: "preferred@other.example", dom: domain,
|
name: "alice", nip05: "preferred@other.example", dom: domain,
|
||||||
|
|||||||
Reference in New Issue
Block a user