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(klass) click to toggle source

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

access_locked?() click to toggle source

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
active_for_auth?() click to toggle source

Override active_for_auth? for locking purposes

Calls superclass method
# File lib/negroni/models/lockable.rb, line 90
def active_for_auth?
  super && !access_locked?
end
inactive_message() click to toggle source

Override for locking purposes

Calls superclass method
# File lib/negroni/models/lockable.rb, line 95
def inactive_message
  access_locked? ? :locked : super
end
lock_access!(opts = {}) click to toggle source

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_unlock_instructions() click to toggle source

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_unlock_instructions() click to toggle source

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
unauthenticated_message() click to toggle source

Override for locking

Calls superclass method
# 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_access!() click to toggle source

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
valid_for_auth?() click to toggle source

Override for locking purposes

Calls superclass method
# 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

attempts_exceeded?() click to toggle source
# File lib/negroni/models/lockable.rb, line 139
def attempts_exceeded?
  failed_attempts >= self.class.maximum_attempts
end
if_access_locked() { || ... } click to toggle source
# 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
last_attempt?() click to toggle source
# File lib/negroni/models/lockable.rb, line 143
def last_attempt?
  failed_attempts == self.class.maximum_attempts - 1
end
lock_expired?() click to toggle source
# 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