feat: Implement initial Go backend and React frontend project structure for the gotail application.
This commit is contained in:
209
backend/internal/middleware/middleware.go
Normal file
209
backend/internal/middleware/middleware.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// AuthMiddleware implementa autenticação HTTP Basic
|
||||
type AuthMiddleware struct {
|
||||
enabled bool
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
// NewAuthMiddleware cria uma nova instância do middleware de autenticação
|
||||
func NewAuthMiddleware(enabled bool, username, password string) *AuthMiddleware {
|
||||
return &AuthMiddleware{
|
||||
enabled: enabled,
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
// Handler retorna o handler HTTP com autenticação
|
||||
func (m *AuthMiddleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !m.enabled {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
m.requestAuth(w)
|
||||
return
|
||||
}
|
||||
|
||||
usernameMatch := subtle.ConstantTimeCompare([]byte(user), []byte(m.username)) == 1
|
||||
passwordMatch := subtle.ConstantTimeCompare([]byte(pass), []byte(m.password)) == 1
|
||||
|
||||
if usernameMatch && passwordMatch {
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
log.Warn().
|
||||
Str("username", user).
|
||||
Str("remote_addr", r.RemoteAddr).
|
||||
Msg("Authentication failed")
|
||||
m.requestAuth(w)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) requestAuth(w http.ResponseWriter) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Web Tail Pro - Restricted Access"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized\n"))
|
||||
}
|
||||
|
||||
// CORSMiddleware implementa CORS
|
||||
type CORSMiddleware struct {
|
||||
allowedOrigins []string
|
||||
}
|
||||
|
||||
// NewCORSMiddleware cria uma nova instância do middleware CORS
|
||||
func NewCORSMiddleware(origins []string) *CORSMiddleware {
|
||||
return &CORSMiddleware{allowedOrigins: origins}
|
||||
}
|
||||
|
||||
// Handler retorna o handler HTTP com CORS
|
||||
func (m *CORSMiddleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
if m.isAllowedOrigin(origin) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *CORSMiddleware) isAllowedOrigin(origin string) bool {
|
||||
if len(m.allowedOrigins) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, allowed := range m.allowedOrigins {
|
||||
if allowed == "*" || allowed == origin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RateLimitMiddleware implementa rate limiting por IP
|
||||
type RateLimitMiddleware struct {
|
||||
limiters map[string]*rate.Limiter
|
||||
mu sync.RWMutex
|
||||
rps int
|
||||
}
|
||||
|
||||
// NewRateLimitMiddleware cria uma nova instância do middleware de rate limiting
|
||||
func NewRateLimitMiddleware(rps int) *RateLimitMiddleware {
|
||||
return &RateLimitMiddleware{
|
||||
limiters: make(map[string]*rate.Limiter),
|
||||
rps: rps,
|
||||
}
|
||||
}
|
||||
|
||||
// Handler retorna o handler HTTP com rate limiting
|
||||
func (m *RateLimitMiddleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ip := m.getIP(r)
|
||||
limiter := m.getLimiter(ip)
|
||||
|
||||
if !limiter.Allow() {
|
||||
log.Warn().Str("ip", ip).Str("path", r.URL.Path).Msg("Rate limit exceeded")
|
||||
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *RateLimitMiddleware) getLimiter(ip string) *rate.Limiter {
|
||||
m.mu.RLock()
|
||||
limiter, exists := m.limiters[ip]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
m.mu.Lock()
|
||||
limiter = rate.NewLimiter(rate.Limit(m.rps), m.rps*2)
|
||||
m.limiters[ip] = limiter
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
return limiter
|
||||
}
|
||||
|
||||
func (m *RateLimitMiddleware) getIP(r *http.Request) string {
|
||||
// Tentar obter IP real de headers de proxy
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
ip = r.Header.Get("X-Real-IP")
|
||||
}
|
||||
if ip == "" {
|
||||
ip = strings.Split(r.RemoteAddr, ":")[0]
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// SecurityHeadersMiddleware adiciona headers de segurança
|
||||
func SecurityHeadersMiddleware(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'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'")
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// LoggingMiddleware registra todas as requisições HTTP
|
||||
func LoggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// Wrapper para capturar status code
|
||||
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
|
||||
next.ServeHTTP(wrapped, r)
|
||||
|
||||
log.Info().
|
||||
Str("method", r.Method).
|
||||
Str("path", r.URL.Path).
|
||||
Str("remote_addr", r.RemoteAddr).
|
||||
Int("status", wrapped.statusCode).
|
||||
Dur("duration", time.Since(start)).
|
||||
Msg("HTTP request")
|
||||
})
|
||||
}
|
||||
|
||||
// responseWriter é um wrapper para capturar o status code
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
rw.statusCode = code
|
||||
rw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
Reference in New Issue
Block a user