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.
- data/CHANGELOG.rdoc +21 -2
- data/README.rdoc +40 -54
- data/Rakefile +1 -1
- data/TODO +1 -3
- data/app/controllers/confirmations_controller.rb +9 -20
- data/app/controllers/passwords_controller.rb +9 -20
- data/app/controllers/sessions_controller.rb +9 -9
- data/app/controllers/unlocks_controller.rb +22 -0
- data/app/models/devise_mailer.rb +6 -1
- data/app/views/confirmations/new.html.erb +1 -5
- data/app/views/devise_mailer/unlock_instructions.html.erb +7 -0
- data/app/views/passwords/edit.html.erb +1 -5
- data/app/views/passwords/new.html.erb +1 -5
- data/app/views/sessions/new.html.erb +1 -7
- data/app/views/shared/_devise_links.erb +15 -0
- data/app/views/unlocks/new.html.erb +12 -0
- data/generators/devise/templates/migration.rb +2 -0
- data/generators/devise/templates/model.rb +4 -1
- data/generators/devise_install/templates/devise.rb +20 -10
- data/lib/devise.rb +62 -18
- data/lib/devise/controllers/common.rb +24 -0
- data/lib/devise/controllers/helpers.rb +160 -80
- data/lib/devise/controllers/internal_helpers.rb +120 -0
- data/lib/devise/controllers/url_helpers.rb +2 -10
- data/lib/devise/encryptors/bcrypt.rb +2 -2
- data/lib/devise/hooks/activatable.rb +1 -4
- data/lib/devise/hooks/rememberable.rb +30 -0
- data/lib/devise/hooks/timeoutable.rb +4 -2
- data/lib/devise/locales/en.yml +9 -2
- data/lib/devise/mapping.rb +15 -11
- data/lib/devise/models.rb +16 -35
- data/lib/devise/models/activatable.rb +1 -1
- data/lib/devise/models/authenticatable.rb +1 -9
- data/lib/devise/models/confirmable.rb +6 -2
- data/lib/devise/models/lockable.rb +142 -0
- data/lib/devise/models/rememberable.rb +19 -2
- data/lib/devise/models/timeoutable.rb +1 -2
- data/lib/devise/orm/active_record.rb +2 -0
- data/lib/devise/orm/data_mapper.rb +1 -1
- data/lib/devise/orm/mongo_mapper.rb +12 -1
- data/lib/devise/rails/routes.rb +5 -1
- data/lib/devise/rails/warden_compat.rb +13 -13
- data/lib/devise/schema.rb +7 -0
- data/lib/devise/strategies/authenticatable.rb +1 -3
- data/lib/devise/strategies/base.rb +1 -1
- data/lib/devise/strategies/rememberable.rb +37 -0
- data/lib/devise/test_helpers.rb +1 -1
- data/lib/devise/version.rb +1 -1
- data/test/controllers/helpers_test.rb +155 -33
- data/test/controllers/internal_helpers_test.rb +55 -0
- data/test/devise_test.rb +24 -3
- data/test/encryptors_test.rb +3 -1
- data/test/integration/lockable_test.rb +83 -0
- data/test/integration/rememberable_test.rb +1 -1
- data/test/mailers/unlock_instructions_test.rb +62 -0
- data/test/models/authenticatable_test.rb +0 -23
- data/test/models/lockable_test.rb +202 -0
- data/test/models/timeoutable_test.rb +7 -7
- data/test/models/validatable_test.rb +2 -2
- data/test/models_test.rb +9 -76
- data/test/orm/active_record.rb +1 -0
- data/test/orm/mongo_mapper.rb +0 -1
- data/test/rails_app/app/active_record/admin.rb +1 -1
- data/test/rails_app/app/active_record/user.rb +2 -1
- data/test/rails_app/app/mongo_mapper/admin.rb +1 -1
- data/test/rails_app/app/mongo_mapper/user.rb +2 -1
- data/test/rails_app/config/initializers/devise.rb +13 -10
- data/test/rails_app/config/routes.rb +5 -3
- data/test/routes_test.rb +5 -0
- data/test/support/integration_tests_helper.rb +1 -0
- metadata +16 -12
- data/lib/devise/controllers/filters.rb +0 -186
- data/lib/devise/models/cookie_serializer.rb +0 -21
- data/lib/devise/models/session_serializer.rb +0 -19
- data/lib/devise/serializers/base.rb +0 -23
- data/lib/devise/serializers/cookie.rb +0 -43
- data/lib/devise/serializers/session.rb +0 -22
- data/test/controllers/filters_test.rb +0 -177
- data/test/rails_app/app/active_record/account.rb +0 -7
- 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 =
|
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
|
-
#
|
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?(:
|
8
|
+
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope)
|
9
9
|
last_request_at = warden.session(scope)['last_request_at']
|
10
|
-
|
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
|
data/lib/devise/locales/en.yml
CHANGED
@@ -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'
|
data/lib/devise/mapping.rb
CHANGED
@@ -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
|
-
|
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
|
65
|
-
@path_prefix = options.delete(:path_prefix).
|
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.
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
|
data/lib/devise/models.rb
CHANGED
@@ -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 :
|
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 :
|
9
|
-
autoload :
|
10
|
-
autoload :
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
|
@@ -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
|
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
|
-
|
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
|