From f6e3a1fc7682fdfb5de5b04bfd9cb68b07a5f0c2 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Wed, 8 Apr 2026 21:38:07 +0200 Subject: [PATCH] chore: move multi error handler to shared --- packages/api/internal/db/apikeys.go | 9 --- packages/api/internal/utils/error.go | 73 +------------------ packages/api/main.go | 2 +- packages/auth/pkg/auth/multi_error_handler.go | 71 ++++++++++++++++++ packages/dashboard-api/main.go | 11 +-- 5 files changed, 77 insertions(+), 89 deletions(-) delete mode 100644 packages/api/internal/db/apikeys.go create mode 100644 packages/auth/pkg/auth/multi_error_handler.go diff --git a/packages/api/internal/db/apikeys.go b/packages/api/internal/db/apikeys.go deleted file mode 100644 index 684d16ca8a..0000000000 --- a/packages/api/internal/db/apikeys.go +++ /dev/null @@ -1,9 +0,0 @@ -package db - -import ( - sharedauth "github.com/e2b-dev/infra/packages/auth/pkg/auth" -) - -type TeamForbiddenError = sharedauth.TeamForbiddenError - -type TeamBlockedError = sharedauth.TeamBlockedError diff --git a/packages/api/internal/utils/error.go b/packages/api/internal/utils/error.go index e22f9f78a7..1b604a8048 100644 --- a/packages/api/internal/utils/error.go +++ b/packages/api/internal/utils/error.go @@ -1,27 +1,17 @@ package utils import ( - "errors" "fmt" "net/http" "strings" - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel/attribute" - "github.com/e2b-dev/infra/packages/api/internal/auth" - "github.com/e2b-dev/infra/packages/api/internal/db" + "github.com/e2b-dev/infra/packages/auth/pkg/auth" "github.com/e2b-dev/infra/packages/shared/pkg/telemetry" ) -const ( - securityErrPrefix = "error in openapi3filter.SecurityRequirementsError: security requirements failed: " - forbiddenErrPrefix = "team forbidden: " - blockedErrPrefix = "team blocked: " -) - func ErrorHandler(c *gin.Context, message string, statusCode int) { var errMsg error @@ -49,7 +39,7 @@ func ErrorHandler(c *gin.Context, message string, statusCode int) { c.Error(errMsg) // Handle forbidden errors - if after, ok := strings.CutPrefix(message, forbiddenErrPrefix); ok { + if after, ok := strings.CutPrefix(message, auth.ForbiddenErrPrefix); ok { c.AbortWithStatusJSON( http.StatusForbidden, gin.H{ @@ -62,7 +52,7 @@ func ErrorHandler(c *gin.Context, message string, statusCode int) { } // Handle blocked errors - if after, ok := strings.CutPrefix(message, blockedErrPrefix); ok { + if after, ok := strings.CutPrefix(message, auth.BlockedErrPrefix); ok { c.AbortWithStatusJSON( http.StatusForbidden, gin.H{ @@ -75,7 +65,7 @@ func ErrorHandler(c *gin.Context, message string, statusCode int) { } // Handle security requirements errors from the openapi3filter - if after, ok := strings.CutPrefix(message, securityErrPrefix); ok { + if after, ok := strings.CutPrefix(message, auth.SecurityErrPrefix); ok { // Keep the original status code as it can be also timeout (read body timeout) error code. // The securityErrPrefix is added for all errors going through the processCustomErrors function. c.AbortWithStatusJSON( @@ -91,58 +81,3 @@ func ErrorHandler(c *gin.Context, message string, statusCode int) { c.AbortWithStatusJSON(statusCode, gin.H{"code": statusCode, "message": fmt.Errorf("validation error: %s", message).Error()}) } - -// MultiErrorHandler handles wrapped SecurityRequirementsError, so there are no multiple errors returned to the user. -func MultiErrorHandler(me openapi3.MultiError) error { - if len(me) == 0 { - return nil - } - err := me[0] - - // Recreate logic from oapi-codegen/gin-middleware to handle the error - // Source: https://github.com/oapi-codegen/gin-middleware/blob/main/oapi_validate.go - switch e := err.(type) { //nolint:errorlint // we copied this and don't want it to change - case *openapi3filter.RequestError: - // We've got a bad request - // Split up the verbose error by lines and return the first one - // openapi errors seem to be multi-line with a decent message on the first - errorLines := strings.Split(e.Error(), "\n") - - return fmt.Errorf("error in openapi3filter.RequestError: %s", errorLines[0]) - case *openapi3filter.SecurityRequirementsError: - return processCustomErrors(e) // custom implementation - default: - // This should never happen today, but if our upstream code changes, - // we don't want to crash the server, so handle the unexpected error. - return fmt.Errorf("error validating request: %w", err) - } -} - -func processCustomErrors(e *openapi3filter.SecurityRequirementsError) error { - // Return only one security requirement error (there may be multiple securitySchemes) - unwrapped := e.Errors - err := unwrapped[0] - - var teamForbidden *db.TeamForbiddenError - var teamBlocked *db.TeamBlockedError - // Return only the first non-missing authorization header error (if possible) - for _, errW := range unwrapped { - if errors.Is(errW, auth.ErrNoAuthHeader) { - continue - } - - if errors.As(errW, &teamForbidden) { - return fmt.Errorf("%s%s", forbiddenErrPrefix, err.Error()) - } - - if errors.As(errW, &teamBlocked) { - return fmt.Errorf("%s%s", blockedErrPrefix, err.Error()) - } - - err = errW - - break - } - - return fmt.Errorf("%s%s", securityErrPrefix, err.Error()) -} diff --git a/packages/api/main.go b/packages/api/main.go index c6ada17499..58f63a165d 100644 --- a/packages/api/main.go +++ b/packages/api/main.go @@ -189,7 +189,7 @@ func NewGinServer(ctx context.Context, config cfg.Config, tel *telemetry.Client, statusCode := max(c.Writer.Status(), fallbackStatusCode) utils.ErrorHandler(c, message, statusCode) }, - MultiErrorHandler: utils.MultiErrorHandler, + MultiErrorHandler: auth.MultiErrorHandler, Options: openapi3filter.Options{ AuthenticationFunc: AuthenticationFunc, // Handle multiple errors as MultiError type diff --git a/packages/auth/pkg/auth/multi_error_handler.go b/packages/auth/pkg/auth/multi_error_handler.go new file mode 100644 index 0000000000..835c5f425b --- /dev/null +++ b/packages/auth/pkg/auth/multi_error_handler.go @@ -0,0 +1,71 @@ +package auth + +import ( + "errors" + "fmt" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" +) + +const ( + SecurityErrPrefix = "error in openapi3filter.SecurityRequirementsError: security requirements failed: " + ForbiddenErrPrefix = "team forbidden: " + BlockedErrPrefix = "team blocked: " +) + +// MultiErrorHandler handles wrapped SecurityRequirementsError, so there are no multiple errors returned to the user. +func MultiErrorHandler(me openapi3.MultiError) error { + if len(me) == 0 { + return nil + } + err := me[0] + + // Recreate logic from oapi-codegen/gin-middleware to handle the error + // Source: https://github.com/oapi-codegen/gin-middleware/blob/main/oapi_validate.go + switch e := err.(type) { //nolint:errorlint // we copied this and don't want it to change + case *openapi3filter.RequestError: + // We've got a bad request + // Split up the verbose error by lines and return the first one + // openapi errors seem to be multi-line with a decent message on the first + errorLines := strings.Split(e.Error(), "\n") + + return fmt.Errorf("error in openapi3filter.RequestError: %s", errorLines[0]) + case *openapi3filter.SecurityRequirementsError: + return processCustomErrors(e) // custom implementation + default: + // This should never happen today, but if our upstream code changes, + // we don't want to crash the server, so handle the unexpected error. + return fmt.Errorf("error validating request: %w", err) + } +} + +func processCustomErrors(e *openapi3filter.SecurityRequirementsError) error { + // Return only one security requirement error (there may be multiple securitySchemes) + unwrapped := e.Errors + err := unwrapped[0] + + var teamForbidden *TeamForbiddenError + var teamBlocked *TeamBlockedError + // Return only the first non-missing authorization header error (if possible) + for _, errW := range unwrapped { + if errors.Is(errW, ErrNoAuthHeader) { + continue + } + + if errors.As(errW, &teamForbidden) { + return fmt.Errorf("%s%s", ForbiddenErrPrefix, err.Error()) + } + + if errors.As(errW, &teamBlocked) { + return fmt.Errorf("%s%s", BlockedErrPrefix, err.Error()) + } + + err = errW + + break + } + + return fmt.Errorf("%s%s", SecurityErrPrefix, err.Error()) +} diff --git a/packages/dashboard-api/main.go b/packages/dashboard-api/main.go index ad924fce4c..14762c9a87 100644 --- a/packages/dashboard-api/main.go +++ b/packages/dashboard-api/main.go @@ -9,13 +9,11 @@ import ( "os" "os/signal" "strconv" - "strings" "sync" "sync/atomic" "syscall" "time" - "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" @@ -214,14 +212,7 @@ func run() int { "message": message, }) }, - MultiErrorHandler: func(me openapi3.MultiError) error { - msgs := make([]string, 0, len(me)) - for _, e := range me { - msgs = append(msgs, e.Error()) - } - - return fmt.Errorf("%s", strings.Join(msgs, "; ")) - }, + MultiErrorHandler: sharedauth.MultiErrorHandler, Options: openapi3filter.Options{ AuthenticationFunc: authenticationFunc, },