chore: implement config management and HTTP transport layer with CORS support
Some checks failed
CI Pipeline / Lint (push) Successful in 6m3s
CI Pipeline / Security Scan (push) Successful in 6m2s
CI Pipeline / Test (push) Failing after 3m9s
CI Pipeline / Build (push) Has been skipped
CI Pipeline / Notification (push) Successful in 2s

This commit is contained in:
ulflow_phattt2901 2025-05-25 17:52:51 +07:00
parent 9df4673657
commit 74528f2d86
5 changed files with 103 additions and 29 deletions

View File

@ -104,7 +104,9 @@ func (l *ViperConfigLoader) loadEnvFile(v *viper.Viper) error {
// Lưu giá trị gốc cho tương thích ngược
v.Set(key, val)
os.Setenv(key, val)
if err := os.Setenv(key, val); err != nil {
return fmt.Errorf("failed to set environment variable %s: %w", key, err)
}
}
return nil

View File

@ -34,8 +34,6 @@ type DatabaseConfig struct {
MigrationPath string `mapstructure:"migration_path" validate:"required"`
}
<<<<<<< Updated upstream
=======
// JWTConfig chứa cấu hình cho JWT
type JWTConfig struct {
Secret string `mapstructure:"secret" validate:"required,min=32"`
@ -46,7 +44,7 @@ type JWTConfig struct {
Audience []string `mapstructure:"audience" validate:"required,min=1"`
}
>>>>>>> Stashed changes
// Config là struct tổng thể chứa tất cả các cấu hình
type Config struct {
App AppConfig `mapstructure:"app" validate:"required"`

View File

@ -0,0 +1,88 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
// CORSConfig chứa cấu hình cho CORS
type CORSConfig struct {
AllowOrigins []string `yaml:"allow_origins"`
AllowMethods []string `yaml:"allow_methods"`
AllowHeaders []string `yaml:"allow_headers"`
ExposeHeaders []string `yaml:"expose_headers"`
AllowCredentials bool `yaml:"allow_credentials"`
MaxAge int `yaml:"max_age"`
}
// DefaultCORSConfig trả về cấu hình CORS mặc định
func DefaultCORSConfig() CORSConfig {
return CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * 60 * 60, // 12 hours
}
}
// CORS trả về middleware xử lý CORS
func CORS(config CORSConfig) gin.HandlerFunc {
return func(c *gin.Context) {
// Set CORS headers
origin := c.GetHeader("Origin")
if len(config.AllowOrigins) == 0 {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
} else {
for _, o := range config.AllowOrigins {
if o == "*" || o == origin {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
break
}
}
}
if config.AllowCredentials {
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
}
if len(config.ExposeHeaders) > 0 {
c.Writer.Header().Set("Access-Control-Expose-Headers", joinStrings(config.ExposeHeaders, ", "))
}
// Handle preflight requests
if c.Request.Method == "OPTIONS" {
if len(config.AllowMethods) > 0 {
c.Writer.Header().Set("Access-Control-Allow-Methods", joinStrings(config.AllowMethods, ", "))
}
if len(config.AllowHeaders) > 0 {
c.Writer.Header().Set("Access-Control-Allow-Headers", joinStrings(config.AllowHeaders, ", "))
}
if config.MaxAge > 0 {
c.Writer.Header().Set("Access-Control-Max-Age", string(rune(config.MaxAge)))
}
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// Hàm hỗ trợ nối các chuỗi
func joinStrings(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
if len(strs) == 1 {
return strs[0]
}
result := strs[0]
for _, s := range strs[1:] {
result += sep + s
}
return result
}

View File

@ -19,7 +19,7 @@ func TestSecurityMiddlewares(t *testing.T) {
config := middleware.DefaultSecurityConfig()
// Tùy chỉnh cấu hình nếu cần
config.CORS.AllowedOrigins = []string{"https://example.com"}
config.CORS.AllowOrigins = []string{"https://example.com"}
config.RateLimit.Rate = 100 // 100 requests per minute
config.Headers.ContentSecurityPolicy = "default-src 'self'; script-src 'self'"
@ -39,7 +39,10 @@ func TestSecurityMiddlewares(t *testing.T) {
t.Run("Test CORS", func(t *testing.T) {
resp, err := http.Get(ts.URL + "/test")
assert.NoError(t, err)
defer resp.Body.Close()
defer func() {
err := resp.Body.Close()
assert.NoError(t, err, "failed to close response body")
}()
// Kiểm tra CORS header
assert.Equal(t, "https://example.com", resp.Header.Get("Access-Control-Allow-Origin"))
@ -51,7 +54,8 @@ func TestSecurityMiddlewares(t *testing.T) {
for i := 0; i < 110; i++ {
resp, err := http.Get(ts.URL + "/test")
assert.NoError(t, err)
resp.Body.Close()
err = resp.Body.Close()
assert.NoError(t, err, "failed to close response body")
if i >= 100 {
// Sau 100 request, server sẽ trả về 429
@ -67,7 +71,10 @@ func TestSecurityMiddlewares(t *testing.T) {
t.Run("Test Security Headers", func(t *testing.T) {
resp, err := http.Get(ts.URL + "/test")
assert.NoError(t, err)
defer resp.Body.Close()
defer func() {
err := resp.Body.Close()
assert.NoError(t, err, "failed to close response body")
}()
// Kiểm tra các security headers
headers := []string{

View File

@ -22,30 +22,9 @@ func SetupRouter(cfg *config.Config) *gin.Engine {
// Recovery middleware
router.Use(gin.Recovery())
<<<<<<< Updated upstream
// CORS middleware nếu cần
// router.Use(middleware.CORS())
=======
// CORS middleware
router.Use(middleware.CORS(middleware.DefaultCORSConfig()))
// Khởi tạo repositories
userRepo := persistence.NewUserRepository(db)
roleRepo := persistence.NewRoleRepository(db)
// Khởi tạo services
authSvc := service.NewAuthService(
userRepo,
roleRepo,
cfg.JWT.Secret,
time.Duration(cfg.JWT.AccessTokenExpire)*time.Minute,
cfg.JWT.RefreshTokenExpire * 24, // Convert days to hours
)
// Khởi tạo middleware
authMiddleware := middleware.NewAuthMiddleware(authSvc)
>>>>>>> Stashed changes
// Khởi tạo các handlers
healthHandler := handler.NewHealthHandler(cfg)