fixed region fetching in user_attributes
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
+14
-13
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user