From e7b6fac89c868ad32c2f63c02122680d8d738392 Mon Sep 17 00:00:00 2001 From: Suyunjing Date: Sun, 8 Nov 2026 13:19:15 +0800 Subject: [PATCH] fix(auth): match SQLite username constraint errors (#3295) - Match the UNIQUE constraint text anywhere in the SQLite error message. - Add regression tests for the SSO auto-registration username retry. - Verify the fix with targeted SSO tests and go vet. Co-authored-by: LunelleEverdawn --- server/handles/ssologin.go | 2 +- server/handles/ssologin_register_test.go | 58 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 server/handles/ssologin_register_test.go diff --git a/server/handles/ssologin.go b/server/handles/ssologin.go index 3b152505..c0edb958 100644 --- a/server/handles/ssologin.go +++ b/server/handles/ssologin.go @@ -296,7 +296,7 @@ func autoRegister(username, userID string, err error) (*model.User, error) { SsoID: userID, } if err = db.CreateUser(user); err != nil { - if strings.HasPrefix(err.Error(), "UNIQUE constraint failed") && strings.HasSuffix(err.Error(), "username") { + if strings.Contains(err.Error(), "UNIQUE constraint failed") && strings.Contains(err.Error(), "username") { user.Username = user.Username + "_" + userID if err = db.CreateUser(user); err != nil { return nil, err diff --git a/server/handles/ssologin_register_test.go b/server/handles/ssologin_register_test.go new file mode 100644 index 00000000..6fc9218b --- /dev/null +++ b/server/handles/ssologin_register_test.go @@ -0,0 +1,58 @@ +package handles + +import ( + "errors" + "testing" + + "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/db" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/glebarez/sqlite" + "gorm.io/gorm" +) + +func setupAutoRegisterTest(t *testing.T) { + t.Helper() + database, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + conf.Conf = conf.DefaultConfig(t.TempDir()) + db.Init(database) + for key, value := range map[string]string{ + conf.SSOAutoRegister: "true", + conf.SSODefaultPermission: "0", + conf.SSODefaultDir: "/", + } { + op.Cache.SetSetting(key, &model.SettingItem{Key: key, Value: value}) + } + t.Cleanup(op.Cache.ClearAll) +} + +// Re-login after unbinding SSO: the username is still taken by the unbound +// user, so registration must retry with the SSO ID suffix instead of failing. +func TestAutoRegisterRetriesOnUsernameConflict(t *testing.T) { + setupAutoRegisterTest(t) + if err := db.CreateUser(&model.User{Username: "alice"}); err != nil { + t.Fatal(err) + } + + user, err := autoRegister("alice", "sso-1", gorm.ErrRecordNotFound) + if err != nil { + t.Fatalf("expected retry to succeed, got: %v", err) + } + if user.Username != "alice_sso-1" || user.SsoID != "sso-1" { + t.Fatalf("unexpected user: username=%q sso_id=%q", user.Username, user.SsoID) + } +} + +func TestAutoRegisterReturnsUnrelatedErrors(t *testing.T) { + setupAutoRegisterTest(t) + want := errors.New("unrelated") + + user, err := autoRegister("alice", "sso-1", want) + if user != nil || !errors.Is(err, want) { + t.Fatalf("expected original error, got user=%v err=%v", user, err) + } +}