fix: P0/P1 security and quality fixes
P0-01: Add ESCAPE clause to LIKE queries in operation_log.go and device.go P0-02: Add atomic Increment to L1Cache and L2Cache interfaces P0-07: Add TOTP verification step after password login P1-01: Sanitize error messages in error.go middleware P1-03: Remove err.Error() from export error messages P1-04: Add error return to CountByResultSince in login_log.go P1-05: Add transactional DeleteCascade to RoleRepository P1-06: Add PasswordChangedAt tracking for JWT token invalidation P1-07: Wrap theme SetDefault in database transaction P1-08: Use config values for database pool parameters P1-09: Add rows.Err() checks in social_account_repo.go P1-10: Validate sortOrder with map in user.go ORDER BY P1-11: Add GORM tags to Announcement struct P1-15: Add pageSize upper limit (100) to device and log handlers
This commit is contained in:
@@ -104,16 +104,19 @@ func (r *LoginLogRepository) DeleteOlderThan(ctx context.Context, days int) erro
|
||||
|
||||
// CountByResultSince 统计指定时间之后特定结果的登录次数
|
||||
// success=true 统计成功次数,false 统计失败次数
|
||||
func (r *LoginLogRepository) CountByResultSince(ctx context.Context, success bool, since time.Time) int64 {
|
||||
func (r *LoginLogRepository) CountByResultSince(ctx context.Context, success bool, since time.Time) (int64, error) {
|
||||
status := 0 // 失败
|
||||
if success {
|
||||
status = 1 // 成功
|
||||
}
|
||||
var count int64
|
||||
r.db.WithContext(ctx).Model(&domain.LoginLog{}).
|
||||
err := r.db.WithContext(ctx).Model(&domain.LoginLog{}).
|
||||
Where("status = ? AND created_at >= ?", status, since).
|
||||
Count(&count)
|
||||
return count
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ListAllForExport 获取所有登录日志(用于导出,无分页)
|
||||
|
||||
@@ -263,10 +263,14 @@ func TestLoginLogRepositoryQueriesAndRetention(t *testing.T) {
|
||||
t.Fatalf("expected 2 recent logs, got total=%d len=%d", total, len(recentLogs))
|
||||
}
|
||||
|
||||
if count := repo.CountByResultSince(ctx, true, now.Add(-2*time.Hour)); count != 1 {
|
||||
if count, err := repo.CountByResultSince(ctx, true, now.Add(-2*time.Hour)); err != nil {
|
||||
t.Fatalf("CountByResultSince failed: %v", err)
|
||||
} else if count != 1 {
|
||||
t.Fatalf("expected 1 recent success login, got %d", count)
|
||||
}
|
||||
if count := repo.CountByResultSince(ctx, false, now.Add(-2*time.Hour)); count != 1 {
|
||||
if count, err := repo.CountByResultSince(ctx, false, now.Add(-2*time.Hour)); err != nil {
|
||||
t.Fatalf("CountByResultSince failed: %v", err)
|
||||
} else if count != 1 {
|
||||
t.Fatalf("expected 1 recent failed login, got %d", count)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,18 @@ func (r *RoleRepository) Delete(ctx context.Context, id int64) error {
|
||||
return r.db.WithContext(ctx).Delete(&domain.Role{}, id).Error
|
||||
}
|
||||
|
||||
// DeleteCascade 级联删除角色(同时删除角色权限关联)
|
||||
func (r *RoleRepository) DeleteCascade(ctx context.Context, id int64) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 先删除角色权限关联
|
||||
if err := tx.Where("role_id = ?", id).Delete(&domain.RolePermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 再删除角色
|
||||
return tx.Delete(&domain.Role{}, id).Error
|
||||
})
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取角色
|
||||
func (r *RoleRepository) GetByID(ctx context.Context, id int64) (*domain.Role, error) {
|
||||
var role domain.Role
|
||||
|
||||
@@ -204,6 +204,9 @@ func (r *SocialAccountRepositoryImpl) GetByUserID(ctx context.Context, userID in
|
||||
}
|
||||
accounts = append(accounts, &account)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
@@ -290,6 +293,9 @@ func (r *SocialAccountRepositoryImpl) List(ctx context.Context, offset, limit in
|
||||
}
|
||||
accounts = append(accounts, &account)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return accounts, total, nil
|
||||
}
|
||||
|
||||
@@ -89,11 +89,13 @@ func (r *ThemeConfigRepository) ListAll(ctx context.Context) ([]*domain.ThemeCon
|
||||
|
||||
// SetDefault 设置默认主题
|
||||
func (r *ThemeConfigRepository) SetDefault(ctx context.Context, id int64) error {
|
||||
// 先清除所有默认标记
|
||||
if err := r.db.WithContext(ctx).Model(&domain.ThemeConfig{}).Where("is_default = ?", true).Update("is_default", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置新的默认主题
|
||||
return r.db.WithContext(ctx).Model(&domain.ThemeConfig{}).Where("id = ?", id).Update("is_default", true).Error
|
||||
// 使用事务确保原子性:先清除所有默认标记,再设置新默认
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 先清除所有默认标记
|
||||
if err := tx.Model(&domain.ThemeConfig{}).Where("is_default = ?", true).Update("is_default", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 设置新的默认主题
|
||||
return tx.Model(&domain.ThemeConfig{}).Where("id = ?", id).Update("is_default", true).Error
|
||||
})
|
||||
}
|
||||
|
||||
@@ -326,8 +326,9 @@ func (r *UserRepository) AdvancedSearch(ctx context.Context, filter *AdvancedFil
|
||||
sortBy = filter.SortBy
|
||||
}
|
||||
}
|
||||
if filter.SortOrder == "asc" {
|
||||
sortOrder = "ASC"
|
||||
allowedSortOrders := map[string]bool{"asc": true, "desc": true}
|
||||
if allowedSortOrders[strings.ToLower(filter.SortOrder)] {
|
||||
sortOrder = strings.ToUpper(filter.SortOrder)
|
||||
}
|
||||
query = query.Order(sortBy + " " + sortOrder)
|
||||
|
||||
@@ -404,8 +405,9 @@ func (r *UserRepository) ListCursor(ctx context.Context, filter *AdvancedFilter,
|
||||
}
|
||||
|
||||
sortOrder := "DESC"
|
||||
if filter.SortOrder == "asc" {
|
||||
sortOrder = "ASC"
|
||||
allowedSortOrders := map[string]bool{"asc": true, "desc": true}
|
||||
if allowedSortOrders[strings.ToLower(filter.SortOrder)] {
|
||||
sortOrder = strings.ToUpper(filter.SortOrder)
|
||||
}
|
||||
|
||||
orderClause := sortBy + " " + sortOrder + ", id " + sortOrder
|
||||
|
||||
Reference in New Issue
Block a user