package middleware import ( "net/http" "regexp" "strings" "time" "golang.org/x/time/rate" ) // ── Input Validation ─────────────────────────────────────────────────────── var ( emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) uuidRegex = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) phoneRegex = regexp.MustCompile(`^[+0-9\s()-]{8,20}$`) orgNumRegex = regexp.MustCompile(`^\d{6}-\d{4}$`) ) // ValidateEmail kontrollerar email-format func ValidateEmail(email string) bool { return emailRegex.MatchString(email) } // ValidateUUID kontrollerar UUID-format func ValidateUUID(id string) bool { return uuidRegex.MatchString(id) } // ValidatePhone kontrollerar telefonnummer func ValidatePhone(phone string) bool { return phoneRegex.MatchString(phone) } // ValidateOrgNumber kontrollerar svenskt orgnummer func ValidateOrgNumber(org string) bool { return orgNumRegex.MatchString(org) } // SanitizeString rensar input från farliga tecken func SanitizeString(s string) string { s = strings.TrimSpace(s) // Ta bort potentiellt farliga tecken s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") s = strings.ReplaceAll(s, "\"", """) return s } // ── RBAC (Role Based Access Control) ────────────────────────────────────── type Permission string const ( PermRead Permission = "read" PermWrite Permission = "write" PermDelete Permission = "delete" PermAdmin Permission = "admin" ) type Resource string const ( ResCustomers Resource = "customers" ResEmployees Resource = "employees" ResFinance Resource = "finance" ResLegal Resource = "legal" ResHR Resource = "hr" ResSettings Resource = "settings" ResAudit Resource = "audit" ) // RolePermissions definierar vilka permissions varje roll har var RolePermissions = map[string]map[Resource][]Permission{ "admin": { ResCustomers: {PermRead, PermWrite, PermDelete}, ResEmployees: {PermRead, PermWrite, PermDelete}, ResFinance: {PermRead, PermWrite, PermDelete}, ResLegal: {PermRead, PermWrite, PermDelete}, ResHR: {PermRead, PermWrite, PermDelete}, ResSettings: {PermRead, PermWrite, PermDelete}, ResAudit: {PermRead, PermWrite, PermDelete}, }, "manager": { ResCustomers: {PermRead, PermWrite}, ResEmployees: {PermRead, PermWrite}, ResFinance: {PermRead}, ResLegal: {PermRead}, ResHR: {PermRead, PermWrite}, }, "user": { ResCustomers: {PermRead}, ResEmployees: {PermRead}, ResFinance: {PermRead}, }, "viewer": { ResCustomers: {PermRead}, ResEmployees: {PermRead}, }, } // HasPermission kontrollerar om en roll har en specifik permission func HasPermission(role string, resource Resource, permission Permission) bool { perms, ok := RolePermissions[role] if !ok { return false } resourcePerms, ok := perms[resource] if !ok { return false } for _, p := range resourcePerms { if p == permission || p == PermAdmin { return true } } return false } // RBACMiddleware kontrollerar behörigheter func RBACMiddleware(resource Resource, permission Permission) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Hämta roll från context (satt av auth middleware) role, ok := r.Context().Value("role").(string) if !ok { http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized) return } if !HasPermission(role, resource, permission) { http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden) return } next.ServeHTTP(w, r) }) } } // ── Rate Limiting ───────────────────────────────────────────────────────── type RateLimiter struct { limiters map[string]*rate.Limiter } func NewRateLimiter() *RateLimiter { return &RateLimiter{ limiters: make(map[string]*rate.Limiter), } } func (rl *RateLimiter) GetLimiter(key string) *rate.Limiter { limiter, ok := rl.limiters[key] if !ok { limiter = rate.NewLimiter(rate.Every(time.Second), 10) // 10 req/s rl.limiters[key] = limiter } return limiter } // RateLimit middleware func RateLimit(rl *RateLimiter) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { key := r.RemoteAddr if !rl.GetLimiter(key).Allow() { http.Error(w, `{"error":"rate limit exceeded"}`, http.StatusTooManyRequests) return } next.ServeHTTP(w, r) }) } } // ── Audit Log ───────────────────────────────────────────────────────────── type AuditEvent struct { Timestamp time.Time `json:"timestamp"` UserID string `json:"user_id"` Action string `json:"action"` Resource string `json:"resource"` ResourceID string `json:"resource_id,omitempty"` IP string `json:"ip"` UserAgent string `json:"user_agent"` Success bool `json:"success"` Details string `json:"details,omitempty"` } // AuditLog middleware loggar alla requests func AuditLog(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() // Wrap response writer för att fånga status code wrapped := &responseRecorder{ResponseWriter: w, statusCode: http.StatusOK} next.ServeHTTP(wrapped, r) // Logga audit event event := AuditEvent{ Timestamp: start, Action: r.Method, Resource: r.URL.Path, IP: r.RemoteAddr, UserAgent: r.UserAgent(), Success: wrapped.statusCode < 400, } // Hämta user ID från context om finns if userID, ok := r.Context().Value("user_id").(string); ok { event.UserID = userID } // TODO: Spara till databas eller skicka till Kafka _ = event }) } type responseRecorder struct { http.ResponseWriter statusCode int } func (rr *responseRecorder) WriteHeader(code int) { rr.statusCode = code rr.ResponseWriter.WriteHeader(code) } // ── Security Headers ────────────────────────────────────────────────────── func SecurityHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "1; mode=block") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") w.Header().Set("Content-Security-Policy", "default-src 'self'") next.ServeHTTP(w, r) }) }