veza/veza-backend-api/internal/repositories/user_repository.go

154 lines
6.1 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
package repositories
import (
"context"
"fmt"
"time"
"veza-backend-api/internal/models"
"github.com/google/uuid"
2025-12-03 19:29:37 +00:00
"gorm.io/gorm"
)
// UserRepository définit les méthodes pour interagir avec le modèle User
// (Cette interface est celle utilisée par les autres packages qui dépendent de ce repository)
// MIGRATION UUID: Toutes les méthodes utilisent maintenant uuid.UUID au lieu de int64
2025-12-03 19:29:37 +00:00
type UserRepository interface {
CreateUser(ctx context.Context, user *models.User) error
GetUserByID(ctx context.Context, id uuid.UUID) (*models.User, error)
2025-12-03 19:29:37 +00:00
GetUserByEmail(ctx context.Context, email string) (*models.User, error)
GetUserByUsername(ctx context.Context, username string) (*models.User, error)
UpdateUser(ctx context.Context, user *models.User) error
DeleteUser(ctx context.Context, id uuid.UUID) error
UpdateLastLoginAt(ctx context.Context, userID uuid.UUID) error
IncrementTokenVersion(ctx context.Context, userID uuid.UUID) error
2025-12-03 19:29:37 +00:00
}
// GormUserRepository est une implémentation de UserRepository utilisant GORM
type GormUserRepository struct {
db *gorm.DB
}
// NewGormUserRepository crée une nouvelle instance de GormUserRepository
func NewGormUserRepository(db *gorm.DB) *GormUserRepository {
return &GormUserRepository{db: db}
}
// CreateUser crée un nouvel utilisateur dans la base de données
func (r *GormUserRepository) CreateUser(ctx context.Context, user *models.User) error {
return r.db.WithContext(ctx).Create(user).Error
}
// GetUserByID récupère un utilisateur par son ID
// MIGRATION UUID: Accepte maintenant uuid.UUID au lieu de int64
func (r *GormUserRepository) GetUserByID(ctx context.Context, id uuid.UUID) (*models.User, error) {
2025-12-03 19:29:37 +00:00
var user models.User
if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil {
2025-12-03 19:29:37 +00:00
if err == gorm.ErrRecordNotFound {
return nil, nil // Utilisateur non trouvé
}
return nil, fmt.Errorf("failed to get user by ID: %w", err)
}
return &user, nil
}
// GetUserByEmail récupère un utilisateur par son email
func (r *GormUserRepository) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user models.User
if err := r.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil // Utilisateur non trouvé
}
return nil, fmt.Errorf("failed to get user by email: %w", err)
}
return &user, nil
}
// GetUserByUsername récupère un utilisateur par son nom d'utilisateur
func (r *GormUserRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
var user models.User
if err := r.db.WithContext(ctx).Where("username = ?", username).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil // Utilisateur non trouvé
}
return nil, fmt.Errorf("failed to get user by username: %w", err)
}
return &user, nil
}
// UpdateUser met à jour un utilisateur existant
func (r *GormUserRepository) UpdateUser(ctx context.Context, user *models.User) error {
return r.db.WithContext(ctx).Save(user).Error
}
// DeleteUser supprime un utilisateur (soft delete si GORM est configuré pour ça)
// BE-DB-003: Add soft delete support to all models
// MIGRATION UUID: Accepte maintenant uuid.UUID au lieu de int64
func (r *GormUserRepository) DeleteUser(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&models.User{}, "id = ?", id).Error
2025-12-03 19:29:37 +00:00
}
// RestoreUser restaure un utilisateur supprimé (soft delete)
// BE-DB-003: Add soft delete support to all models
func (r *GormUserRepository) RestoreUser(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Unscoped().Model(&models.User{}).Where("id = ?", id).Update("deleted_at", nil).Error
}
// HardDeleteUser supprime définitivement un utilisateur (hard delete)
// BE-DB-003: Add soft delete support to all models
func (r *GormUserRepository) HardDeleteUser(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Unscoped().Delete(&models.User{}, "id = ?", id).Error
}
2025-12-03 19:29:37 +00:00
// UpdateLastLoginAt met à jour le champ last_login_at pour un utilisateur
// MIGRATION UUID: Accepte maintenant uuid.UUID au lieu de int64
func (r *GormUserRepository) UpdateLastLoginAt(ctx context.Context, userID uuid.UUID) error {
2025-12-03 19:29:37 +00:00
return r.db.WithContext(ctx).Model(&models.User{}).Where("id = ?", userID).Update("last_login_at", time.Now()).Error
}
// IncrementTokenVersion incrémente la version du token d'un utilisateur
// MIGRATION UUID: Accepte maintenant uuid.UUID au lieu de int64
func (r *GormUserRepository) IncrementTokenVersion(ctx context.Context, userID uuid.UUID) error {
2025-12-03 19:29:37 +00:00
return r.db.WithContext(ctx).Model(&models.User{}).Where("id = ?", userID).Update("token_version", gorm.Expr("token_version + ?", 1)).Error
}
// --- Compatibility methods for services.UserRepository interface ---
// MIGRATION UUID: Parse UUID string directement au lieu de int64
2025-12-03 19:29:37 +00:00
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
// SECURITY(HIGH-003): Legacy convenience methods now require context propagation.
// context.Background() bypass removed to prevent request timeout/cancellation bypass.
// Callers must pass the HTTP request context to enable proper timeout and cancellation.
func (r *GormUserRepository) GetByID(ctx context.Context, id string) (*models.User, error) {
userID, err := uuid.Parse(id)
2025-12-03 19:29:37 +00:00
if err != nil {
return nil, fmt.Errorf("invalid user ID format (expected UUID): %w", err)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
return r.GetUserByID(ctx, userID)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
func (r *GormUserRepository) GetByEmail(ctx context.Context, email string) (*models.User, error) {
return r.GetUserByEmail(ctx, email)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
func (r *GormUserRepository) GetByUsername(ctx context.Context, username string) (*models.User, error) {
return r.GetUserByUsername(ctx, username)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
func (r *GormUserRepository) Create(ctx context.Context, user *models.User) error {
return r.CreateUser(ctx, user)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
func (r *GormUserRepository) Update(ctx context.Context, user *models.User) error {
return r.UpdateUser(ctx, user)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
func (r *GormUserRepository) Delete(ctx context.Context, id string) error {
userID, err := uuid.Parse(id)
2025-12-03 19:29:37 +00:00
if err != nil {
return fmt.Errorf("invalid user ID format (expected UUID): %w", err)
2025-12-03 19:29:37 +00:00
}
fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings Security fixes implemented: CRITICAL: - CRIT-001: IDOR on chat rooms — added IsRoomMember check before returning room data or message history (returns 404, not 403) - CRIT-002: play_count/like_count exposed publicly — changed JSON tags to "-" so they are never serialized in API responses HIGH: - HIGH-001: TOCTOU race on marketplace downloads — transaction + SELECT FOR UPDATE on GetDownloadURL - HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256) - HIGH-003: context.Background() bypass in user repository — full context propagation from handlers → services → repository (29 files) - HIGH-004: Race condition on promo codes — SELECT FOR UPDATE - HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE - HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default - HIGH-007: RGPD hard delete incomplete — added cleanup for sessions, settings, follows, notifications, audit_logs anonymization - HIGH-008: RTMP callback auth weak — fail-closed when unconfigured, header-only (no query param), constant-time compare - HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn and verifies IsHost before processing - HIGH-010: Moderator self-strike — added issuedBy != userID check MEDIUM: - MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand - MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256) Updated REMEDIATION_MATRIX: 14 findings marked ✅ CORRIGÉ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 04:40:53 +00:00
return r.DeleteUser(ctx, userID)
2025-12-03 19:29:37 +00:00
}