From 29cf10c37980d4ee1722b3f765964bf4cb669302 Mon Sep 17 00:00:00 2001 From: F04C Date: Thu, 16 Apr 2026 13:42:50 +0800 Subject: [PATCH] fixed region fetching in user_attributes --- go.mod | 7 ++++--- go.sum | 12 ++++++++++++ handlers/authorize.go | 27 ++++++++++++++------------- handlers/health.go | 7 ++++--- helper/response.go | 28 ---------------------------- middleware/jwt.go | 7 ++++--- middleware/rate_limiter.go | 9 +++++---- 7 files changed, 43 insertions(+), 54 deletions(-) delete mode 100644 helper/response.go diff --git a/go.mod b/go.mod index 9ac0c34..b182bc5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,10 @@ module authorization go 1.25.1 require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/alicebob/miniredis/v2 v2.35.0 github.com/getsentry/sentry-go v0.39.0 + github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/gorilla/mux v1.8.1 @@ -17,17 +20,15 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/KyleBanks/depth v1.2.1 // indirect - github.com/alicebob/miniredis/v2 v2.35.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cespares/response v0.0.0-20260416052801-b22a14921d12 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/swag v0.19.15 // indirect - github.com/go-redis/redismock/v9 v9.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/go.sum b/go.sum index 3b31cfd..cbd2161 100644 --- a/go.sum +++ b/go.sum @@ -14,12 +14,16 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespares/response v0.0.0-20260416052801-b22a14921d12 h1:VtwZEFXJADUIpECuGittyHNLRAkEOr2HdCcticfz6Ow= +github.com/cespares/response v0.0.0-20260416052801-b22a14921d12/go.mod h1:+9XxGj6565PWnCxvMvC0QCrg90beAzkK5jOHNNPNG3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/getsentry/sentry-go v0.39.0 h1:uhnexj8PNCyCve37GSqxXOeXHh4cJNLNNB4w70Jtgo0= github.com/getsentry/sentry-go v0.39.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -67,6 +71,12 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= +github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -129,6 +139,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/handlers/authorize.go b/handlers/authorize.go index 0dad462..4532089 100644 --- a/handlers/authorize.go +++ b/handlers/authorize.go @@ -1,7 +1,6 @@ package handlers import ( - "authorization/helper" "authorization/middleware" "authorization/models" "authorization/services" @@ -9,6 +8,8 @@ import ( "io" "log" "net/http" + + sabat "github.com/cespares/response" ) var authService *models.CachedAuthorizationService @@ -36,7 +37,7 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { claims, ok := middleware.GetClaims(r) if !ok { log.Printf("ERROR: Missing JWT claims in request context (method=%s, path=%s)", r.Method, r.URL.Path) - helper.RespondWithError(w, http.StatusUnauthorized, "Unauthorized") + sabat.RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -48,7 +49,7 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { bodyBytes, err := io.ReadAll(r.Body) if err != nil { log.Printf("ERROR: Failed to read authorization request body: %v", err) - helper.RespondWithError(w, http.StatusBadRequest, "Invalid request body") + sabat.RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } log.Printf("Raw authorization request body: %s", string(bodyBytes)) @@ -56,7 +57,7 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { // Decode JSON into AuthorizationContext if err := json.Unmarshal(bodyBytes, &ctx); err != nil { log.Printf("ERROR: Failed to unmarshal request body: %v", err) - helper.RespondWithError(w, http.StatusBadRequest, "Invalid request payload") + sabat.RespondWithError(w, http.StatusBadRequest, "Invalid request payload") return } @@ -65,7 +66,7 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { log.Printf("User ID ctx=%s, resource=%s, action=%s, roleID=%d", ctx.UsersID, ctx.Resource, ctx.Action, ctx.RoleID) if ctx.UsersID == "" || ctx.Resource == "" || ctx.Action == "" { log.Printf("ERROR: Missing required fields - UsersID=%s, Resource=%s, Action=%s", ctx.UsersID, ctx.Resource, ctx.Action) - helper.RespondWithError(w, http.StatusBadRequest, "Missing required fields: users_id, resource, action") + sabat.RespondWithError(w, http.StatusBadRequest, "Missing required fields: users_id, resource, action") return } @@ -74,7 +75,7 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { // Verify JWT user matches request user (security check) if ctx.UsersID != claims.UsersID { log.Printf("ERROR: User ID mismatch - ctx.UsersID='%s' vs claims.UsersID='%s'", ctx.UsersID, claims.UsersID) - helper.RespondWithError(w, http.StatusForbidden, "User ID mismatch") + sabat.RespondWithError(w, http.StatusForbidden, "User ID mismatch") return } @@ -107,7 +108,7 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { log.Printf("[Handler] Role candidate resolution - requested=%v, finalCandidates=%v", requestedRoles, validRoles) if len(validRoles) == 0 { log.Printf("ERROR: Role mismatch for user=%s - requestedRoles=%v, claimRoles=%v", ctx.UsersID, requestedRoles, claimRoles) - helper.RespondWithError(w, http.StatusForbidden, "Role ID mismatch") + sabat.RespondWithError(w, http.StatusForbidden, "Role ID mismatch") return } @@ -119,29 +120,29 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) { log.Printf("[Handler] Performing authorization check for user=%s, resource=%s, action=%s", ctx.UsersID, ctx.Resource, ctx.Action) result, err := services.AuthorizeWithCache(authService, &ctx) if err != nil { - helper.LogError(err, "Authorization service error") + sabat.LogError(err, "Authorization service error") log.Printf("✗ Authorization service error for user=%s: %v", ctx.UsersID, err) - helper.RespondWithError(w, http.StatusInternalServerError, "Authorization check failed") + sabat.RespondWithError(w, http.StatusInternalServerError, "Authorization check failed") return } // Return result if result.Allowed { log.Printf("✓ [Handler] Authorization ALLOWED - Returning 200 OK to client") - // Return response matching AuthorizationResponse model for client compatibility + // Return sabat matching Authorizationsabat model for client compatibility response := map[string]interface{}{ "allowed": result.Allowed, "reason": result.Message, } - helper.RespondWithJSON(w, http.StatusOK, response) + sabat.RespondWithJSON(w, http.StatusOK, response) } else { log.Printf("✗ [Handler] Authorization DENIED - Returning 403 Forbidden to client (reason: %s)", result.Message) - // Return response matching AuthorizationResponse model for client compatibility + // Return sabat matching Authorizationsabat model for client compatibility response := map[string]interface{}{ "allowed": result.Allowed, "reason": result.Message, } - helper.RespondWithJSON(w, http.StatusForbidden, response) + sabat.RespondWithJSON(w, http.StatusForbidden, response) } } diff --git a/handlers/health.go b/handlers/health.go index 36d38c5..d1dd1f8 100644 --- a/handlers/health.go +++ b/handlers/health.go @@ -2,13 +2,14 @@ package handlers import ( "authorization/db" - "authorization/helper" "authorization/models" "authorization/redisclient" "context" "encoding/json" "net/http" "time" + + sabat "github.com/cespares/response" ) // HealthHandler provides a basic liveness check @@ -22,7 +23,7 @@ func HealthHandler(w http.ResponseWriter, r *http.Request) { response := models.HealthResponse{ Status: "ok", } - helper.RespondWithJSON(w, http.StatusOK, response) + sabat.RespondWithJSON(w, http.StatusOK, response) } // ReadyHandler checks if the service is ready to handle requests @@ -81,6 +82,6 @@ func ReadyHandler(w http.ResponseWriter, r *http.Request) { Status: status, Services: services, }); err != nil { - helper.LogError(err, "Error encoding health response") + sabat.LogError(err, "Error encoding health response") } } diff --git a/helper/response.go b/helper/response.go deleted file mode 100644 index d8bd6a2..0000000 --- a/helper/response.go +++ /dev/null @@ -1,28 +0,0 @@ -package helper - -import ( - "encoding/json" - "net/http" -) - -func RespondWithError(w http.ResponseWriter, statusCode int, message string) { - w.Header().Set(ContentTypeHeader, ApplicationJSON) - w.WriteHeader(statusCode) - if encodeErr := json.NewEncoder(w).Encode(map[string]string{ErrorLabel: message}); encodeErr != nil { - LogError(encodeErr, ErrorEncodingResponse) - } -} - -func RespondWithMessage(w http.ResponseWriter, message string) { - if encodeErr := json.NewEncoder(w).Encode(map[string]string{MessageLabel: message}); encodeErr != nil { - LogError(encodeErr, ErrorEncodingResponse) - } -} - -func RespondWithJSON(w http.ResponseWriter, statusCode int, data interface{}) { - w.Header().Set(ContentTypeHeader, ApplicationJSON) - w.WriteHeader(statusCode) - if encodeErr := json.NewEncoder(w).Encode(data); encodeErr != nil { - LogError(encodeErr, ErrorEncodingResponse) - } -} diff --git a/middleware/jwt.go b/middleware/jwt.go index 549db0b..4bb30b2 100644 --- a/middleware/jwt.go +++ b/middleware/jwt.go @@ -1,7 +1,6 @@ package middleware import ( - "authorization/helper" "authorization/models" "authorization/redisclient" "context" @@ -16,6 +15,8 @@ import ( "sync" "time" + sabat "github.com/cespares/response" + "github.com/golang-jwt/jwt/v5" ) @@ -208,7 +209,7 @@ func JWTAuth(next http.HandlerFunc) http.HandlerFunc { // Extract token from header tokenString, ok := extractBearerToken(r.Header.Get("Authorization")) if !ok { - helper.RespondWithError(w, http.StatusUnauthorized, "Unauthorized") + sabat.RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -223,7 +224,7 @@ func JWTAuth(next http.HandlerFunc) http.HandlerFunc { // Parse and validate token claims, err := parseAndValidateToken(tokenString) if err != nil { - helper.RespondWithError(w, http.StatusUnauthorized, errExpiredToken) + sabat.RespondWithError(w, http.StatusUnauthorized, errExpiredToken) return } diff --git a/middleware/rate_limiter.go b/middleware/rate_limiter.go index b565ce4..6955f33 100644 --- a/middleware/rate_limiter.go +++ b/middleware/rate_limiter.go @@ -1,13 +1,14 @@ package middleware import ( - "authorization/helper" "authorization/models" "authorization/redisclient" "context" "fmt" "net/http" "time" + + sabat "github.com/cespares/response" ) // DefaultRateLimitConfig returns default rate limiting settings @@ -24,7 +25,7 @@ func RateLimiterMiddleware(config models.RateLimitConfig) func(http.HandlerFunc) return func(w http.ResponseWriter, r *http.Request) { // Fail-open: Skip rate limiting if Redis is not available (prevents full outage) if redisclient.RDB == nil { - helper.LogError(nil, "Rate limiter: Redis not available, allowing request (fail-open)") + sabat.LogError(nil, "Rate limiter: Redis not available, allowing request (fail-open)") next.ServeHTTP(w, r) return } @@ -41,13 +42,13 @@ func RateLimiterMiddleware(config models.RateLimitConfig) func(http.HandlerFunc) allowed, err := checkRateLimit(identifier, config) if err != nil { // On error, fail open (allow request) but log the error - helper.LogError(err, "rate limiter error") + sabat.LogError(err, "rate limiter error") next.ServeHTTP(w, r) return } if !allowed { - helper.RespondWithError(w, http.StatusTooManyRequests, "Rate limit exceeded") + sabat.RespondWithError(w, http.StatusTooManyRequests, "Rate limit exceeded") return }