mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
13 KiB
13 KiB
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:
// 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
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
// 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
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
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
// 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
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
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
// 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)
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
// Use SSL for database connection in production
if config.AppConfig.AppEnv == "production" {
dsn += "?sslmode=require"
}
5.2 Sensitive Data Encryption
// 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
# Encrypt database backups
pg_dump dbname | gpg -c > backup.sql.gpg
6. Logging & Monitoring
6.1 Security Event Logging
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
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
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
# 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
// 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
// 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
// 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
# 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
// 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
# Run security checks
./scripts/security-check.sh
Create scripts/security-check.sh:
#!/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
- Detect: Monitor logs for suspicious activity
- Contain: Disable compromised accounts/keys
- Investigate: Review audit logs
- Remediate: Apply patches, change secrets
- Document: Record incident details
- Review: Update security measures