usernames: operator reserved-username blocklist
A plain blocklist for names like @support - separate from the collectible
system, so a reservation has no owner, no price and no "bought on Fragment"
badge.
- reserved_usernames table + migration.
- Enforced in replacePeerUsernameTx (the single editable-username write point:
account.updateUsername, channels.updateUsername, @BotFather /setusername) and
in the collectible mint path; a reserved name returns USERNAME_OCCUPIED.
- admin.Service: ReserveUsername / UnreserveUsername (journalled commands) and
the ReservedUsernames listing.
- adminapi: /v1/reserved-usernames{,/reserve,/unreserve}.
- telesrv-admin panel + a "Reserved Usernames" page in the web UI (dist rebuilt).
- Postgres and in-memory store implementations; the memory registry gains an
optional reserved-name check so tests exercise the same rule.
This commit is contained in:
parent
d2ffaa92bf
commit
a83aa45fb8
23 changed files with 874 additions and 39 deletions
|
|
@ -510,12 +510,30 @@ func (fakeService) ReviewModerationAppeal(context.Context, domain.ModerationDeci
|
|||
|
||||
type captureCollectibleUsernameService struct {
|
||||
fakeService
|
||||
mint admin.MintCollectibleUsernameRequest
|
||||
transfer admin.TransferCollectibleUsernameRequest
|
||||
revoke admin.RevokeCollectibleUsernameRequest
|
||||
del admin.DeleteCollectibleUsernameRequest
|
||||
filter domain.CollectibleUsernameFilter
|
||||
assetID int64
|
||||
mint admin.MintCollectibleUsernameRequest
|
||||
transfer admin.TransferCollectibleUsernameRequest
|
||||
revoke admin.RevokeCollectibleUsernameRequest
|
||||
del admin.DeleteCollectibleUsernameRequest
|
||||
reserve admin.ReserveUsernameRequest
|
||||
unreserve admin.UnreserveUsernameRequest
|
||||
resFilter domain.ReservedUsernameFilter
|
||||
filter domain.CollectibleUsernameFilter
|
||||
assetID int64
|
||||
}
|
||||
|
||||
func (s *captureCollectibleUsernameService) ReserveUsername(_ context.Context, req admin.ReserveUsernameRequest) (admin.CommandResult, error) {
|
||||
s.reserve = req
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (s *captureCollectibleUsernameService) UnreserveUsername(_ context.Context, req admin.UnreserveUsernameRequest) (admin.CommandResult, error) {
|
||||
s.unreserve = req
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (s *captureCollectibleUsernameService) ReservedUsernames(_ context.Context, filter domain.ReservedUsernameFilter) ([]domain.ReservedUsername, error) {
|
||||
s.resFilter = filter
|
||||
return []domain.ReservedUsername{{Username: "support", Reason: "official", Actor: "ops"}}, nil
|
||||
}
|
||||
|
||||
func (s *captureCollectibleUsernameService) MintCollectibleUsername(_ context.Context, req admin.MintCollectibleUsernameRequest) (admin.CommandResult, error) {
|
||||
|
|
@ -731,3 +749,49 @@ func (fakeService) CollectibleUsernameByID(context.Context, int64) (domain.Colle
|
|||
func (fakeService) CollectibleUsernameTransfers(context.Context, int64, int) ([]domain.CollectibleUsernameTransfer, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (fakeService) ReserveUsername(_ context.Context, req admin.ReserveUsernameRequest) (admin.CommandResult, error) {
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (fakeService) UnreserveUsername(_ context.Context, req admin.UnreserveUsernameRequest) (admin.CommandResult, error) {
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (fakeService) ReservedUsernames(context.Context, domain.ReservedUsernameFilter) ([]domain.ReservedUsername, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestAdminAPIReservedUsernames(t *testing.T) {
|
||||
svc := &captureCollectibleUsernameService{}
|
||||
srv := &Server{token: "secret", svc: svc}
|
||||
|
||||
reserve := httptest.NewRequest(http.MethodPost, "/v1/reserved-usernames/reserve", strings.NewReader(
|
||||
`{"command_id":"r-1","actor":"ops","reason":"official","username":"support"}`))
|
||||
reserve.Header.Set("Authorization", "Bearer secret")
|
||||
rec := httptest.NewRecorder()
|
||||
srv.routes().ServeHTTP(rec, reserve)
|
||||
if rec.Code != http.StatusOK || svc.reserve.Username != "support" || svc.reserve.CommandID != "r-1" {
|
||||
t.Fatalf("reserve status=%d req=%+v", rec.Code, svc.reserve)
|
||||
}
|
||||
|
||||
unreserve := httptest.NewRequest(http.MethodPost, "/v1/reserved-usernames/unreserve", strings.NewReader(
|
||||
`{"command_id":"u-1","actor":"ops","reason":"done","username":"support"}`))
|
||||
unreserve.Header.Set("Authorization", "Bearer secret")
|
||||
rec = httptest.NewRecorder()
|
||||
srv.routes().ServeHTTP(rec, unreserve)
|
||||
if rec.Code != http.StatusOK || svc.unreserve.Username != "support" {
|
||||
t.Fatalf("unreserve status=%d req=%+v", rec.Code, svc.unreserve)
|
||||
}
|
||||
|
||||
list := httptest.NewRequest(http.MethodGet, "/v1/reserved-usernames?q=sup&limit=10", nil)
|
||||
list.Header.Set("Authorization", "Bearer secret")
|
||||
rec = httptest.NewRecorder()
|
||||
srv.routes().ServeHTTP(rec, list)
|
||||
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"username":"support"`) {
|
||||
t.Fatalf("list status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if svc.resFilter.Query != "sup" || svc.resFilter.Limit != 10 {
|
||||
t.Fatalf("list filter = %+v", svc.resFilter)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue