Kalvin Chang
Kalvin Chang

Reputation: 60

Rails RoutingError from a constraint failing (IP whitelisting a file)

I am trying to IP whitelist access to a particular file by placing a constraint on the file's route.

## config/routes.rb
get 'SOME FILE',
      to: 'main#(function that renders the file as plain)',
      constraints: <IP CONSTRAINT CLASS>.new


## (ip constraint class).rb
require 'ipaddr'
WHITELIST = %w(8.8.8.8/27).freeze

    def matches?(request)
      request && APPLE_IP_WHITELIST.reduce(false) do |match, range|
        match || IPAddr.new(range).include?(request.remote_addr)
      end
    end

For IPs outside of the whitelist, a RoutingError is thrown. I try to handle the error by using a catch all route.

## config/routes.rb
get '*unmatched_route', :to => 'application#show_not_found', via: :all


## app/controllers/application_controller.rb
def show_not_found
  render_error_status(404)
end

def render_error_status(status)
    @status = status
    template = case @status
               when 400, 401, 403, 405
                 'application/errors/standard'
               when 404, 422
                 'application/errors/with_block'
               when 500
                 'application/errors/custom_500'
               end
    render template: template, layout: 'error', status: @status
end

Interestingly, when I try to access some nonsense route, like 'localhost:3000/asdf', the proper 404 page (application/errors/with_block.html.haml) is shown. However, when the whitelist RoutingError is handled, I get a

ActionView::MissingTemplate (Missing template application/errors/with_block with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :haml, :coffee]}. Searched in: * "... /app/views" * "... /.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/kaminari-0.17.0/app/views" ):

tldr: how do I handle RoutingError resulting from a constraint failing?

Edit: it appears that formats: [:html] needs to added when calling render - but why is this necessary now and not the other times when render_error_status is called?

Upvotes: 0

Views: 134

Answers (1)

Deepesh
Deepesh

Reputation: 6418

The main issue is with the request type, when the routing constraint fails Rails tries to find file with text format, see the error:

ActionView::MissingTemplate (Missing template application/errors/with_block with {:locale=>[:en], :formats=>[:text]

This is because you are requesting a text file from the route which cannot be returned because your routing constraint failed. So when the request type is text the error page should also be in that format. For example if I have an API where I send a JSON request and if there is an issue of permission and I provide HTML response then it will be of no use to the API. So the request and response format should be same in Rails when using the default conventions.

Your method should have different response types like this:

def render_error_status(status)
  status = status
  template = case status
             when 400, 401, 403, 405
               'application/errors/standard'
             when 404, 422
               'application/errors/with_block'
             when 500
               'application/errors/custom_500'
             end
  respond_to do |format|
    format.html { render template, status: status }
    format.text { render text: 'Not found', status: :not_found }
  end
end

There are other options too available to do this like responding with HTML in case of text request but the standard Rails way is like this.

Upvotes: 1

Related Questions