admin: allow shorter operator-assigned usernames, add reference docs

Lower the username floor to 3 characters on the admin-only write path
(account/channel set-username), separate from the 5-character self-service
minimum, since a deliberately short operator handle isn't the squatting the
higher floor guards against. Channel admin username assignment previously
had no length/format validation at all; this adds it.

Also adds three reference docs: the client's deep-link (tg://, owpg://)
surface, an OpenAPI 3.1 spec for both admin HTTP APIs (built from source,
not the stale docs/admin-panel-api.en.md), and a starter reserved-username
blocklist.
This commit is contained in:
Astra 2026-09-17 15:24:31 +01:00
parent 6af4576fed
commit c39011e542
5 changed files with 3861 additions and 5 deletions

View file

@ -116,7 +116,13 @@ func WithReservedUsernames(names []string) Option {
}
const (
minUsernameLen = 5
minUsernameLen = 5
// minUsernameLenAdmin is the floor for an operator-assigned username via
// UpdateUsernameAdmin (the admin API/panel) -- shorter than what self-service
// account.updateUsername allows, since a deliberate short handle assigned by
// an operator (e.g. a 3-4 char official/brand account) is not the squatting
// the higher self-service minimum guards against.
minUsernameLenAdmin = 3
maxUsernameLen = 32
maxProfileNameRunes = 64
// bio 长度双档,对齐 appConfig about_length_limit_default=70 /
@ -383,7 +389,13 @@ func (s *Service) updateUsername(ctx context.Context, userID int64, username str
return s.projectOne(ctx, self.ID, self)
}
if username != "" {
if !validUsername(username) || (enforceReserved && s.reserved.Contains(username)) {
minLen := minUsernameLen
if !enforceReserved {
// enforceReserved=false is exactly the admin-bypass path
// (UpdateUsernameAdmin) -- see minUsernameLenAdmin's doc comment.
minLen = minUsernameLenAdmin
}
if !validUsernameMinLen(username, minLen) || (enforceReserved && s.reserved.Contains(username)) {
return domain.User{}, domain.ErrUsernameInvalid
}
var (
@ -1040,7 +1052,11 @@ func normalizeUsername(username string) string {
}
func validUsername(username string) bool {
if len(username) < minUsernameLen || len(username) > maxUsernameLen {
return validUsernameMinLen(username, minUsernameLen)
}
func validUsernameMinLen(username string, minLen int) bool {
if len(username) < minLen || len(username) > maxUsernameLen {
return false
}
for i := 0; i < len(username); i++ {