Perbaikan Middelware dan tool generete

This commit is contained in:
2025-08-21 22:44:55 +07:00
parent fb41d7720e
commit 80dafd2a69
5 changed files with 133 additions and 39 deletions

View File

@@ -62,7 +62,7 @@ MYSQL_MEDICAL_SSLMODE=disable
KEYCLOAK_ISSUER=https://auth.rssa.top/realms/sandbox KEYCLOAK_ISSUER=https://auth.rssa.top/realms/sandbox
KEYCLOAK_AUDIENCE=nuxtsim-pendaftaran KEYCLOAK_AUDIENCE=nuxtsim-pendaftaran
KEYCLOAK_JWKS_URL=https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs KEYCLOAK_JWKS_URL=https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs
KEYCLOAK_ENABLED=false KEYCLOAK_ENABLED=true
# BPJS Configuration # BPJS Configuration
BPJS_BASEURL=https://apijkn.bpjs-kesehatan.go.id/vclaim-rest BPJS_BASEURL=https://apijkn.bpjs-kesehatan.go.id/vclaim-rest

View File

@@ -0,0 +1,59 @@
package middleware
import (
"fmt"
"net/http"
"api-service/internal/config"
"github.com/gin-gonic/gin"
)
// ConfigurableAuthMiddleware provides flexible authentication based on configuration
func ConfigurableAuthMiddleware(cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
// Skip authentication for development/testing if explicitly disabled
if !cfg.Keycloak.Enabled {
fmt.Println("Authentication is disabled - allowing all requests")
c.Next()
return
}
// Use Keycloak authentication when enabled
AuthMiddleware()(c)
}
}
// StrictAuthMiddleware enforces authentication regardless of Keycloak.Enabled setting
func StrictAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if appConfig == nil {
fmt.Println("AuthMiddleware: Config not initialized")
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "authentication service not configured"})
return
}
// Always enforce authentication
AuthMiddleware()(c)
}
}
// OptionalKeycloakAuthMiddleware allows requests but adds authentication info if available
func OptionalKeycloakAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if appConfig == nil || !appConfig.Keycloak.Enabled {
c.Next()
return
}
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
// No token provided, but continue
c.Next()
return
}
// Try to validate token, but don't fail if invalid
AuthMiddleware()(c)
}
}

View File

@@ -13,6 +13,8 @@ import (
"sync" "sync"
"time" "time"
"api-service/internal/config"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
@@ -22,24 +24,19 @@ var (
ErrInvalidToken = errors.New("invalid token") ErrInvalidToken = errors.New("invalid token")
) )
// Configurable Keycloak parameters - replace with your actual values or load from config/env
const (
KeycloakIssuer = "https://keycloak.example.com/auth/realms/yourrealm"
KeycloakAudience = "your-client-id"
JwksURL = KeycloakIssuer + "/protocol/openid-connect/certs"
)
// JwksCache caches JWKS keys with expiration // JwksCache caches JWKS keys with expiration
type JwksCache struct { type JwksCache struct {
mu sync.RWMutex mu sync.RWMutex
keys map[string]*rsa.PublicKey keys map[string]*rsa.PublicKey
expiresAt time.Time expiresAt time.Time
sfGroup singleflight.Group sfGroup singleflight.Group
config *config.Config
} }
func NewJwksCache() *JwksCache { func NewJwksCache(cfg *config.Config) *JwksCache {
return &JwksCache{ return &JwksCache{
keys: make(map[string]*rsa.PublicKey), keys: make(map[string]*rsa.PublicKey),
config: cfg,
} }
} }
@@ -74,7 +71,17 @@ func (c *JwksCache) GetKey(kid string) (*rsa.PublicKey, error) {
} }
func (c *JwksCache) fetchKeys() (map[string]*rsa.PublicKey, error) { func (c *JwksCache) fetchKeys() (map[string]*rsa.PublicKey, error) {
resp, err := http.Get(JwksURL) if !c.config.Keycloak.Enabled {
return nil, fmt.Errorf("keycloak authentication is disabled")
}
jwksURL := c.config.Keycloak.JwksURL
if jwksURL == "" {
// Construct JWKS URL from issuer if not explicitly provided
jwksURL = c.config.Keycloak.Issuer + "/protocol/openid-connect/certs"
}
resp, err := http.Get(jwksURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -138,11 +145,32 @@ func base64UrlDecode(s string) ([]byte, error) {
return base64.URLEncoding.DecodeString(s) return base64.URLEncoding.DecodeString(s)
} }
var jwksCache = NewJwksCache() // Global config instance
var appConfig *config.Config
var jwksCacheInstance *JwksCache
// InitializeAuth initializes the auth middleware with config
func InitializeAuth(cfg *config.Config) {
appConfig = cfg
jwksCacheInstance = NewJwksCache(cfg)
}
// AuthMiddleware validates Bearer token as Keycloak JWT token // AuthMiddleware validates Bearer token as Keycloak JWT token
func AuthMiddleware() gin.HandlerFunc { func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
if appConfig == nil {
fmt.Println("AuthMiddleware: Config not initialized")
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "authentication service not configured"})
return
}
if !appConfig.Keycloak.Enabled {
// Skip authentication if Keycloak is disabled but log for debugging
fmt.Println("AuthMiddleware: Keycloak authentication is disabled - allowing all requests")
c.Next()
return
}
fmt.Println("AuthMiddleware: Checking Authorization header") // Debug log fmt.Println("AuthMiddleware: Checking Authorization header") // Debug log
authHeader := c.GetHeader("Authorization") authHeader := c.GetHeader("Authorization")
@@ -174,8 +202,8 @@ func AuthMiddleware() gin.HandlerFunc {
return nil, errors.New("kid header not found") return nil, errors.New("kid header not found")
} }
return jwksCache.GetKey(kid) return jwksCacheInstance.GetKey(kid)
}, jwt.WithIssuer(KeycloakIssuer), jwt.WithAudience(KeycloakAudience)) }, jwt.WithIssuer(appConfig.Keycloak.Issuer), jwt.WithAudience(appConfig.Keycloak.Audience))
if err != nil || !token.Valid { if err != nil || !token.Valid {
fmt.Printf("AuthMiddleware: Invalid or expired token: %v\n", err) // Debug log fmt.Printf("AuthMiddleware: Invalid or expired token: %v\n", err) // Debug log

View File

@@ -17,6 +17,9 @@ import (
func RegisterRoutes(cfg *config.Config) *gin.Engine { func RegisterRoutes(cfg *config.Config) *gin.Engine {
router := gin.New() router := gin.New()
// Initialize auth middleware configuration
middleware.InitializeAuth(cfg)
// Add global middleware // Add global middleware
router.Use(middleware.CORSConfig()) router.Use(middleware.CORSConfig())
router.Use(middleware.ErrorHandler()) router.Use(middleware.ErrorHandler())
@@ -55,39 +58,42 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
// BPJS endpoints // BPJS endpoints
bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs) bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs)
v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK) v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK)
// Retribusi endpoints
retribusiHandler := retribusiHandlers.NewRetribusiHandler() // ============= PUBLISHED ROUTES ===============================================
retribusiGroup := v1.Group("/retribusi")
{ // // Retribusi endpoints
retribusiGroup.GET("", retribusiHandler.GetRetribusi) // retribusiHandler := retribusiHandlers.NewRetribusiHandler()
retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic) // Route baru // retribusiGroup := v1.Group("/retribusi")
retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced) // Route pencarian // {
retribusiGroup.GET("/:id", retribusiHandler.GetRetribusiByID) // retribusiGroup.GET("", retribusiHandler.GetRetribusi)
retribusiGroup.POST("", retribusiHandler.CreateRetribusi) // retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic) // Route baru
retribusiGroup.PUT("/:id", retribusiHandler.UpdateRetribusi) // retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced) // Route pencarian
retribusiGroup.DELETE("/:id", retribusiHandler.DeleteRetribusi) // retribusiGroup.GET("/:id", retribusiHandler.GetRetribusiByID)
} // retribusiGroup.POST("", retribusiHandler.CreateRetribusi)
// retribusiGroup.PUT("/:id", retribusiHandler.UpdateRetribusi)
// retribusiGroup.DELETE("/:id", retribusiHandler.DeleteRetribusi)
// }
// ============================================================================= // =============================================================================
// PROTECTED ROUTES (Authentication Required) // PROTECTED ROUTES (Authentication Required)
// ============================================================================= // =============================================================================
// Create protected group with AuthMiddleware // Create protected group with configurable authentication
protected := v1.Group("/") protected := v1.Group("/")
protected.Use(middleware.AuthMiddleware()) // Use Keycloak AuthMiddleware protected.Use(middleware.ConfigurableAuthMiddleware(cfg)) // Use configurable authentication
// User profile (protected) // User profile (protected)
protected.GET("/auth/me", authHandler.Me) protected.GET("/auth/me", authHandler.Me)
// Retribusi endpoints (CRUD operations - should be protected) // Retribusi endpoints (CRUD operations - should be protected)
// retribusiHandler := retribusiHandlers.NewRetribusiHandler() retribusiHandler := retribusiHandlers.NewRetribusiHandler()
// protectedRetribusi := protected.Group("/retribusi") protectedRetribusi := protected.Group("/retribusi")
// { {
// protectedRetribusi.GET("", retribusiHandler.GetRetribusi) // GET /api/v1/retribusi protectedRetribusi.GET("", retribusiHandler.GetRetribusi) // GET /api/v1/retribusi
// protectedRetribusi.GET("/:id", retribusiHandler.GetRetribusiByID) // GET /api/v1/retribusi/:id protectedRetribusi.GET("/:id", retribusiHandler.GetRetribusiByID) // GET /api/v1/retribusi/:id
// protectedRetribusi.POST("/", retribusiHandler.CreateRetribusi) // POST /api/v1/retribusi/ protectedRetribusi.POST("/", retribusiHandler.CreateRetribusi) // POST /api/v1/retribusi/
// protectedRetribusi.PUT("/:id", retribusiHandler.UpdateRetribusi) // PUT /api/v1/retribusi/:id protectedRetribusi.PUT("/:id", retribusiHandler.UpdateRetribusi) // PUT /api/v1/retribusi/:id
// protectedRetribusi.DELETE("/:id", retribusiHandler.DeleteRetribusi) // DELETE /api/v1/retribusi/:id protectedRetribusi.DELETE("/:id", retribusiHandler.DeleteRetribusi) // DELETE /api/v1/retribusi/:id
// } }
// BPJS endpoints (sensitive data - should be protected) // BPJS endpoints (sensitive data - should be protected)
// bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs) // bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs)

View File

@@ -117,7 +117,7 @@ func main() {
// Create directories with improved logic // Create directories with improved logic
var handlerDir, modelDir string var handlerDir, modelDir string
if category != "" { if category != "" {
handlerDir = filepath.Join("internal", "handlers") handlerDir = filepath.Join("internal", "handlers", category)
modelDir = filepath.Join("internal", "models", category) modelDir = filepath.Join("internal", "models", category)
} else { } else {
handlerDir = filepath.Join("internal", "handlers") handlerDir = filepath.Join("internal", "handlers")
@@ -248,6 +248,7 @@ func New` + data.Name + `Handler() *` + data.Name + `Handler {
handlerContent += generateHelperMethods(data) handlerContent += generateHelperMethods(data)
writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent)
} }
func generateGetMethods(data HandlerData) string { func generateGetMethods(data HandlerData) string {
@@ -1490,7 +1491,7 @@ func updateRoutesFile(data HandlerData) {
newRoutes := generateProtectedRouteBlock(data) newRoutes := generateProtectedRouteBlock(data)
// Insert above protected routes marker // Insert above protected routes marker
insertMarker := "// =============================================================================" insertMarker := "// ============= PUBLISHED ROUTES ==============================================="
if strings.Contains(routesContent, insertMarker) { if strings.Contains(routesContent, insertMarker) {
if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) {
// Insert before the marker // Insert before the marker