diff --git a/.gitignore b/.gitignore index fbdb6a58..01dee62b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ walkthrough*.md task.md V2bX/ +reference/ diff --git a/service/xboard/service.go b/service/xboard/service.go index 7eb392ed..c9b80038 100644 --- a/service/xboard/service.go +++ b/service/xboard/service.go @@ -74,6 +74,7 @@ type Service struct { access sync.Mutex inboundManager adapter.InboundManager ssCipher string // stored for user key derivation in syncUsers + ssServerKey string // stored for SS2022 per-user key extraction } type XBoardServiceOptions struct { @@ -616,6 +617,7 @@ func (s *Service) setupNode() error { // Store cipher for user key derivation in syncUsers s.ssCipher = method + s.ssServerKey = serverKey // V2bX approach: use server_key from panel DIRECTLY as PSK // The panel provides it already in the correct format (base64 for 2022) @@ -900,7 +902,7 @@ func (s *Service) syncUsers() { } for _, u := range users { - key := u.ResolveKey() + key := s.resolveUserKey(u, isSS2022) if key == "" { continue } @@ -1083,18 +1085,35 @@ type XUser struct { } func (u *XUser) ResolveKey() string { - if u.UUID != "" { - return u.UUID - } if u.Passwd != "" { return u.Passwd } if u.Password != "" { return u.Password } + if u.UUID != "" { + return u.UUID + } return u.Token } +func (s *Service) resolveUserKey(u XUser, isSS2022 bool) string { + key := u.ResolveKey() + if !isSS2022 || key == "" { + return key + } + if strings.Contains(key, ":") { + serverKey, userKey, ok := strings.Cut(key, ":") + if ok && userKey != "" { + if s.ssServerKey != "" && serverKey != "" && serverKey != s.ssServerKey { + s.logger.Warn("Xboard SS2022 user key server key mismatch for user [", u.ID, "]") + } + return userKey + } + } + return key +} + func (s *Service) fetchUsers() ([]XUser, error) { nodeID := s.options.UserNodeID if nodeID == 0 { diff --git a/service/xboard/service_test.go b/service/xboard/service_test.go new file mode 100644 index 00000000..eb51d6c6 --- /dev/null +++ b/service/xboard/service_test.go @@ -0,0 +1,41 @@ +package xboard + +import "testing" + +func TestXUserResolveKeyPrefersPasswordFields(t *testing.T) { + user := XUser{ + UUID: "uuid-value", + Passwd: "passwd-value", + Password: "password-value", + Token: "token-value", + } + + if got := user.ResolveKey(); got != "passwd-value" { + t.Fatalf("ResolveKey() = %q, want %q", got, "passwd-value") + } +} + +func TestResolveUserKeyForSS2022CombinedPassword(t *testing.T) { + service := &Service{ssServerKey: "master-key"} + user := XUser{ + ID: 1, + Password: "master-key:user-key", + UUID: "uuid-value", + } + + if got := service.resolveUserKey(user, true); got != "user-key" { + t.Fatalf("resolveUserKey() = %q, want %q", got, "user-key") + } +} + +func TestResolveUserKeyForNonSS2022UsesResolvedKey(t *testing.T) { + service := &Service{} + user := XUser{ + UUID: "uuid-value", + Passwd: "passwd-value", + } + + if got := service.resolveUserKey(user, false); got != "passwd-value" { + t.Fatalf("resolveUserKey() = %q, want %q", got, "passwd-value") + } +}