233 lines
7.0 KiB
Go
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()
|
|
}
|
|
}
|