fix(admin): 编辑停用用户时清除全部登录会话 (#686)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Catlaxy AI
2026-10-06 09:23:46 +08:00
committed by GitHub
parent fd692634d6
commit 2b9ce566e7
2 changed files with 77 additions and 0 deletions

View File

@@ -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)
}
}

View File

@@ -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()