diff --git a/README.md b/README.md index 8bdd898fd..8914ddaf2 100644 --- a/README.md +++ b/README.md @@ -3348,11 +3348,13 @@ If you're using a specification with [Security Schemes](https://spec.openapis.or > Out-of-the-box, the server-side code generated by `oapi-codegen` does not provide security validation. > > To perform authentication, you will need to use the [validation middleware](#requestresponse-validation-middleware). -> -> In the future, we plan to [implement server-side validation in the generated code](https://github.com/oapi-codegen/oapi-codegen/issues/1524) To see how this can work, check out the [authenticated API example](examples/authenticated-api/echo). +If an operation includes an empty `{}` security alternative, generated server +middleware treats that operation as allowing anonymous requests and doesn't embed required +security scopes in the request context. + ### On the client With a generated client, you'll want to use the client's generated `WithRequestEditorFn` function to pass in a given request editor `RequestEditorFn`. diff --git a/internal/test/servers/middleware/fiber/main_test.go b/internal/test/servers/middleware/fiber/main_test.go index 5fb54530f..82b358cb4 100644 --- a/internal/test/servers/middleware/fiber/main_test.go +++ b/internal/test/servers/middleware/fiber/main_test.go @@ -3,10 +3,15 @@ package serversmiddlewarefiber import ( "net/http" "net/http/httptest" + "os" + "path/filepath" + "runtime" + "strings" "testing" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type impl struct{} @@ -21,14 +26,6 @@ func (i *impl) Test(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } -// hasSecurityScopes returns true if the BearerAuthScopes key was set in context, -// even if the scopes slice is empty (an empty slice means the security scheme is -// defined on the operation with no required scopes, which still requires auth). -func hasSecurityScopes(c *fiber.Ctx) bool { - _, ok := c.Context().UserValue(BearerAuthScopes).([]string) - return ok -} - func TestIssue518(t *testing.T) { server := &impl{} @@ -47,23 +44,17 @@ func TestIssue518(t *testing.T) { }, HandlerMiddlewares: []HandlerMiddlewareFunc{ func(c *fiber.Ctx, next fiber.Handler) error { - if hasSecurityScopes(c) && c.Get(fiber.HeaderAuthorization) == "" { - return c.SendStatus(fiber.StatusUnauthorized) - } return next(c) }, }, }) }) - t.Run("secured endpoint requires auth when scopes are present", func(t *testing.T) { + t.Run("endpoint with anonymous security alternative allows missing auth", func(t *testing.T) { r := fiber.New() RegisterHandlersWithOptions(r, server, FiberServerOptions{ HandlerMiddlewares: []HandlerMiddlewareFunc{ func(c *fiber.Ctx, next fiber.Handler) error { - if hasSecurityScopes(c) && c.Get(fiber.HeaderAuthorization) == "" { - return c.SendStatus(fiber.StatusUnauthorized) - } return next(c) }, }, @@ -72,7 +63,7 @@ func TestIssue518(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/auth-check", nil) resp, err := r.Test(req) assert.NoError(t, err) - assert.Equal(t, fiber.StatusUnauthorized, resp.StatusCode) + assert.Equal(t, fiber.StatusOK, resp.StatusCode) req = httptest.NewRequest(http.MethodGet, "/auth-check", nil) req.Header.Set(fiber.HeaderAuthorization, "Bearer token") @@ -87,6 +78,16 @@ func TestIssue518(t *testing.T) { }) } +func TestIssue518AnonymousSecurityAlternativeDoesNotEmitScopes(t *testing.T) { + _, testFile, _, ok := runtime.Caller(0) + assert.True(t, ok) + + generatedPath := filepath.Join(filepath.Dir(testFile), "middleware.gen.go") + generated, err := os.ReadFile(generatedPath) + require.NoError(t, err) + assert.False(t, strings.Contains(string(generated), "SetUserValue")) +} + // From issue-1469: registering fiber handlers (with and without options) for // an operation with an empty-object requestBody must not panic. func TestIssue1469(t *testing.T) { diff --git a/internal/test/servers/middleware/fiber/middleware.gen.go b/internal/test/servers/middleware/fiber/middleware.gen.go index 3544cb156..c279fb75e 100644 --- a/internal/test/servers/middleware/fiber/middleware.gen.go +++ b/internal/test/servers/middleware/fiber/middleware.gen.go @@ -7,10 +7,6 @@ import ( "github.com/gofiber/fiber/v2" ) -const ( - BearerAuthScopes bearerAuthContextKey = "bearerAuth.Scopes" -) - // bearerAuthContextKey is the context key for bearerAuth security scheme type bearerAuthContextKey string @@ -36,8 +32,6 @@ type HandlerMiddlewareFunc func(c *fiber.Ctx, next fiber.Handler) error // AuthCheck operation middleware func (siw *ServerInterfaceWrapper) AuthCheck(c *fiber.Ctx) error { - c.Context().SetUserValue((BearerAuthScopes), []string{}) - handler := func(c *fiber.Ctx) error { return siw.Handler.AuthCheck(c) } diff --git a/internal/test/servers/middleware/fiber/spec.yaml b/internal/test/servers/middleware/fiber/spec.yaml index 8f0aaf950..f02bab850 100644 --- a/internal/test/servers/middleware/fiber/spec.yaml +++ b/internal/test/servers/middleware/fiber/spec.yaml @@ -9,6 +9,7 @@ paths: get: operationId: authCheck security: + - {} - bearerAuth: [] responses: 200: diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 55e512a66..3924b43ef 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -300,6 +300,9 @@ func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequiremen outDefs := make([]SecurityDefinition, 0) for _, sr := range securityRequirements { + if len(sr) == 0 { + return nil + } for _, k := range SortedMapKeys(sr) { v := sr[k] outDefs = append(outDefs, SecurityDefinition{ProviderName: k, Scopes: v})