# Security Best Practices & Hardening Guide

## Overview

This document outlines security best practices for the Fotbal Club application based on OWASP Top 10 and industry standards.

---

## 1. Authentication & Authorization

### Current Implementation ✅
- JWT-based authentication with HttpOnly cookies
- Password hashing using bcrypt
- Role-based access control (admin, editor, user)

### Improvements Needed

#### 1.1 Account Lockout
**Risk**: Brute force attacks on login endpoint

**Implementation**:
```go
// internal/middleware/ratelimit.go - Add to auth controller
type loginAttempts struct {
    sync.Mutex
    attempts map[string][]time.Time
}

var loginAttemptTracker = &loginAttempts{
    attempts: make(map[string][]time.Time),
}

func (lat *loginAttempts) isLocked(email string) bool {
    lat.Lock()
    defer lat.Unlock()
    
    attempts, exists := lat.attempts[email]
    if !exists {
        return false
    }
    
    // Remove attempts older than 15 minutes
    cutoff := time.Now().Add(-15 * time.Minute)
    var recent []time.Time
    for _, t := range attempts {
        if t.After(cutoff) {
            recent = append(recent, t)
        }
    }
    lat.attempts[email] = recent
    
    // Lock after 5 failed attempts in 15 minutes
    return len(recent) >= 5
}

func (lat *loginAttempts) recordFailure(email string) {
    lat.Lock()
    defer lat.Unlock()
    lat.attempts[email] = append(lat.attempts[email], time.Now())
}

func (lat *loginAttempts) clearAttempts(email string) {
    lat.Lock()
    defer lat.Unlock()
    delete(lat.attempts, email)
}
```

#### 1.2 Password Strength Requirements
**Current**: No validation
**Recommended**: Minimum 8 characters, mix of upper/lower/numbers/symbols

```go
func ValidatePasswordStrength(password string) error {
    if len(password) < 8 {
        return errors.New("heslo musí mít alespoň 8 znaků")
    }
    
    var hasUpper, hasLower, hasNumber bool
    for _, char := range password {
        switch {
        case unicode.IsUpper(char):
            hasUpper = true
        case unicode.IsLower(char):
            hasLower = true
        case unicode.IsNumber(char):
            hasNumber = true
        }
    }
    
    if !hasUpper || !hasLower || !hasNumber {
        return errors.New("heslo musí obsahovat velká i malá písmena a čísla")
    }
    
    return nil
}
```

#### 1.3 Session Management
**Recommendation**: Implement session timeout and refresh tokens

```go
// Add to JWT claims
type Claims struct {
    UserID uint   `json:"user_id"`
    Role   string `json:"role"`
    Exp    int64  `json:"exp"`
    Iat    int64  `json:"iat"`
    Jti    string `json:"jti"` // JWT ID for token revocation
    jwt.RegisteredClaims
}

// Token blacklist for logout
var tokenBlacklist = sync.Map{}

func RevokeToken(jti string) {
    tokenBlacklist.Store(jti, time.Now().Add(24 * time.Hour))
}

func IsTokenRevoked(jti string) bool {
    _, exists := tokenBlacklist.Load(jti)
    return exists
}
```

---

## 2. Input Validation & Sanitization

### 2.1 Email Validation
```go
func ValidateEmail(email string) bool {
    re := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    return re.MatchString(email) && len(email) <= 254
}
```

### 2.2 URL Validation
```go
func ValidateURL(rawURL string) error {
    if rawURL == "" {
        return nil
    }
    
    u, err := url.Parse(rawURL)
    if err != nil {
        return err
    }
    
    if u.Scheme != "http" && u.Scheme != "https" {
        return errors.New("pouze HTTP(S) URL jsou povoleny")
    }
    
    return nil
}
```

### 2.3 SQL Injection Prevention
**Current**: ✅ Using GORM (parameterized queries)
**Warning**: Avoid raw SQL unless necessary

```go
// SAFE ✅
db.Where("email = ?", email).First(&user)

// UNSAFE ❌
db.Raw("SELECT * FROM users WHERE email = '" + email + "'").Scan(&user)
```

---

## 3. File Upload Security

### Current Issues
- MIME type validation only
- No antivirus scanning
- No file size per user quota

### Improvements

#### 3.1 Enhanced File Validation
```go
func ValidateUploadedFile(file multipart.File, header *multipart.FileHeader) error {
    // Check file size
    if header.Size > 10*1024*1024 { // 10MB
        return errors.New("soubor je příliš velký")
    }
    
    // Read magic bytes
    buf := make([]byte, 512)
    _, err := file.Read(buf)
    if err != nil {
        return err
    }
    file.Seek(0, 0) // Reset
    
    // Validate MIME
    mime := http.DetectContentType(buf)
    allowed := []string{
        "image/jpeg",
        "image/png",
        "image/gif",
        "image/webp",
        "application/pdf",
    }
    
    valid := false
    for _, m := range allowed {
        if m == mime {
            valid = true
            break
        }
    }
    
    if !valid {
        return errors.New("nepodporovaný typ souboru")
    }
    
    // Check extension matches MIME
    ext := strings.ToLower(filepath.Ext(header.Filename))
    mimeToExt := map[string]string{
        "image/jpeg": ".jpg",
        "image/png":  ".png",
        "image/gif":  ".gif",
        "image/webp": ".webp",
        "application/pdf": ".pdf",
    }
    
    expectedExt := mimeToExt[mime]
    if ext != expectedExt && ext != ".jpeg" { // Allow both .jpg and .jpeg
        return errors.New("přípona souboru neodpovídá typu")
    }
    
    return nil
}
```

#### 3.2 User Upload Quota
```go
func CheckUserUploadQuota(db *gorm.DB, userID uint) error {
    var totalSize int64
    err := db.Model(&models.UploadedFile{}).
        Where("uploaded_by_id = ?", userID).
        Select("COALESCE(SUM(file_size), 0)").
        Scan(&totalSize).Error
    
    if err != nil {
        return err
    }
    
    // 100MB quota per user
    if totalSize > 100*1024*1024 {
        return errors.New("překročena kvóta úložiště")
    }
    
    return nil
}
```

---

## 4. API Security

### 4.1 Rate Limiting by Endpoint
```go
// More granular rate limits
var rateLimits = map[string]struct{ max int; window time.Duration }{
    "/api/v1/auth/login":    {max: 5, window: 15 * time.Minute},
    "/api/v1/contact":       {max: 3, window: 1 * time.Hour},
    "/api/v1/articles":      {max: 100, window: 1 * time.Minute},
    "/api/v1/upload":        {max: 10, window: 1 * time.Hour},
}
```

### 4.2 API Key Management (for external integrations)
```go
type APIKey struct {
    gorm.Model
    Key         string    `gorm:"unique;not null"`
    Name        string    `gorm:"not null"`
    UserID      uint      `gorm:"not null"`
    ExpiresAt   time.Time
    LastUsedAt  *time.Time
    IsActive    bool      `gorm:"default:true"`
}

func ValidateAPIKey(key string) (*APIKey, error) {
    var apiKey APIKey
    err := db.Where("key = ? AND is_active = ? AND expires_at > ?", 
        key, true, time.Now()).First(&apiKey).Error
    
    if err != nil {
        return nil, errors.New("neplatný API klíč")
    }
    
    // Update last used
    now := time.Now()
    apiKey.LastUsedAt = &now
    db.Save(&apiKey)
    
    return &apiKey, nil
}
```

---

## 5. Database Security

### 5.1 Connection Security
```go
// Use SSL for database connection in production
if config.AppConfig.AppEnv == "production" {
    dsn += "?sslmode=require"
}
```

### 5.2 Sensitive Data Encryption
```go
// Encrypt sensitive fields at rest
func EncryptSensitiveData(plaintext string) (string, error) {
    key := []byte(config.AppConfig.EncryptionKey) // 32 bytes
    
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return "", err
    }
    
    ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
    return base64.StdEncoding.EncodeToString(ciphertext), nil
}
```

### 5.3 Backup Encryption
```bash
# Encrypt database backups
pg_dump dbname | gpg -c > backup.sql.gpg
```

---

## 6. Logging & Monitoring

### 6.1 Security Event Logging
```go
func LogSecurityEvent(event string, userID uint, details map[string]interface{}) {
    entry := map[string]interface{}{
        "timestamp": time.Now(),
        "event":     event,
        "user_id":   userID,
        "details":   details,
    }
    
    logger.Warn("SECURITY: %+v", entry)
    
    // Store in database for audit trail
    auditLog := models.AuditLog{
        Event:   event,
        UserID:  userID,
        Details: details,
    }
    db.Create(&auditLog)
}

// Usage
LogSecurityEvent("login_failed", 0, map[string]interface{}{
    "email": email,
    "ip":    c.ClientIP(),
})
```

### 6.2 Sensitive Data Redaction
```go
func RedactSensitiveFields(data map[string]interface{}) map[string]interface{} {
    sensitive := []string{"password", "token", "secret", "api_key"}
    
    for _, field := range sensitive {
        if _, exists := data[field]; exists {
            data[field] = "***REDACTED***"
        }
    }
    
    return data
}
```

---

## 7. Environment Security

### 7.1 Environment Variable Validation
```go
func ValidateRequiredEnvVars() error {
    required := []string{
        "JWT_SECRET",
        "DATABASE_URL",
        "SMTP_HOST",
    }
    
    var missing []string
    for _, key := range required {
        if os.Getenv(key) == "" {
            missing = append(missing, key)
        }
    }
    
    if len(missing) > 0 {
        return fmt.Errorf("chybí povinné proměnné prostředí: %s", 
            strings.Join(missing, ", "))
    }
    
    // Validate JWT secret strength
    if len(os.Getenv("JWT_SECRET")) < 32 {
        return errors.New("JWT_SECRET musí mít alespoň 32 znaků")
    }
    
    return nil
}
```

### 7.2 Secrets Management
```bash
# Use environment-specific .env files
.env.development
.env.staging
.env.production

# Never commit .env files
# Use secret management tools in production:
# - AWS Secrets Manager
# - HashiCorp Vault
# - Azure Key Vault
```

---

## 8. Frontend Security

### 8.1 XSS Prevention
```typescript
// Always sanitize user-generated content
import DOMPurify from 'dompurify';

const SafeHTML = ({ html }: { html: string }) => {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
    ALLOWED_ATTR: ['href'],
  });
  
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
};
```

### 8.2 Sensitive Data in LocalStorage
```typescript
// NEVER store sensitive data in localStorage
// ❌ BAD
localStorage.setItem('jwt_token', token);

// ✅ GOOD - Use HttpOnly cookies
// Token is set by backend in secure cookie
```

### 8.3 Content Security Policy
```typescript
// Report CSP violations
window.addEventListener('securitypolicyviolation', (e) => {
  fetch('/api/v1/csp-report', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      violatedDirective: e.violatedDirective,
      blockedURI: e.blockedURI,
      documentURI: e.documentURI,
    }),
  });
});
```

---

## 9. Dependency Security

### 9.1 Regular Updates
```bash
# Go dependencies
go get -u ./...
go mod tidy

# npm dependencies
npm audit
npm audit fix

# Check for known vulnerabilities
npm install -g snyk
snyk test
```

### 9.2 Dependency Pinning
```json
// package.json - use exact versions in production
{
  "dependencies": {
    "react": "18.2.0",  // Not "^18.2.0"
    "axios": "1.6.2"
  }
}
```

---

## 10. Deployment Security Checklist

- [ ] All environment variables set correctly
- [ ] JWT_SECRET is strong (32+ characters)
- [ ] Database uses SSL/TLS
- [ ] HTTPS enforced (no HTTP)
- [ ] HSTS header enabled
- [ ] CSP configured and tested
- [ ] CSRF protection active
- [ ] Rate limiting enabled
- [ ] File upload limits configured
- [ ] Backup encryption enabled
- [ ] Log monitoring configured
- [ ] Security headers verified
- [ ] Dev/debug features disabled
- [ ] Error messages don't leak sensitive info
- [ ] Default credentials changed
- [ ] API documentation not publicly accessible

---

## Quick Security Review Command

```bash
# Run security checks
./scripts/security-check.sh
```

Create `scripts/security-check.sh`:
```bash
#!/bin/bash

echo "🔒 Security Check"
echo "=================="

# Check for hardcoded secrets
echo "🔍 Checking for hardcoded secrets..."
grep -r "password\s*=\s*['\"]" --include="*.go" --include="*.ts" --include="*.tsx" . || echo "✅ None found"

# Check JWT secret in production
echo "🔍 Checking JWT secret..."
if [ "$APP_ENV" = "production" ] && [ "$JWT_SECRET" = "default-secret-key-change-in-production" ]; then
    echo "❌ CRITICAL: Default JWT secret in production!"
else
    echo "✅ JWT secret OK"
fi

# Check HTTPS
echo "🔍 Checking HTTPS enforcement..."
# Add checks here

# Check dependencies
echo "🔍 Checking npm vulnerabilities..."
cd frontend && npm audit --audit-level=high

echo "✅ Security check complete"
```

---

## Incident Response Plan

1. **Detect**: Monitor logs for suspicious activity
2. **Contain**: Disable compromised accounts/keys
3. **Investigate**: Review audit logs
4. **Remediate**: Apply patches, change secrets
5. **Document**: Record incident details
6. **Review**: Update security measures

---

## Resources

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [CWE Top 25](https://cwe.mitre.org/top25/)
- [Go Security](https://go.dev/security/)
- [React Security](https://react.dev/learn/security)
