Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User tokens don't properly deserialize #121

Closed
sdornan opened this issue Jan 26, 2015 · 18 comments
Closed

User tokens don't properly deserialize #121

sdornan opened this issue Jan 26, 2015 · 18 comments
Labels

Comments

@sdornan
Copy link

sdornan commented Jan 26, 2015

I am having trouble integrating devise_token_auth v0.1.31.beta9 into an existing Rails 4.2 app that already uses Devise.

I first ran the install. The user model was missing a few columns that devise_token_auth uses, so I edited down the generated migration to:

class DeviseTokenAuthCreateUsers < ActiveRecord::Migration
  def change
    add_column :users, :provider, :string, :null => false, :default => ""
    add_column :users, :uid, :string, :null => false, :default => ""
    add_column :users, :tokens, :text

    User.all.each do |user|
      user.provider = 'email'
      user.uid = user.email
      user.save!
    end

    add_index :users, [:uid, :provider],     :unique => true
  end
end

Attempting to perform the migration results in the following:

== 20150126183919 DeviseTokenAuthCreateUsers: migrating =======================
-- add_column(:users, :provider, :string, {:null=>false, :default=>""})
   -> 0.0218s
-- add_column(:users, :uid, :string, {:null=>false, :default=>""})
   -> 0.0164s
-- add_column(:users, :tokens, :text)
   -> 0.0012s
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

undefined method `delete_if' for "{}":String/usr/local/Cellar/rbenv/0.4.0/versions/2.2.0/lib/ruby/gems/2.2.0/gems/devise_token_auth-0.1.31.beta9/app/models/devise_token_auth/concerns/user.rb:238:in `destroy_expired_tokens'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:11:in `block in change'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:7:in `change'
-e:1:in `<main>'
NoMethodError: undefined method `delete_if' for "{}":String
/usr/local/Cellar/rbenv/0.4.0/versions/2.2.0/lib/ruby/gems/2.2.0/gems/devise_token_auth-0.1.31.beta9/app/models/devise_token_auth/concerns/user.rb:238:in `destroy_expired_tokens'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:11:in `block in change'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:7:in `change'
-e:1:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

It seems that for some reason Rails isn't deserializing the tokens JSON column into a Ruby hash. DeviseTokenAuth::Concerns::User is included in my user modal as it should be. It if makes a difference, I am running Postgres 9.4 as my database.

Any idea what could be causing this?

@sdornan
Copy link
Author

sdornan commented Jan 28, 2015

I ended up overriding destroy_expired_tokens in my user model:

  def destroy_expired_tokens
    return if self.tokens == '{}'
    super
  end

@lynndylanhurley
Copy link
Owner

@sdornan - thanks for the detailed error report. I'll look into this ASAP

@jasonswett
Copy link
Contributor

+1

@shicholas
Copy link

👍, why not make the tokens attribute a json column? (I'm sure there's some terrible security concern I am not aware of).

@booleanbetrayal
Copy link
Collaborator

@shicholas - there's no guarantee that json datatypes are available in all target DB platforms, but i think it may warrant an auto-detect mechanism or configurable switch to specify a desired column type during setup. Would you like to submit a PR to that effect?

@shicholas
Copy link

Sure sounds good. Please give me a few days.

@booleanbetrayal
Copy link
Collaborator

Sounds good. Thanks!

@shicholas
Copy link

#276 I have some questions about testing I'll pose there.

@booleanbetrayal
Copy link
Collaborator

Closing as we now support json datatypes via #276, etc. Can re-open as needed.

@jivdhaliwal
Copy link

Just in case anyone else stumbles upon this issue, instead of overriding destroy_expired_tokens you can simply set user tokens to nil during the initial migration. The User model will then call the set_empty_token_hash callback defined in the provided model concern.

This was run on rails 4.2.4, devise_token_auth 0.1.34, mysql server 5.5.40. Here's my modified migration:

class AddDeviseTokenAuthFieldsToUsers < ActiveRecord::Migration
  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
end

@superrandres
Copy link

help to me! I'm use Postgresql and I have go to my row on db for setting to nil, e.j.

2.3.0 :002 > user.tokens
=> "{}"
2.3.0 :003 > user.tokens = nil
=> nil
2.3.0 :004 > user.save
(0.1ms) BEGIN
SQL (0.3ms) UPDATE "users" SET "tokens" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["tokens", nil], ["updated_at", 2017-03-22 17:31:23 UTC], ["id", 1]]
(23.9ms) COMMIT
=> true
2.3.0 :005 > user.tokens
=> {}

Also I've changed my seed file, here it is.
User.create(email: '[email protected]', nickname: 'ronald', name: 'Ronald Espinoza', password: "austro1992", tokens: nil)

I've attempt put your solution on my migration file but not working for me. Here my file "db/migrate/20170321204000_devise_token_auth_create_users.rb".

`class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.0]
def change
create_table(:users) do |t|
## Required
t.string :provider, :null => false, :default => "email"
t.string :uid, :null => false, :default => ""

  ## Database authenticatable
  t.string :encrypted_password, :null => false, :default => ""

  ## Recoverable
  t.string   :reset_password_token
  t.datetime :reset_password_sent_at

  ## Rememberable
  t.datetime :remember_created_at

  ## Trackable
  t.integer  :sign_in_count, :default => 0, :null => false
  t.datetime :current_sign_in_at
  t.datetime :last_sign_in_at
  t.string   :current_sign_in_ip
  t.string   :last_sign_in_ip

  ## Confirmable
  t.string   :confirmation_token
  t.datetime :confirmed_at
  t.datetime :confirmation_sent_at
  t.string   :unconfirmed_email # Only if using reconfirmable

  ## Lockable
  # t.integer  :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
  # t.string   :unlock_token # Only if unlock strategy is :email or :both
  # t.datetime :locked_at

  ## User Info
  t.string :name
  t.string :nickname
  t.string :image
  t.string :email

  ## Tokens
  t.json :tokens

  t.timestamps
end

add_index :users, :email,                unique: true
add_index :users, [:uid, :provider],     unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token,   unique: true
# add_index :users, :unlock_token,       unique: true

reversible do |direction|
    direction.up do
	    User.find_each do |user|
		    user.uid = user.email
		    user.tokens = nil
		    user.save!
	    end
    end
end

end
end`

@stratigos
Copy link
Contributor

stratigos commented Sep 2, 2017

I dont believe that simply having a json column fixes this issue entirely. I had a tough time this afternoon trying to figure out how to get a seeded User to work with this gem. CLI generated User, and one created through the application (POST registrations...) work fine.

User.where(email: '[email protected]').first_or_create do |user|
  some_attr = some_val
  tokens = nil # or an actual tokens val, any JSON object, any Hash, or any JSON-like String
end

The above will always result in some kind of String value that throws the error in the OP on login. I also tried overriding #destroy_expired_tokens, but that led to another error due to expectation of a JSON/Hash object. I found a way around that, but was not comfortable with 2 hacks (overrides...) to make this work.

Any form of one-line or block-based #create on my User, when run through ./bin/rails db:seed, will always result in some invalid value that throws an error on login (usually, the value is '{}', where it should be {}).

What does work for me, is to first create the User record, then reload it, set #tokens to nil, and then save again:

seeded_user = User.create(email: ...)

seeded_user.reload
seeded_user.tokens = nil
seeded_user.save

This actually sets #tokens to nil, which in turn allows the devise_token_auth internals to set it to {} correctly.

atd added a commit to singularities/circular-work that referenced this issue Oct 5, 2017
atd added a commit to singularities/circular-work that referenced this issue Oct 5, 2017
@vanessasoutoc
Copy link

Great. It's work for me!!! Thanks

@MaicolBen
Copy link
Collaborator

Still happening as some folks and I see it, I'll make a PR with the fix suggested here.

@tomascharad
Copy link

+1

@dks17
Copy link
Contributor

dks17 commented Jan 22, 2019

Hello. There is #1250 PR that should fix this issue. Would you help testing it on your projects?

@MaicolBen
Copy link
Collaborator

This was fixed in #1250, post here if it's still happening when you have this gem from the master branch

@colmben
Copy link
Contributor

colmben commented Jan 22, 2020

Note that I saw this this evening on a Heroku "pipelines:promotion" where a stage "slug" is moved to prod, after introducing device token auth on an admin user type. Eventually a "restart all dynos" in Heroku solved the problem. Looks like the db:migrate to add the tokens column etc needed a restart to fully bed in. Just an FYI, might save someone a few hours of digging around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests