cyu / rack-cors

Rack Middleware for handling Cross-Origin Resource Sharing (CORS), which makes cross-origin AJAX possible.
MIT License
3.26k stars 263 forks source link

abstract CORS validation to a public method #208

Closed BlancoSebastianEzequiel closed 3 years ago

BlancoSebastianEzequiel commented 4 years ago

The idea would be to separate to a public method the logic that is in charge of generating the Result, since in case of a miss I want to avoid calling the app.call (env).

The idea behind this, is that i would like to have one custom middleware that call this public method and in case of miss, return an error. Without this feature i have to have two middlewares, one for creating the result (whis is basically having Rack::Cors as a middleware) and another for validating it and returning and error in case of miss. This is because, the method call of Rack::Cors calls to app.call(env) anyway. And i would like to return an unauthorized error (401) and avoid calling app.call(env)

This is what is was trying to achieve:

module ApiUtils
  module Middlewares
    class CorsPolicy
      def initialize(app, cors_policy_options)
        @app = app
        @cors_policy_options = cors_policy_options
      end

      def call(env)
        cors_middleware = Rack::Cors.new(@app, debug: @cors_policy_options[:debug], &method(:cors_options))
        result = cors_middleware.validate(env)
        cors_result = env[Rack::Cors::RACK_CORS]
        return [401, {}, [{ error: cors_result.miss_reason }.to_json]] unless cors_result.hit

        result
      rescue Rack::Cors::Resource::CorsMisconfigurationError => e
        [500, {}, [{ error: e.message }.to_json]]
      end

      private

      def cors_options(config)
        config.allow do |allow|
          allow.origins(@cors_policy_options[:origins])
          allow.resource(
            @cors_policy_options.resource[:path],
            methods: @cors_policy_options.resource[:methods],
            headers: @cors_policy_options.resource[:headers],
            expose: @cors_policy_options.resource[:expose],
            credentials: @cors_policy_options.resource[:credentials]
          )
        end
      end
    end
  end
end
cyu commented 4 years ago

@BlancoSebastianEzequiel You can get the same effect with the example able by passing a proxy of @app instead of the @app as-is. The Proxy would inspect env on call and not call the real @app.call

Something like this:

module ApiUtils
  module Middlewares
    class CorsEnforcer
      def initialize(app)
        @app = app
      end

      def call(env)
        cors_result = env[Rack::Cors::RACK_CORS]
        return [401, {}, [{ error: cors_result.miss_reason }.to_json]] unless cors_result.hit
        @app.call(env)
      end
    end

    class CorsPolicy
      def initialize(app, cors_policy_options)
        @app = CorsEnforcer.new(app)
        @cors_policy_options = cors_policy_options
      end

      def call(env)
        cors_middleware = Rack::Cors.new(@app, debug: @cors_policy_options[:debug], &method(:cors_options))
        cors_middleware.call(env)
      rescue Rack::Cors::Resource::CorsMisconfigurationError => e
        [500, {}, [{ error: e.message }.to_json]]
      end

      private

      def cors_options(config)
        config.allow do |allow|
          allow.origins(@cors_policy_options[:origins])
          allow.resource(
            @cors_policy_options.resource[:path],
            methods: @cors_policy_options.resource[:methods],
            headers: @cors_policy_options.resource[:headers],
            expose: @cors_policy_options.resource[:expose],
            credentials: @cors_policy_options.resource[:credentials]
          )
        end
      end
    end
  end
end
BlancoSebastianEzequiel commented 4 years ago

I end up doing something very similar. I have two middlewares instead of one like in your example.

Maybe your example is better because it uses only one middleware. In my case the order of the two middlewares is important. So i'll take it into account. Thank you!

cyu commented 3 years ago

Closing this since there is a viable solution.