first commit
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user