Files
satusehat-worker/internal/infrastructure/transport/http/middleware/middleware.go
T
2026-04-14 01:21:54 +00:00

233 lines
7.0 KiB
Go

package middleware
import (
"context"
"log"
"net/http"
"os"
"strings"
"time"
"service/pkg/logger" // Tambahkan import ini
"github.com/google/uuid"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func CORSMiddleware() gin.HandlerFunc {
// Development mode - izinkan semua origin (hanya untuk dev!)
if os.Getenv("APP_ENV") == "development" && os.Getenv("CORS_ALLOW_ALL") == "true" {
log.Println("WARNING: CORS allowing all origins (development mode)")
// Gunakan config khusus untuk allow all origins
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AllowCredentials = false // Tidak bisa digunakan dengan AllowAllOrigins
config.AllowMethods = []string{
"GET", "POST", "PUT", "PATCH", "DELETE",
"HEAD", "OPTIONS",
}
config.AllowHeaders = []string{
"Origin",
"Content-Length",
"Content-Type",
"Authorization",
"X-Requested-With",
"X-API-Key",
"X-CSRF-Token",
"X-Custom-Header",
"Accept",
"Accept-Language",
"Accept-Encoding",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
// Headers tambahan untuk Nuxt 3
"x-use-fetch",
"x-nuxt-base-url",
"x-forwarded-for",
"x-forwarded-proto",
"x-forwarded-host",
}
config.MaxAge = 12 * time.Hour
return cors.New(config)
}
// Config untuk specific origins
config := cors.DefaultConfig()
// Baca allowed origins dari environment variable
// Format: CORS_ORIGINS=http://localhost:3000,http://localhost:3001,https://myapp.com
originsEnv := os.Getenv("CORS_ORIGINS")
if originsEnv != "" {
// Split by comma dan trim spaces
origins := strings.Split(originsEnv, ",")
for i, origin := range origins {
origins[i] = strings.TrimSpace(origin)
}
config.AllowOrigins = origins
log.Printf("CORS: Using origins from environment: %v", config.AllowOrigins)
} else {
// Default origins untuk Nuxt 3 development
config.AllowOrigins = []string{
"http://localhost:3000", // Nuxt 3 default
"http://localhost:3001", // Nuxt 3 alternatif
"http://localhost:3002", // Nuxt 3 alternatif
"http://localhost:3005", // Nuxt 3 port Anda
"http://localhost:8080", // Common dev port
"http://localhost:5173", // Vite default port
"http://localhost:5174", // Vite alternatif
"https://localhost:3000", // HTTPS Nuxt
"https://localhost:8080", // HTTPS common
"http://meninjar.dev.rssa.id:8094", // Domain production Anda
}
log.Printf("CORS: Using default origins: %v", config.AllowOrigins)
}
// Method yang diizinkan untuk Nuxt 3 + TypeScript
config.AllowMethods = []string{
"GET", "POST", "PUT", "PATCH", "DELETE",
"HEAD", "OPTIONS",
}
// Headers yang diizinkan untuk Nuxt 3 + Axios
config.AllowHeaders = []string{
"Origin",
"Content-Length",
"Content-Type",
"Authorization",
"X-Requested-With",
"X-API-Key",
"X-CSRF-Token",
"X-Custom-Header",
"Accept",
"Accept-Language",
"Accept-Encoding",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
// Headers tambahan untuk Nuxt 3
"x-use-fetch",
"x-nuxt-base-url",
"x-forwarded-for",
"x-forwarded-proto",
"x-forwarded-host",
}
// Izinkan credentials (penting untuk Nuxt 3)
config.AllowCredentials = true
// Preflight cache duration
config.MaxAge = 12 * time.Hour
return cors.New(config)
}
func LoggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Ambil atau Generate Request ID untuk Tracing (Correlation ID)
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
// Injeksi request_id ke dalam context request
ctx := context.WithValue(c.Request.Context(), "request_id", requestID)
c.Request = c.Request.WithContext(ctx)
c.Header("X-Request-ID", requestID)
// Process request
c.Next()
// Log using custom logger
latency := time.Since(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
fields := []logger.Field{
logger.String("ip", clientIP),
logger.String("method", method),
logger.String("path", path),
logger.Int("status", statusCode),
logger.Duration("latency", latency),
logger.String("user_agent", c.Request.UserAgent()),
}
if raw != "" {
fields = append(fields, logger.String("query", raw))
}
if len(c.Errors) > 0 {
fields = append(fields, logger.String("error", c.Errors.String()))
}
logCtx := logger.Default().WithContext(ctx)
// Use appropriate log level based on status code
if statusCode >= 500 {
logCtx.Error("HTTP Request", fields...)
} else if statusCode >= 400 {
logCtx.Warn("HTTP Request", fields...)
} else {
logCtx.Info("HTTP Request", fields...)
}
}
}
func ErrorMiddleware() gin.HandlerFunc {
return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
logger.Default().WithContext(c.Request.Context()).Error("Panic recovered", logger.Any("panic", recovered))
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal server error",
"code": "INTERNAL_ERROR",
})
})
}
func SecurityMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. HSTS (Strict-Transport-Security)
// Memaksa browser hanya menggunakan HTTPS selama 1 tahun, termasuk subdomain.
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
// 2. Content Security Policy (CSP) - Code Injection Protection
// Kita longgarkan khusus untuk path /swagger agar UI bisa melakukan load JavaScript & CSS inline bawaannya.
if strings.HasPrefix(c.Request.URL.Path, "/swagger") {
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none'")
} else {
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content")
}
// 3. X-Content-Type-Options
// Mencegah browser menebak (sniffing) MIME type.
c.Header("X-Content-Type-Options", "nosniff")
// 4. X-Frame-Options (Clickjacking Protection)
// Mencegah website di-embed dalam iframe orang lain.
c.Header("X-Frame-Options", "DENY")
// 5. X-XSS-Protection
// Layer pertahanan lama untuk browser lama (Legacy), tapi tetap bagus untuk ada.
c.Header("X-XSS-Protection", "1; mode=block")
// 6. Referrer-Policy
// Menjaga privasi user saat klik link keluar dari aplikasi Anda.
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
// 7. Permissions-Policy (Feature Policy)
// Mematikan fitur browser yang tidak dipakai (kamera, mic, lokasi) untuk mengurangi attack vector.
c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=()")
// Remove Information Leakage
c.Header("Server", "Unknown") // Atau hapus total
c.Header("X-Powered-By", "")
c.Next()
}
}