Search code examples
ruby-on-railsrubydevisehttp-headersapi-design

How to conciliate Devise reset-password route with ApiConstraints?


I'm using devise_token_auth and everything works well until I add API constraints to the routes: constraints: ApiConstraint.new(version: 4, default: true)

Rails.application.routes.draw do
  namespace :api do
    scope module: :v4, constraints: ApiConstraint.new(version: 4, default: true) do
      # Token auth routes available at /api/auth
      mount_devise_token_auth_for "User", at: "auth"
    end
  end
end

Adding these constraints restrain access to the routes unless the request contains the correct headers:

class ApiConstraint
  attr_reader :version

  VENDOR_MIME = "application/vnd.myproject-v%d"

  def initialize(version: "4", default: false)
    @version, @default = version, default
  end

  def matches?(request)
    byebug # <-- I wish there was something like request.current_path available
    return @default unless request.headers.key?("accept")
    request
      .headers
      .fetch("accept")
      .include?(VENDOR_MIME % version)
  end
end

I realised this is problematic for the reset password link which we get by email:

https://myproject.herokuapp.com/api/auth/password/edit?reset_password_token=...

... as it can't carry any headers!

I could redirect the user to a webpage which then does an AJAX call with the appropriate headers, but I was wondering if there was a way to enhance my routes.rb or ApiConstraint class to make an exception on the /auth/password/edit route?


Solution

  • You can use request.url, request.fullpath, request.params to access the data you need.

    More importantly, in order to figure out what methods are available to you at runtime, once the REPL opens up on the line where you put your byebug debugger entry, you can use these following methods (assuming your using pry):

    ls # display everything in the current scope
    cd request # or any other variable or instance variable.
    cd .. # you can navigate your code as if it was a filesystem
    
    request.methods.grep /path/
    request.methods.grep /url/
    request.methods.grep /param/
    
    # or
    
    cd request
    methods.grep /query/
    

    OP update

    # api_constraint.rb
    def initialize(version: "4", default: false, exceptions: [])
      @version, @default, @exceptions = version, default, exceptions
    end
    
    def matches?(request)
      return true if @exceptions.include? request.path
      ...
    end
    
    #routes.rb
    scope module: :v4, constraints: ApiConstraint.new(version: 4, default: true, exceptions: [
      "/api/auth/password/edit"
    ]) do
      ...
    end