module Negroni::Models::Lockable
Handles blocking a user access after a certain number of attempts. Lockable
accepts two different strategies to unlock a user after it's blocked: email and time. The former will send an email to the user when the lock happens, containing a link to unlock its account. The second will unlock the user automatically after some configured time (ie 2.hours). It's also possible to set up lockable to use both email and time strategies.
## Options
Lockable
adds the following options to `negroni`:
* `maximum_attempts`: how many attempts should be accepted before blocking the user. * `lock_strategy`: lock the user account by :failed_attempts or :none. * `unlock_strategy`: unlock the user account by :time, :email, :both or :none. * `unlock_in`: the time you want to lock the user after to lock happens. Only available when unlock_strategy is :time or :both. * `unlock_keys`: the keys you want to use when locking and unlocking.
Public Class Methods
Required fields
# File lib/negroni/models/lockable.rb, line 36 def self.required_fields(klass) att = [] att << :failed_attempts if klass.lock_strategy_enabled? :failed_attempts att << :locked_at if klass.unlock_strategy_enabled? :time att << :unlock_token if klass.unlock_strategy_enabled? :email att end
Public Instance Methods
Returns true if the user has access locked @return [Boolean]
# File lib/negroni/models/lockable.rb, line 70 def access_locked? locked_at && !lock_expired? end
Override active_for_auth? for locking purposes
# File lib/negroni/models/lockable.rb, line 90 def active_for_auth? super && !access_locked? end
Override for locking purposes
# File lib/negroni/models/lockable.rb, line 95 def inactive_message access_locked? ? :locked : super end
Lock a user setting its locked_at to actual time.
@param opts [Hash] hash of options @option opts [Boolean] :send_instructions pass `false` to not send the
email
# File lib/negroni/models/lockable.rb, line 49 def lock_access!(opts = {}) self.locked_at = Time.now.utc if unlock_strategy_enabled?(:email) && opts.fetch(:send_instructions, true) send_unlock_instructions else save(validate: false) end end
Resend instructions if the user is locked
# File lib/negroni/models/lockable.rb, line 85 def resend_unlock_instructions if_access_locked { send_unlock_instructions } end
send instructions to unlock
# File lib/negroni/models/lockable.rb, line 75 def send_unlock_instructions raw, enc = Negroni.token_generator.generate(self.class, :unlock_token) self.unlock_token = enc save(validate: false) # rubocop:disable Rails/SaveBang send_auth_notification(:unlock_instructions, raw, {}) raw end
Override for locking
# File lib/negroni/models/lockable.rb, line 123 def unauthenticated_message if Negroni.paranoid super elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?) :locked elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning :last_attempt else super end end
Unlock a user, clearing `locked_at` and `failed_attempts`.
# File lib/negroni/models/lockable.rb, line 61 def unlock_access! self.locked_at = nil self.failed_attempts = 0 if respond_to?(:failed_attempts=) self.unlock_token = nil if respond_to?(:unlock_token=) save(validate: false) end
Override for locking purposes
# File lib/negroni/models/lockable.rb, line 102 def valid_for_auth? return super unless persisted? && lock_strategy_enabled?(:failed_attempts) unlock_access! if lock_expired? return true if super && !access_locked? self.failed_attempts ||= 0 self.failed_attempts += 1 if attempts_exceeded? lock_access! unless access_locked? else save(validate: false) end false end
Protected Instance Methods
# File lib/negroni/models/lockable.rb, line 139 def attempts_exceeded? failed_attempts >= self.class.maximum_attempts end
# File lib/negroni/models/lockable.rb, line 152 def if_access_locked if access_locked? yield else errors.add(Negroni.unlock_keys.first, :not_locked) false end end
# File lib/negroni/models/lockable.rb, line 143 def last_attempt? failed_attempts == self.class.maximum_attempts - 1 end
# File lib/negroni/models/lockable.rb, line 147 def lock_expired? return false unless unlock_strategy_enabled?(:time) locked_at && locked_at < self.class.unlock_in.ago end