feat(domains): ValidAccountName pins the slug shape account names must take
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGdTfzwT492ysLPDaWfpvJ
2 files changed,
+39
−0
internal/domains/slugname.go+16 −0internal/domains/slugname_test.go+23 −0
diff --git a/internal/domains/slugname.go b/internal/domains/slugname.go| new file mode 100644 |
| index 000000000..59ea6ec10 |
| --- /dev/null |
| +++ b/internal/domains/slugname.go |
| @@ -0,0 +1,16 @@ |
| +package domains |
| + |
| +import "regexp" |
| + |
| +// accountNameRe pins what an account NAME may be so it can live in a URL |
| +// path segment and a CLI flag: 1–63 characters of lowercase a-z, 0-9 and |
| +// hyphen, no leading or trailing hyphen. Names are still deliberately |
| +// non-unique (identity is the sqid); this constrains shape, not ownership. |
| +var accountNameRe = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$`) |
| + |
| +// ValidAccountName reports whether name is usable as an account name. |
| +// Applied at the edges where a human TYPES a name (console new-account |
| +// form, POST /auth/cli/accounts, `carlos accounts create`) — never at |
| +// auto-mint, where the personal account is named by keymail address and a |
| +// refusal would strand a signed-in user. |
| +func ValidAccountName(name string) bool { return accountNameRe.MatchString(name) } |
diff --git a/internal/domains/slugname_test.go b/internal/domains/slugname_test.go| new file mode 100644 |
| index 000000000..bed2b1663 |
| --- /dev/null |
| +++ b/internal/domains/slugname_test.go |
| @@ -0,0 +1,23 @@ |
| +package domains |
| + |
| +import "testing" |
| + |
| +func TestValidAccountName(t *testing.T) { |
| + valid := []string{"amadan", "a", "kass-2", "ops", "a-b-c", "0day", "x9"} |
| + invalid := []string{ |
| + "", "Amadan", "-lead", "trail-", "two words", "paul@keymail.dev", |
| + "dots.no", "unicode-é", "UPPER", |
| + // 64 chars — one past the limit. |
| + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
| + } |
| + for _, name := range valid { |
| + if !ValidAccountName(name) { |
| + t.Errorf("ValidAccountName(%q) = false, want true", name) |
| + } |
| + } |
| + for _, name := range invalid { |
| + if ValidAccountName(name) { |
| + t.Errorf("ValidAccountName(%q) = true, want false", name) |
| + } |
| + } |
| +} |