admin: bypass the reserved-username blocklist at write time too
UpdateUsernameAdmin already skipped the reserved-word check in the availability lookup, but UserStore.UpdateUsername's own write path (replacePeerUsernameTx / CollectibleUsernameStore.SetEditableUsername) enforces the same operator blocklist a second time, independently and unconditionally. That second check is what was still rejecting an admin handing out a word they'd deliberately reserved, with "username occupied". Add UpdateUsernameAdmin/SetEditableUsernameAdmin bypass variants down the write path (postgres and memory) and route users.Service's actual write through them when the availability check was already bypassed.
This commit is contained in:
parent
53872f8fc9
commit
ea17d7da0d
6 changed files with 180 additions and 15 deletions
|
|
@ -105,7 +105,18 @@ func NewCollectibleUsernameStore() *CollectibleUsernameStore {
|
|||
// usernames on the user and channel rows, so tests need this hook to give a peer
|
||||
// the editable registry row the projection expects. An empty username clears the
|
||||
// slot.
|
||||
func (s *CollectibleUsernameStore) SetEditableUsername(_ context.Context, peer domain.Peer, username string) (bool, error) {
|
||||
func (s *CollectibleUsernameStore) SetEditableUsername(ctx context.Context, peer domain.Peer, username string) (bool, error) {
|
||||
return s.setEditableUsernameChecked(ctx, peer, username, true)
|
||||
}
|
||||
|
||||
// SetEditableUsernameAdmin is SetEditableUsername without the operator
|
||||
// reserved-username blocklist check, for the admin console deliberately
|
||||
// assigning a reserved word to a specific account.
|
||||
func (s *CollectibleUsernameStore) SetEditableUsernameAdmin(ctx context.Context, peer domain.Peer, username string) (bool, error) {
|
||||
return s.setEditableUsernameChecked(ctx, peer, username, false)
|
||||
}
|
||||
|
||||
func (s *CollectibleUsernameStore) setEditableUsernameChecked(_ context.Context, peer domain.Peer, username string, checkReserved bool) (bool, error) {
|
||||
if !validCollectibleUsernamePeer(peer) {
|
||||
return false, domain.ErrUsernameInvalid
|
||||
}
|
||||
|
|
@ -121,7 +132,7 @@ func (s *CollectibleUsernameStore) SetEditableUsername(_ context.Context, peer d
|
|||
return false, domain.ErrUsernameInvalid
|
||||
}
|
||||
key := strings.ToLower(username)
|
||||
if s.nameReserved(key) {
|
||||
if checkReserved && s.nameReserved(key) {
|
||||
return false, domain.ErrUsernameOccupied
|
||||
}
|
||||
if existing, ok := s.registry[key]; ok {
|
||||
|
|
|
|||
|
|
@ -163,10 +163,25 @@ func (s *UserStore) CheckUsername(_ context.Context, userID int64, username stri
|
|||
if s.usernameRegistry != nil && s.usernameRegistry.nameReserved(username) {
|
||||
return false, nil
|
||||
}
|
||||
return s.usernameAvailableIgnoringReserved(userID, username)
|
||||
}
|
||||
|
||||
// CheckUsernameAdmin is CheckUsername without the operator reserved-username
|
||||
// blocklist check, for the admin console deliberately assigning a reserved
|
||||
// word to a specific account.
|
||||
func (s *UserStore) CheckUsernameAdmin(_ context.Context, userID int64, username string) (bool, error) {
|
||||
username = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(username, "@")))
|
||||
if username == "" {
|
||||
return true, nil
|
||||
}
|
||||
return s.usernameAvailableIgnoringReserved(userID, username)
|
||||
}
|
||||
|
||||
func (s *UserStore) usernameAvailableIgnoringReserved(userID int64, usernameLower string) (bool, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for id, u := range s.byID {
|
||||
if !u.Deleted && strings.ToLower(u.Username) == username && id != userID {
|
||||
if !u.Deleted && strings.ToLower(u.Username) == usernameLower && id != userID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -211,6 +226,17 @@ func (s *UserStore) Search(_ context.Context, currentUserID int64, query, phoneQ
|
|||
}
|
||||
|
||||
func (s *UserStore) UpdateUsername(ctx context.Context, userID int64, username string) (domain.User, error) {
|
||||
return s.updateUsernameChecked(ctx, userID, username, true)
|
||||
}
|
||||
|
||||
// UpdateUsernameAdmin is UpdateUsername without the operator reserved-username
|
||||
// blocklist check, for the admin console deliberately assigning a reserved
|
||||
// word to a specific account.
|
||||
func (s *UserStore) UpdateUsernameAdmin(ctx context.Context, userID int64, username string) (domain.User, error) {
|
||||
return s.updateUsernameChecked(ctx, userID, username, false)
|
||||
}
|
||||
|
||||
func (s *UserStore) updateUsernameChecked(ctx context.Context, userID int64, username string, checkReserved bool) (domain.User, error) {
|
||||
username = strings.TrimSpace(strings.TrimPrefix(username, "@"))
|
||||
usernameLower := strings.ToLower(username)
|
||||
s.mu.Lock()
|
||||
|
|
@ -227,7 +253,13 @@ func (s *UserStore) UpdateUsername(ctx context.Context, userID int64, username s
|
|||
}
|
||||
}
|
||||
if s.usernameRegistry != nil {
|
||||
if _, err := s.usernameRegistry.SetEditableUsername(ctx, domain.Peer{Type: domain.PeerTypeUser, ID: userID}, username); err != nil {
|
||||
var err error
|
||||
if checkReserved {
|
||||
_, err = s.usernameRegistry.SetEditableUsername(ctx, domain.Peer{Type: domain.PeerTypeUser, ID: userID}, username)
|
||||
} else {
|
||||
_, err = s.usernameRegistry.SetEditableUsernameAdmin(ctx, domain.Peer{Type: domain.PeerTypeUser, ID: userID}, username)
|
||||
}
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue