From 2b9ce566e737cf6d5d95ade56528b41ad02ea2da Mon Sep 17 00:00:00 2001 From: Catlaxy AI Date: Tue, 6 Oct 2026 09:23:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E7=BC=96=E8=BE=91=E5=81=9C?= =?UTF-8?q?=E7=94=A8=E7=94=A8=E6=88=B7=E6=97=B6=E6=B8=85=E9=99=A4=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E7=99=BB=E5=BD=95=E4=BC=9A=E8=AF=9D=20(#686)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 5.5 --- .../app/admin_user_disable_session_test.go | 72 +++++++++++++++++++ backend/internal/app/admin_users.go | 5 ++ 2 files changed, 77 insertions(+) create mode 100644 backend/internal/app/admin_user_disable_session_test.go diff --git a/backend/internal/app/admin_user_disable_session_test.go b/backend/internal/app/admin_user_disable_session_test.go new file mode 100644 index 000000000..fe60363f0 --- /dev/null +++ b/backend/internal/app/admin_user_disable_session_test.go @@ -0,0 +1,72 @@ +package app + +import ( + "testing" + "time" + + "infinite-canvas/backend/internal/model" + "infinite-canvas/backend/internal/repository" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestUpdateUserDisableRevokesSessions(t *testing.T) { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + sqlDB, err := db.DB() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) + if err := db.AutoMigrate(&model.User{}, &model.AuthSession{}, &model.AdminAuditEvent{}); err != nil { + t.Fatal(err) + } + svc := &Service{repo: repository.New(db)} + actor := &model.User{ID: "admin", Username: "admin", Role: model.UserRoleAdmin, Status: model.UserStatusActive} + user := model.User{ID: "member", Username: "member", Role: model.UserRoleUser, Status: model.UserStatusActive} + other := model.User{ID: "other", Username: "other", Role: model.UserRoleUser, Status: model.UserStatusActive} + for _, u := range []*model.User{actor, &user, &other} { + if err := db.Create(u).Error; err != nil { + t.Fatal(err) + } + } + for _, session := range []model.AuthSession{ + {ID: "one", UserID: user.ID, TokenHash: hashToken("token-one"), ExpiresAt: time.Now().Add(time.Hour)}, + {ID: "two", UserID: user.ID, TokenHash: hashToken("token-two"), ExpiresAt: time.Now().Add(time.Hour)}, + {ID: "other", UserID: other.ID, TokenHash: hashToken("token-other"), ExpiresAt: time.Now().Add(time.Hour)}, + } { + if err := db.Create(&session).Error; err != nil { + t.Fatal(err) + } + } + if _, err := svc.UpdateUser(actor, user.ID, UpdateUserRequest{DisplayName: "Creator"}); err != nil { + t.Fatal(err) + } + if _, err := svc.CurrentUser("one.token-one"); err != nil { + t.Fatalf("profile edit revoked session: %v", err) + } + if _, err := svc.UpdateUser(actor, user.ID, UpdateUserRequest{Status: model.UserStatusDisabled}); err != nil { + t.Fatal(err) + } + var count int64 + if err := db.Model(&model.AuthSession{}).Where("user_id = ?", user.ID).Count(&count).Error; err != nil { + t.Fatal(err) + } + if count != 0 { + t.Errorf("disabled user session count = %d, want 0", count) + } + if _, err := svc.UpdateUser(actor, user.ID, UpdateUserRequest{Status: model.UserStatusActive}); err != nil { + t.Fatal(err) + } + for _, cookie := range []string{"one.token-one", "two.token-two"} { + if _, err := svc.CurrentUser(cookie); err == nil { + t.Errorf("old cookie revived after re-enable: %s", cookie) + } + } + if _, err := svc.CurrentUser("other.token-other"); err != nil { + t.Fatalf("other user's session revoked: %v", err) + } +} diff --git a/backend/internal/app/admin_users.go b/backend/internal/app/admin_users.go index a0e1cd8b6..84c951db4 100644 --- a/backend/internal/app/admin_users.go +++ b/backend/internal/app/admin_users.go @@ -238,6 +238,11 @@ func (s *Service) UpdateUser(actor *model.User, userID string, req UpdateUserReq return nil, fmt.Errorf("清理旧登录会话失败,密码未更新:%w", err) } } + if nextStatus == model.UserStatusDisabled { + if err := s.repo.DeleteUserAuthSessions(user.ID); err != nil { + return nil, err + } + } user.Role = nextRole user.Status = nextStatus user.UpdatedAt = time.Now()