devise 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

Files changed (80) hide show
  1. data/CHANGELOG.rdoc +21 -2
  2. data/README.rdoc +40 -54
  3. data/Rakefile +1 -1
  4. data/TODO +1 -3
  5. data/app/controllers/confirmations_controller.rb +9 -20
  6. data/app/controllers/passwords_controller.rb +9 -20
  7. data/app/controllers/sessions_controller.rb +9 -9
  8. data/app/controllers/unlocks_controller.rb +22 -0
  9. data/app/models/devise_mailer.rb +6 -1
  10. data/app/views/confirmations/new.html.erb +1 -5
  11. data/app/views/devise_mailer/unlock_instructions.html.erb +7 -0
  12. data/app/views/passwords/edit.html.erb +1 -5
  13. data/app/views/passwords/new.html.erb +1 -5
  14. data/app/views/sessions/new.html.erb +1 -7
  15. data/app/views/shared/_devise_links.erb +15 -0
  16. data/app/views/unlocks/new.html.erb +12 -0
  17. data/generators/devise/templates/migration.rb +2 -0
  18. data/generators/devise/templates/model.rb +4 -1
  19. data/generators/devise_install/templates/devise.rb +20 -10
  20. data/lib/devise.rb +62 -18
  21. data/lib/devise/controllers/common.rb +24 -0
  22. data/lib/devise/controllers/helpers.rb +160 -80
  23. data/lib/devise/controllers/internal_helpers.rb +120 -0
  24. data/lib/devise/controllers/url_helpers.rb +2 -10
  25. data/lib/devise/encryptors/bcrypt.rb +2 -2
  26. data/lib/devise/hooks/activatable.rb +1 -4
  27. data/lib/devise/hooks/rememberable.rb +30 -0
  28. data/lib/devise/hooks/timeoutable.rb +4 -2
  29. data/lib/devise/locales/en.yml +9 -2
  30. data/lib/devise/mapping.rb +15 -11
  31. data/lib/devise/models.rb +16 -35
  32. data/lib/devise/models/activatable.rb +1 -1
  33. data/lib/devise/models/authenticatable.rb +1 -9
  34. data/lib/devise/models/confirmable.rb +6 -2
  35. data/lib/devise/models/lockable.rb +142 -0
  36. data/lib/devise/models/rememberable.rb +19 -2
  37. data/lib/devise/models/timeoutable.rb +1 -2
  38. data/lib/devise/orm/active_record.rb +2 -0
  39. data/lib/devise/orm/data_mapper.rb +1 -1
  40. data/lib/devise/orm/mongo_mapper.rb +12 -1
  41. data/lib/devise/rails/routes.rb +5 -1
  42. data/lib/devise/rails/warden_compat.rb +13 -13
  43. data/lib/devise/schema.rb +7 -0
  44. data/lib/devise/strategies/authenticatable.rb +1 -3
  45. data/lib/devise/strategies/base.rb +1 -1
  46. data/lib/devise/strategies/rememberable.rb +37 -0
  47. data/lib/devise/test_helpers.rb +1 -1
  48. data/lib/devise/version.rb +1 -1
  49. data/test/controllers/helpers_test.rb +155 -33
  50. data/test/controllers/internal_helpers_test.rb +55 -0
  51. data/test/devise_test.rb +24 -3
  52. data/test/encryptors_test.rb +3 -1
  53. data/test/integration/lockable_test.rb +83 -0
  54. data/test/integration/rememberable_test.rb +1 -1
  55. data/test/mailers/unlock_instructions_test.rb +62 -0
  56. data/test/models/authenticatable_test.rb +0 -23
  57. data/test/models/lockable_test.rb +202 -0
  58. data/test/models/timeoutable_test.rb +7 -7
  59. data/test/models/validatable_test.rb +2 -2
  60. data/test/models_test.rb +9 -76
  61. data/test/orm/active_record.rb +1 -0
  62. data/test/orm/mongo_mapper.rb +0 -1
  63. data/test/rails_app/app/active_record/admin.rb +1 -1
  64. data/test/rails_app/app/active_record/user.rb +2 -1
  65. data/test/rails_app/app/mongo_mapper/admin.rb +1 -1
  66. data/test/rails_app/app/mongo_mapper/user.rb +2 -1
  67. data/test/rails_app/config/initializers/devise.rb +13 -10
  68. data/test/rails_app/config/routes.rb +5 -3
  69. data/test/routes_test.rb +5 -0
  70. data/test/support/integration_tests_helper.rb +1 -0
  71. metadata +16 -12
  72. data/lib/devise/controllers/filters.rb +0 -186
  73. data/lib/devise/models/cookie_serializer.rb +0 -21
  74. data/lib/devise/models/session_serializer.rb +0 -19
  75. data/lib/devise/serializers/base.rb +0 -23
  76. data/lib/devise/serializers/cookie.rb +0 -43
  77. data/lib/devise/serializers/session.rb +0 -22
  78. data/test/controllers/filters_test.rb +0 -177
  79. data/test/rails_app/app/active_record/account.rb +0 -7
  80. data/test/rails_app/app/mongo_mapper/account.rb +0 -9
@@ -0,0 +1,120 @@
1
+ module Devise
2
+ module Controllers
3
+ # Those helpers are used only inside Devise controllers and should not be
4
+ # included in ApplicationController since they all depend on the url being
5
+ # accessed.
6
+ module InternalHelpers #:nodoc:
7
+
8
+ def self.included(base)
9
+ base.class_eval do
10
+ unloadable
11
+
12
+ helper_method :resource, :scope_name, :resource_name, :resource_class, :devise_mapping, :devise_controller?
13
+ hide_action :resource, :scope_name, :resource_name, :resource_class, :devise_mapping, :devise_controller?
14
+
15
+ skip_before_filter *Devise.mappings.keys.map { |m| :"authenticate_#{m}!" }
16
+ before_filter :is_devise_resource?
17
+ end
18
+ end
19
+
20
+ # Gets the actual resource stored in the instance variable
21
+ def resource
22
+ instance_variable_get(:"@#{resource_name}")
23
+ end
24
+
25
+ # Proxy to devise map name
26
+ def resource_name
27
+ devise_mapping.name
28
+ end
29
+ alias :scope_name :resource_name
30
+
31
+ # Proxy to devise map class
32
+ def resource_class
33
+ devise_mapping.to
34
+ end
35
+
36
+ # Attempt to find the mapped route for devise based on request path
37
+ def devise_mapping
38
+ @devise_mapping ||= begin
39
+ mapping = Devise::Mapping.find_by_path(request.path)
40
+ mapping ||= Devise.mappings[Devise.default_scope] if Devise.use_default_scope
41
+ mapping
42
+ end
43
+ end
44
+
45
+ # Overwrites devise_controller? to return true
46
+ def devise_controller?
47
+ true
48
+ end
49
+
50
+ protected
51
+
52
+ # Checks whether it's a devise mapped resource or not.
53
+ def is_devise_resource? #:nodoc:
54
+ raise ActionController::UnknownAction unless devise_mapping && devise_mapping.allows?(controller_name)
55
+ end
56
+
57
+ # Sets the resource creating an instance variable
58
+ def resource=(new_resource)
59
+ instance_variable_set(:"@#{resource_name}", new_resource)
60
+ end
61
+
62
+ # Build a devise resource without setting password and password confirmation fields.
63
+ def build_resource
64
+ self.resource ||= begin
65
+ attributes = params[resource_name].try(:except, :password, :password_confirmation)
66
+ resource_class.new(attributes || {})
67
+ end
68
+ end
69
+
70
+ # Helper for use in before_filters where no authentication is required.
71
+ #
72
+ # Example:
73
+ # before_filter :require_no_authentication, :only => :new
74
+ def require_no_authentication
75
+ redirect_to after_sign_in_path_for(resource_name) if warden.authenticated?(resource_name)
76
+ end
77
+
78
+ # Sets the flash message with :key, using I18n. By default you are able
79
+ # to setup your messages using specific resource scope, and if no one is
80
+ # found we look to default scope.
81
+ # Example (i18n locale file):
82
+ #
83
+ # en:
84
+ # devise:
85
+ # passwords:
86
+ # #default_scope_messages - only if resource_scope is not found
87
+ # user:
88
+ # #resource_scope_messages
89
+ #
90
+ # Please refer to README or en.yml locale file to check what messages are
91
+ # available.
92
+ def set_flash_message(key, kind, now=false)
93
+ flash_hash = now ? flash.now : flash
94
+ flash_hash[key] = I18n.t(:"#{resource_name}.#{kind}",
95
+ :scope => [:devise, controller_name.to_sym], :default => kind)
96
+ end
97
+
98
+ # Shortcut to set flash.now message. Same rules applied from set_flash_message
99
+ def set_now_flash_message(key, kind)
100
+ set_flash_message(key, kind, true)
101
+ end
102
+
103
+ # Render a view for the specified scope. Turned off by default.
104
+ # Accepts just :controller as option.
105
+ def render_with_scope(action, options={})
106
+ controller_name = options.delete(:controller) || self.controller_name
107
+ if Devise.scoped_views
108
+ begin
109
+ render :template => "#{controller_name}/#{devise_mapping.as}/#{action}"
110
+ rescue ActionView::MissingTemplate
111
+ render action, :controller => controller_name
112
+ end
113
+ else
114
+ render action, :controller => controller_name
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -19,7 +19,7 @@ module Devise
19
19
  # Those helpers are added to your ApplicationController.
20
20
  module UrlHelpers
21
21
 
22
- [:session, :password, :confirmation].each do |module_name|
22
+ [:session, :password, :confirmation, :unlock].each do |module_name|
23
23
  [:path, :url].each do |path_or_url|
24
24
  actions = [ nil, :new_ ]
25
25
  actions << :edit_ if module_name == :password
@@ -28,15 +28,7 @@ module Devise
28
28
  actions.each do |action|
29
29
  class_eval <<-URL_HELPERS
30
30
  def #{action}#{module_name}_#{path_or_url}(resource, *args)
31
- resource = case resource
32
- when Symbol, String
33
- resource
34
- when Class
35
- resource.name.underscore
36
- else
37
- resource.class.name.underscore
38
- end
39
-
31
+ resource = Devise::Mapping.find_scope!(resource)
40
32
  send("#{action}\#{resource}_#{module_name}_#{path_or_url}", *args)
41
33
  end
42
34
  URL_HELPERS
@@ -2,10 +2,10 @@ require "bcrypt"
2
2
 
3
3
  module Devise
4
4
  module Encryptors
5
- # = BCrypt
5
+ # = BCrypt
6
6
  # Uses the BCrypt hash algorithm to encrypt passwords.
7
7
  class Bcrypt < Base
8
-
8
+
9
9
  # Gererates a default password digest based on stretches, salt, pepper and the
10
10
  # incoming password. We don't strech it ourselves since BCrypt does so internally.
11
11
  def self.digest(password, stretches, salt, pepper)
@@ -1,7 +1,4 @@
1
- # Each time the user is set we verify if it is still able to really sign in.
2
- # This is done by checking the time frame the user is able to sign in without
3
- # confirming it's account. If the user has not confirmed it's account during
4
- # this time frame, he/she will not able to sign in anymore.
1
+ # Deny user access whenever his account is not active yet.
5
2
  Warden::Manager.after_set_user do |record, warden, options|
6
3
  if record && record.respond_to?(:active?) && !record.active?
7
4
  scope = options[:scope]
@@ -0,0 +1,30 @@
1
+ # After authenticate hook to verify if the user in the given scope asked to be
2
+ # remembered while he does not sign out. Generates a new remember token for
3
+ # that specific user and adds a cookie with this user info to sign in this user
4
+ # automatically without asking for credentials. Refer to rememberable strategy
5
+ # for more info.
6
+ Warden::Manager.after_authentication do |record, warden, options|
7
+ scope = options[:scope]
8
+ remember_me = warden.params[scope].try(:fetch, :remember_me, nil)
9
+
10
+ if Devise::TRUE_VALUES.include?(remember_me) &&
11
+ warden.authenticated?(scope) && record.respond_to?(:remember_me!)
12
+ record.remember_me!
13
+
14
+ warden.response.set_cookie "remember_#{scope}_token", {
15
+ :value => record.class.serialize_into_cookie(record),
16
+ :expires => record.remember_expires_at,
17
+ :path => "/"
18
+ }
19
+ end
20
+ end
21
+
22
+ # Before logout hook to forget the user in the given scope, only if rememberable
23
+ # is activated for this scope. Also clear remember token to ensure the user
24
+ # won't be remembered again.
25
+ Warden::Manager.before_logout do |record, warden, scope|
26
+ if record.respond_to?(:forget_me!)
27
+ record.forget_me!
28
+ warden.response.delete_cookie "remember_#{scope}_token"
29
+ end
30
+ end
@@ -5,12 +5,14 @@
5
5
  # verify timeout in the following request.
6
6
  Warden::Manager.after_set_user do |record, warden, options|
7
7
  scope = options[:scope]
8
- if record && record.respond_to?(:timeout?) && warden.authenticated?(scope)
8
+ if record && record.respond_to?(:timedout?) && warden.authenticated?(scope)
9
9
  last_request_at = warden.session(scope)['last_request_at']
10
- if record.timeout?(last_request_at)
10
+
11
+ if record.timedout?(last_request_at)
11
12
  warden.logout(scope)
12
13
  throw :warden, :scope => scope, :message => :timeout
13
14
  end
15
+
14
16
  warden.session(scope)['last_request_at'] = Time.now.utc
15
17
  end
16
18
  end
@@ -1,21 +1,28 @@
1
1
  en:
2
2
  devise:
3
3
  sessions:
4
+ link: 'Sign in'
4
5
  signed_in: 'Signed in successfully.'
5
6
  signed_out: 'Signed out successfully.'
6
7
  unauthenticated: 'You need to sign in or sign up before continuing.'
7
8
  unconfirmed: 'You have to confirm your account before continuing.'
9
+ locked: 'Your account is locked.'
8
10
  invalid: 'Invalid email or password.'
9
11
  timeout: 'Your session expired, please sign in again to continue.'
10
12
  inactive: 'Your account was not activated yet.'
11
13
  passwords:
14
+ link: 'Forgot password?'
12
15
  send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
13
16
  updated: 'Your password was changed successfully. You are now signed in.'
14
17
  confirmations:
18
+ link: "Didn't receive confirmation instructions?"
15
19
  send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
16
20
  confirmed: 'Your account was successfully confirmed. You are now signed in.'
21
+ unlocks:
22
+ link: "Didn't receive unlock instructions?"
23
+ send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
24
+ unlocked: 'Your account was successfully unlocked. You are now signed in.'
17
25
  mailer:
18
26
  confirmation_instructions: 'Confirmation instructions'
19
27
  reset_password_instructions: 'Reset password instructions'
20
-
21
-
28
+ unlock_instructions: 'Unlock Instructions'
@@ -42,7 +42,8 @@ module Devise
42
42
  # Receives an object and find a scope for it. If a scope cannot be found,
43
43
  # raises an error. If a symbol is given, it's considered to be the scope.
44
44
  def self.find_scope!(duck)
45
- if duck.is_a?(Symbol)
45
+ case duck
46
+ when String, Symbol
46
47
  duck
47
48
  else
48
49
  klass = duck.is_a?(Class) ? duck : duck.class
@@ -61,9 +62,8 @@ module Devise
61
62
  @as = (options.delete(:as) || name).to_sym
62
63
  @klass = (options.delete(:class_name) || name.to_s.classify).to_s
63
64
  @name = (options.delete(:scope) || name.to_s.singularize).to_sym
64
- @path_names = options.delete(:path_names) || {}
65
- @path_prefix = options.delete(:path_prefix).to_s
66
- @path_prefix << "/" unless @path_prefix[-1] == ?/
65
+ @path_names = options.delete(:path_names) || {}
66
+ @path_prefix = "/#{options.delete(:path_prefix)}/".squeeze("/")
67
67
  @route_options = options || {}
68
68
 
69
69
  setup_path_names
@@ -104,11 +104,12 @@ module Devise
104
104
  def parsed_path
105
105
  returning raw_path do |path|
106
106
  self.class.default_url_options.each do |key, value|
107
- path.gsub!(key.inspect, value.to_s)
107
+ path.gsub!(key.inspect, value.to_param)
108
108
  end
109
109
  end
110
110
  end
111
111
 
112
+
112
113
  # Create magic predicates for verifying what module is activated by this map.
113
114
  # Example:
114
115
  #
@@ -116,13 +117,16 @@ module Devise
116
117
  # self.for.include?(:confirmable)
117
118
  # end
118
119
  #
119
- ALL.each do |m|
120
- class_eval <<-METHOD, __FILE__, __LINE__
121
- def #{m}?
122
- self.for.include?(:#{m})
123
- end
124
- METHOD
120
+ def self.register(*modules)
121
+ modules.each do |m|
122
+ class_eval <<-METHOD, __FILE__, __LINE__
123
+ def #{m}?
124
+ self.for.include?(:#{m})
125
+ end
126
+ METHOD
127
+ end
125
128
  end
129
+ Devise::Mapping.register *ALL
126
130
 
127
131
  private
128
132
 
@@ -1,14 +1,14 @@
1
1
  module Devise
2
2
  module Models
3
3
  autoload :Activatable, 'devise/models/activatable'
4
- autoload :Authenticatable, 'devise/models/authenticatable'
5
- autoload :Confirmable, 'devise/models/confirmable'
6
- autoload :Recoverable, 'devise/models/recoverable'
4
+ autoload :Authenticatable, 'devise/models/authenticatable'
5
+ autoload :Confirmable, 'devise/models/confirmable'
6
+ autoload :Lockable, 'devise/models/lockable'
7
+ autoload :Recoverable, 'devise/models/recoverable'
7
8
  autoload :Rememberable, 'devise/models/rememberable'
8
- autoload :SessionSerializer, 'devise/models/session_serializer'
9
- autoload :Timeoutable, 'devise/models/timeoutable'
10
- autoload :Trackable, 'devise/models/trackable'
11
- autoload :Validatable, 'devise/models/validatable'
9
+ autoload :Timeoutable, 'devise/models/timeoutable'
10
+ autoload :Trackable, 'devise/models/trackable'
11
+ autoload :Validatable, 'devise/models/validatable'
12
12
 
13
13
  # Creates configuration values for Devise and for the given module.
14
14
  #
@@ -46,43 +46,24 @@ module Devise
46
46
  end
47
47
  end
48
48
 
49
- # Shortcut method for including all devise modules inside your model.
50
- # You can give some extra options while declaring devise in your model:
49
+ # Include the chosen devise modules in your model:
51
50
  #
52
- # * except: convenient option that allows you to add all devise modules,
53
- # removing only the modules you setup here:
54
- #
55
- # devise :all, :except => :rememberable
51
+ # devise :authenticatable, :confirmable, :recoverable
56
52
  #
57
53
  # You can also give the following configuration values in a hash: :pepper,
58
54
  # :stretches, :confirm_within and :remember_for. Please check your Devise
59
55
  # initialiazer for a complete description on those values.
60
56
  #
61
- # Examples:
62
- #
63
- # # include only authenticatable module
64
- # devise :authenticatable
65
- #
66
- # # include authenticatable + confirmable modules
67
- # devise :authenticatable, :confirmable
68
- #
69
- # # include authenticatable + recoverable modules
70
- # devise :authenticatable, :recoverable
71
- #
72
- # # include authenticatable + rememberable + validatable modules
73
- # devise :authenticatable, :rememberable, :validatable
74
- #
75
- # # shortcut to include all available modules
76
- # devise :all
77
- #
78
- # # include all except recoverable
79
- # devise :all, :except => :recoverable
80
- #
81
57
  def devise(*modules)
82
58
  raise "You need to give at least one Devise module" if modules.empty?
83
-
84
59
  options = modules.extract_options!
85
- modules += Devise.all if modules.delete(:all)
60
+
61
+ # TODO Remove me
62
+ if modules.delete(:all)
63
+ ActiveSupport::Deprecation.warn "devise :all is deprecated. List your modules instead", caller
64
+ modules += Devise.all
65
+ end
66
+
86
67
  modules -= Array(options.delete(:except))
87
68
  modules = Devise::ALL & modules.uniq
88
69
 
@@ -5,7 +5,7 @@ module Devise
5
5
  # This module implements the default API required in activatable hook.
6
6
  module Activatable
7
7
  def active?
8
- raise NotImplementedError
8
+ true
9
9
  end
10
10
 
11
11
  def inactive_message
@@ -1,5 +1,4 @@
1
1
  require 'devise/strategies/authenticatable'
2
- require 'devise/models/session_serializer'
3
2
 
4
3
  module Devise
5
4
  module Models
@@ -31,7 +30,6 @@ module Devise
31
30
  def self.included(base)
32
31
  base.class_eval do
33
32
  extend ClassMethods
34
- extend SessionSerializer
35
33
 
36
34
  attr_reader :password, :old_password
37
35
  attr_accessor :password_confirmation
@@ -84,13 +82,7 @@ module Devise
84
82
  return unless authentication_keys.all? { |k| attributes[k].present? }
85
83
  conditions = attributes.slice(*authentication_keys)
86
84
  resource = find_for_authentication(conditions)
87
- if respond_to?(:valid_for_authentication)
88
- ActiveSupport::Deprecation.warn "valid_for_authentication class method is deprecated. " <<
89
- "Use valid_for_authentication? in the instance instead."
90
- valid_for_authentication(resource, attributes)
91
- elsif resource.try(:valid_for_authentication?, attributes)
92
- resource
93
- end
85
+ resource if resource.try(:valid_for_authentication?, attributes)
94
86
  end
95
87
 
96
88
  # Returns the class for the configured encryptor.
@@ -76,12 +76,16 @@ module Devise
76
76
  # is already confirmed, it should never be blocked. Otherwise we need to
77
77
  # calculate if the confirm time has not expired for this user.
78
78
  def active?
79
- confirmed? || confirmation_period_valid?
79
+ super && (confirmed? || confirmation_period_valid?)
80
80
  end
81
81
 
82
82
  # The message to be shown if the account is inactive.
83
83
  def inactive_message
84
- :unconfirmed
84
+ if !confirmed?
85
+ :unconfirmed
86
+ else
87
+ super
88
+ end
85
89
  end
86
90
 
87
91
  # If you don't want confirmation to be sent on create, neither a code
@@ -0,0 +1,142 @@
1
+ require 'devise/models/activatable'
2
+
3
+ module Devise
4
+ module Models
5
+
6
+ module Lockable
7
+ include Devise::Models::Activatable
8
+ include Devise::Models::Authenticatable
9
+
10
+ def self.included(base)
11
+ base.class_eval do
12
+ extend ClassMethods
13
+ end
14
+ end
15
+
16
+ # Lock an user setting it's locked_at to actual time.
17
+ def lock
18
+ self.locked_at = Time.now
19
+ if [:both, :email].include?(self.class.unlock_strategy)
20
+ generate_unlock_token
21
+ self.send_unlock_instructions
22
+ end
23
+ end
24
+
25
+ # calls lock and save the model
26
+ def lock!
27
+ self.lock
28
+ save(false)
29
+ end
30
+
31
+ # Unlock an user by cleaning locket_at and failed_attempts
32
+ def unlock!
33
+ if_locked do
34
+ self.locked_at = nil
35
+ self.failed_attempts = 0
36
+ self.unlock_token = nil
37
+ save(false)
38
+ end
39
+ end
40
+
41
+ # Verifies whether a user is locked or not
42
+ def locked?
43
+ self.locked_at && !lock_expired?
44
+ end
45
+
46
+ # Send unlock instructions by email
47
+ def send_unlock_instructions
48
+ ::DeviseMailer.deliver_unlock_instructions(self)
49
+ end
50
+
51
+ # Resend the unlock instructions if the user is locked
52
+ def resend_unlock!
53
+ if_locked do
54
+ generate_unlock_token unless self.unlock_token.present?
55
+ save(false)
56
+ send_unlock_instructions
57
+ end
58
+ end
59
+
60
+ # Overwrites active? from Devise::Models::Activatable for locking purposes
61
+ # by verifying whether an user is active to sign in or not based on locked?
62
+ def active?
63
+ super && !locked?
64
+ end
65
+
66
+ # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
67
+ # for verifying whether an user is allowed to sign in or not. If the user
68
+ # is locked, it should never be allowed.
69
+ def valid_for_authentication?(attributes)
70
+ if result = super
71
+ self.failed_attempts = 0
72
+ else
73
+ self.failed_attempts += 1
74
+ self.lock if self.failed_attempts > self.class.maximum_attempts
75
+ end
76
+ save(false) if changed?
77
+ result
78
+ end
79
+
80
+ # Overwrites invalid_message from Devise::Models::Authenticatable to define
81
+ # the correct reason for blocking the sign in.
82
+ def inactive_message
83
+ if locked?
84
+ :locked
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ protected
91
+
92
+ # Generates unlock token
93
+ def generate_unlock_token
94
+ self.unlock_token = Devise.friendly_token
95
+ end
96
+
97
+ # Tells if the lock is expired if :time unlock strategy is active
98
+ def lock_expired?
99
+ if [:both, :time].include?(self.class.unlock_strategy)
100
+ self.locked_at && self.locked_at < self.class.unlock_in.ago
101
+ else
102
+ false
103
+ end
104
+ end
105
+
106
+ # Checks whether the record is locked or not, yielding to the block
107
+ # if it's locked, otherwise adds an error to email.
108
+ def if_locked
109
+ if locked?
110
+ yield
111
+ else
112
+ self.class.add_error_on(self, :email, :not_locked)
113
+ false
114
+ end
115
+ end
116
+
117
+ module ClassMethods
118
+ # Attempt to find a user by it's email. If a record is found, send new
119
+ # unlock instructions to it. If not user is found, returns a new user
120
+ # with an email not found error.
121
+ # Options must contain the user email
122
+ def send_unlock_instructions(attributes={})
123
+ lockable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
124
+ lockable.resend_unlock! unless lockable.new_record?
125
+ lockable
126
+ end
127
+
128
+ # Find a user by it's unlock token and try to unlock it.
129
+ # If no user is found, returns a new user with an error.
130
+ # If the user is not locked, creates an error for the user
131
+ # Options must have the unlock_token
132
+ def unlock!(attributes={})
133
+ lockable = find_or_initialize_with_error_by(:unlock_token, attributes[:unlock_token])
134
+ lockable.unlock! unless lockable.new_record?
135
+ lockable
136
+ end
137
+
138
+ Devise::Models.config(self, :maximum_attempts, :unlock_strategy, :unlock_in)
139
+ end
140
+ end
141
+ end
142
+ end