Files
3x-ui/internal/web/service/panel/user_test.go
T
MHSanaei 2e14a2a51a fix(web): refuse oversized login usernames before recording them
A failed login keeps its username in the in-memory log buffer, the
limiter table and the notifier queue, and the route accepted any body
the global cap allowed. The login route now takes at most 16 KiB and
answers a username over 256 bytes with the generic failure before any
of those see it; the user setters refuse such a name too, so no stored
username can be one the login route turns away.
2026-10-31 09:04:27 +00:00

77 lines
2.6 KiB
Go

package panel
import (
"path/filepath"
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
)
// install.sh replaces the credentials only while this reports true, and before
// the first start a database holds no user at all.
func TestHasDefaultCredential(t *testing.T) {
hash := func(t *testing.T, password string) string {
t.Helper()
h, err := crypto.HashPasswordAsBcrypt(password)
if err != nil {
t.Fatalf("hash: %v", err)
}
return h
}
cases := []struct {
name string
user *model.User
want bool
}{
{name: "no user before the first start", user: nil, want: true},
{name: "legacy admin/admin", user: &model.User{Username: "admin", Password: hash(t, "admin")}, want: true},
{name: "chosen credentials", user: &model.User{Username: "admin", Password: hash(t, "s3cret-Pass")}, want: false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
if err := database.GetDB().Where("1 = 1").Delete(&model.User{}).Error; err != nil {
t.Fatalf("clear users: %v", err)
}
if tc.user != nil {
if err := database.GetDB().Create(tc.user).Error; err != nil {
t.Fatalf("create user: %v", err)
}
}
got, err := (&UserService{}).HasDefaultCredential()
if err != nil || got != tc.want {
t.Fatalf("HasDefaultCredential() = %v, %v; want %v", got, err, tc.want)
}
})
}
}
// The login route refuses names over MaxUsernameLength unread, so storing one would lock
// the admin out of their own panel.
func TestUserSettersRefuseNamesTheLoginRouteRefuses(t *testing.T) {
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
svc := &UserService{}
if err := svc.UpdateFirstUser("admin", "s3cret-Pass"); err != nil {
t.Fatalf("UpdateFirstUser: %v", err)
}
var user model.User
if err := database.GetDB().First(&user).Error; err != nil {
t.Fatalf("load user: %v", err)
}
long := strings.Repeat("a", MaxUsernameLength+1)
if err := svc.UpdateFirstUser(long, "s3cret-Pass"); err == nil {
t.Fatal("UpdateFirstUser stored a name the login route refuses")
}
if err := svc.UpdateUser(user.Id, long, "s3cret-Pass"); err == nil {
t.Fatal("UpdateUser stored a name the login route refuses")
}
var stored model.User
if err := database.GetDB().First(&stored, user.Id).Error; err != nil || stored.Username != "admin" {
t.Fatalf("stored username = %q (%v), want it unchanged", stored.Username, err)
}
}