Reputation: 2616
I have implemented Devise on two user accounts Admin and Customer. Register sign_in functions are working fine. I'm trying to implement lockable on an admin account. I'm using Devise 3.2.4.
After entering wrong credentials for specific time the account is still active and it doesn't record failed_attempts.
I have followed this guide HERE.
My devise.rb:
Devise.setup do |config|
config.secret_key = 'XXXXX_the_secret_key_XXXXXXX'
config.mailer_sender = '[email protected]'
require 'devise/orm/active_record'
# config.authentication_keys = [ :email ]
# config.request_keys = []
config.case_insensitive_keys = [ :email ]
config.strip_whitespace_keys = [ :email ]
# config.params_authenticatable = true
# config.http_authenticatable = false
# config.http_authenticatable_on_xhr = true
# config.http_authentication_realm = 'Application'
# config.paranoid = true
# passing :skip => :sessions to `devise_for` in your config/routes.rb
config.skip_session_storage = [:http_auth]
# config.clean_up_csrf_token_on_authentication = true
config.stretches = Rails.env.test? ? 1 : 10
# config.pepper = '38635688e9d775b28e8da07b695dfced7b3bd4899c0a9a2a0f9b5ed5a8113e79864f76039166f827ef0134452fc0080f279adc4d1724362e079d0af3361edaf5'
# config.allow_unconfirmed_access_for = 2.days
# config.confirm_within = 3.days
config.reconfirmable = true
# config.confirmation_keys = [ :email ]
# config.remember_for = 2.weeks
# config.extend_remember_period = false
# config.rememberable_options = {}
# Range for password length.
config.password_length = 8..128
# config.email_regexp = /\A[^@]+@[^@]+\z/
# config.timeout_in = 30.minutes
# config.expire_auth_token_on_timeout = false
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
config.unlock_keys = [ :email ]
# config.unlock_keys = [ :time ]
config.unlock_strategy = :both
# config.unlock_strategy = :time
config.maximum_attempts = 3
config.unlock_in = 2.hour
# config.last_attempt_warning = false
config.reset_password_within = 24.hours
# config.encryptor = :sha512
config.sign_out_via = :delete
end
My Admin model:
class Admin < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :lockable
end
My migration to add lockable on admin:
class AddLockableToAdmin < ActiveRecord::Migration
def change
add_column :admins, :failed_attempts, :integer, default: 0
add_column :admins, :unlock_token, :string
add_column :admins, :locked_at, :datetime
end
end
My routes.rb:
devise_for :admins
Upvotes: 3
Views: 6588
Reputation: 421
I faced same problem. But I solved by these step
run rails g migration add_lockable_to_devise
add following code in db/migrate/***********_add_lockable_to_devise
def up
add_column :users, :failed_attempts, :integer
add_column :users, :unlock_token, :string
add_column :users, :locked_at, :datetime
add_index :users, :unlock_token, unique: true
execute("UPDATE users SET confirmed_at = NOW()")
end
def down
remove_columns :users, :failed_attempts, :unlock_token, :locked_at
end
3.After that run rake db:migrate
Don't forget step 3
Upvotes: 2
Reputation: 181
After following the advice of Benj, I failed to get it to work. When debugging lib/devise/models/lockable.rb the code entered the if super && !access_locked?
condition.
It turns out my issue was in the sign-in procedure, where I used the valid_for_authentication?
function incorrectly. I.e. I did a check similar to this:
if user.valid_for_authentication? && user.valid_password?(params[:password])
instead of supplying password verification in a block:
if user.valid_for_authentication? { user.valid_password?(params[:password]) }
Upvotes: 4
Reputation: 13181
STEP 1: verify that devise is correctly installed
1- You are missing null: false
in the failed_attempts
field of the migration.
add_column :admins, :failed_attempts, :integer, default: 0, null: false
Fix it and rerun your migration
2- Update all existing records in your console:
Admin.update_all failed_attempts: 0
3- Shutdown your server, console and anything else that uses or preload your application (spring, zeus etc...)
4- in your rails console, verify that devise is correctly installed
Admin.new.respond_to? :failed_attempts
should return true
5- Still in your console, verify that Admin
can be locked manually:
Admin.first.lock_access!
You should see the SQL updating locked_at
and unlock_token
fields of your records
6- Start your server and try again entering a wrong password (using another user for which you locked manually off course), see if the value of failed_attempts
changes
=> result: All work, but logging-in with wrong credential does not increment failed_attempts
STEP2: Verify where devise fails
Brute-force debugging
I don't know if you have a debugger, so we are going to edit temporarily the method responsible of incrementing failed_attempts
, and see where it stops. Open in devise gem the file "lib/devise/models/lockable.rb" and edit it like this:
def valid_for_authentication?
puts 'mark 1'
return super unless persisted? && lock_strategy_enabled?(:failed_attempts)
puts 'mark 2'
# Unlock the user if the lock is expired, no matter
# if the user can login or not (wrong password, etc)
unlock_access! if lock_expired?
if super && !access_locked?
puts 'mark 3 (you should not see me)'
true
else
puts 'mark 4 (you are supposed to see me)'
self.failed_attempts ||= 0
self.failed_attempts += 1
if attempts_exceeded?
puts 'mark 5 (you should not see me)'
lock_access! unless access_locked?
else
puts 'mark 6 (you are supposed to see me)'
save(validate: false)
end
false
end
end
As you can see I added "marks" to see where the execution pass. Note that depending on your version of devise, the content of the method may be slightly different, you just need to add the "marks".
Restart your server, try one log-in with incorrect credential, and look at your console to see which marks are displayed.
After our test you can revert this file to remove the marks
=> Result: None of the mark is displayed in console during a log-in with wrong credential
Execute in console Admin.first.valid_for_authentication?
=> Result: The marks 1, 2, 4, 6 are displayed and the failed_attempts
is incremented in database
SOLUTION (Still to be confirmed)
The form used for authentication have an action
value which is not redirecting to the devise controller. It seems that you are using api_console
that is generating the form for authentication.
Upvotes: 5
Reputation: 4436
I had similar problem and I recreated table with this migration.
Try this:
change_table(:admins) do |t|
t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
end
Upvotes: 1
Reputation: 988
This is a tricky case, and I can't help you much but can guide you to help yourself.
Devise updates the failed_attempts count at https://github.com/plataformatec/devise/blob/master/lib/devise/models/lockable.rb line number 92.
def valid_for_authentication?
return super unless persisted? && lock_strategy_enabled?(:failed_attempts)
# Unlock the user if the lock is expired, no matter
# if the user can login or not (wrong password, etc)
unlock_access! if lock_expired?
if super && !access_locked?
true
else
self.failed_attempts ||= 0
self.failed_attempts += 1
if attempts_exceeded?
lock_access! unless access_locked?
else
save(validate: false)
end
false
end
end
You have to edit the installed gem's corresponding file and add a logger or do puts to stdout to see if the else loop gets executed. The only possible reason why it won't be is when the first line return gets called.
I dont think you can solve this without digging deep into devise code since your configurations are correct.
Upvotes: -1