Search code examples
ruby-on-railsdeviseruby-on-rails-5http-token-authentication

devise_token_auth & Rails 5 - IndexError: string not matched


I'm trying to sign in an existing user using devise_token_auth version 0.1.38, but I'm hitting an IndexError: string not matched in the library's sessions_controller.

IndexError (string not matched):

devise_token_auth (0.1.38) app/controllers/devise_token_auth/sessions_controller.rb:37:in `[]='
devise_token_auth (0.1.38) app/controllers/devise_token_auth/sessions_controller.rb:37:in `create'
actionpack (5.0.0) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
actionpack (5.0.0) lib/abstract_controller/base.rb:188:in `process_action'
actionpack (5.0.0) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.0.0) lib/abstract_controller/callbacks.rb:20:in `block in process_action'

The relevant code from sessions_controller is:

  if @resource and valid_params?(field, q_value) and @resource.valid_password?(resource_params[:password]) and (!@resource.respond_to?(:active_for_authentication?) or @resource.active_for_authentication?)
    # create client id
    @client_id = SecureRandom.urlsafe_base64(nil, false)
    @token     = SecureRandom.urlsafe_base64(nil, false)

    #  !! Next line is line 37 with the error !!
    @resource.tokens[@client_id] = {  
      token: BCrypt::Password.create(@token),
      expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
    }
    @resource.save

    sign_in(:user, @resource, store: false, bypass: false)

I've added devise_token_auth to an existing project so it's very possible that I've created bad data in the tokens column. I've tried various ways of defaulting token json into my existing users including mimicking the code in the sessions_controller.

add_column :users, :tokens, :json, null: false, default: {}

User.reset_column_information
client_id = SecureRandom.urlsafe_base64(nil, false)
token     = SecureRandom.urlsafe_base64(nil, false)

User.find_each do |user|
  user.uid = user.email
  user.provider = 'email'
  user.tokens[client_id] = {
      token: BCrypt::Password.create(token),
      expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
  }
  user.save!
end

I've seen a mention of this in issue #101, but that didn't have a specific resolution. Any idea where I'm going wrong?


Solution

  • It turns out I needed to set the tokens to nil, and then devise will take care of setting that for me. I found the answer in this devise issue.

    def change
      add_column :users, :provider, :string, null: false, default: "email"
      add_column :users, :uid, :string, null: false, default: ""
      add_column :users, :tokens, :text
    
      reversible do |direction|
        direction.up do
          User.find_each do |user|
            user.uid = user.email
            user.tokens = nil
            user.save!
          end
        end
      end
    
      add_index :users, [:uid, :provider], unique: true
    end