sparkly-auth 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/app/controllers/sparkly_accounts_controller.rb +59 -0
- data/app/controllers/sparkly_controller.rb +47 -0
- data/app/controllers/sparkly_sessions_controller.rb +52 -0
- data/app/models/password.rb +3 -0
- data/app/models/remembrance_token.rb +50 -0
- data/app/views/sparkly_accounts/edit.html.erb +24 -0
- data/app/views/sparkly_accounts/new.html.erb +24 -0
- data/app/views/sparkly_accounts/show.html.erb +0 -0
- data/app/views/sparkly_sessions/new.html.erb +22 -0
- data/dependencies.rb +1 -0
- data/generators/sparkly/USAGE +27 -0
- data/generators/sparkly/sparkly_generator.rb +76 -0
- data/generators/sparkly/templates/accounts_controller.rb +65 -0
- data/generators/sparkly/templates/accounts_helper.rb +2 -0
- data/generators/sparkly/templates/help_file.txt +56 -0
- data/generators/sparkly/templates/initializer.rb +30 -0
- data/generators/sparkly/templates/migrations/add_confirmed_to_sparkly_passwords.rb +9 -0
- data/generators/sparkly/templates/migrations/create_sparkly_passwords.rb +19 -0
- data/generators/sparkly/templates/migrations/create_sparkly_remembered_tokens.rb +15 -0
- data/generators/sparkly/templates/sessions_controller.rb +45 -0
- data/generators/sparkly/templates/sessions_helper.rb +2 -0
- data/generators/sparkly/templates/tasks/migrations.rb +1 -0
- data/generators/sparkly/templates/views/sparkly_accounts/edit.html.erb +24 -0
- data/generators/sparkly/templates/views/sparkly_accounts/new.html.erb +24 -0
- data/generators/sparkly/templates/views/sparkly_accounts/show.html.erb +0 -0
- data/generators/sparkly/templates/views/sparkly_sessions/new.html.erb +22 -0
- data/init.rb +44 -0
- data/lib/auth.rb +52 -0
- data/lib/auth/behavior/base.rb +64 -0
- data/lib/auth/behavior/core.rb +87 -0
- data/lib/auth/behavior/core/authenticated_model_methods.rb +52 -0
- data/lib/auth/behavior/core/controller_extensions.rb +52 -0
- data/lib/auth/behavior/core/controller_extensions/class_methods.rb +24 -0
- data/lib/auth/behavior/core/controller_extensions/current_user.rb +54 -0
- data/lib/auth/behavior/core/password_methods.rb +65 -0
- data/lib/auth/behavior/remember_me.rb +17 -0
- data/lib/auth/behavior/remember_me/configuration.rb +21 -0
- data/lib/auth/behavior/remember_me/controller_extensions.rb +66 -0
- data/lib/auth/behavior_lookup.rb +10 -0
- data/lib/auth/configuration.rb +328 -0
- data/lib/auth/encryptors/sha512.rb +20 -0
- data/lib/auth/generators/configuration_generator.rb +20 -0
- data/lib/auth/generators/controllers_generator.rb +34 -0
- data/lib/auth/generators/migration_generator.rb +32 -0
- data/lib/auth/generators/route_generator.rb +19 -0
- data/lib/auth/generators/views_generator.rb +26 -0
- data/lib/auth/model.rb +94 -0
- data/lib/auth/observer.rb +21 -0
- data/lib/auth/target_list.rb +5 -0
- data/lib/auth/tasks/migrations.rb +71 -0
- data/lib/auth/token.rb +10 -0
- data/lib/sparkly-auth.rb +1 -0
- data/rails/init.rb +17 -0
- data/rails/routes.rb +19 -0
- data/sparkly-auth.gemspec +143 -0
- data/spec/controllers/application_controller_spec.rb +13 -0
- data/spec/generators/sparkly_spec.rb +64 -0
- data/spec/lib/auth/behavior/core_spec.rb +184 -0
- data/spec/lib/auth/behavior/remember_me_spec.rb +127 -0
- data/spec/lib/auth/extensions/controller_spec.rb +32 -0
- data/spec/lib/auth/model_spec.rb +57 -0
- data/spec/lib/auth_spec.rb +32 -0
- data/spec/mocks/models/user.rb +3 -0
- data/spec/routes_spec.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/views_spec.rb +18 -0
- metadata +210 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module Auth
|
2
|
+
module Behavior
|
3
|
+
# Adds the most basic authentication behavior to registered models. Passwords have the following
|
4
|
+
# validations added:
|
5
|
+
# - uniqueness of :secret, :scope => [ :authenticatable_type, :authenticatable_id ]
|
6
|
+
# - presence of :secret
|
7
|
+
# - format of :secret ("must be at least 7 characters with at least 1 uppercase, 1 lowercase and 1 number")
|
8
|
+
# - confirmation of :secret, if secret has changed
|
9
|
+
# - presence of :secret_confirmation, if secret has changed
|
10
|
+
#
|
11
|
+
# Additionally, the following methods are added:
|
12
|
+
# #expired?
|
13
|
+
#
|
14
|
+
# The authenticated model(s) will have the following methods added to them:
|
15
|
+
# #password_expired?
|
16
|
+
#
|
17
|
+
class Core < Auth::Behavior::Base
|
18
|
+
migration "create_sparkly_passwords"
|
19
|
+
|
20
|
+
def apply_to_passwords(password_model)
|
21
|
+
password_model.instance_eval do
|
22
|
+
belongs_to :authenticatable, :polymorphic => true
|
23
|
+
|
24
|
+
validates_length_of :unencrypted_secret, :minimum => Auth.minimum_password_length,
|
25
|
+
:message => "must be at least #{Auth.minimum_password_length} characters",
|
26
|
+
:if => :secret_changed?
|
27
|
+
validates_format_of :unencrypted_secret, :with => Auth.password_format, :allow_blank => true,
|
28
|
+
:message => Auth.password_format_message,
|
29
|
+
:if => :secret_changed?
|
30
|
+
|
31
|
+
validates_presence_of :secret
|
32
|
+
validates_confirmation_of :secret, :if => :secret_changed?
|
33
|
+
validates_presence_of :secret_confirmation, :if => :secret_changed?
|
34
|
+
validates_presence_of :persistence_token
|
35
|
+
validates_uniqueness_of :persistence_token, :if => :persistence_token_changed?
|
36
|
+
attr_protected :secret, :secret_confirmation
|
37
|
+
|
38
|
+
include Auth::Behavior::Core::PasswordMethods
|
39
|
+
|
40
|
+
validate do |password|
|
41
|
+
password.errors.rename_attribute("unencrypted_secret", "secret")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply_to_accounts(model_config)
|
47
|
+
model_config.target.instance_eval do
|
48
|
+
has_many :passwords, :dependent => :destroy, :as => :authenticatable, :autosave => true
|
49
|
+
|
50
|
+
attr_protected :password, :password_confirmation
|
51
|
+
validates_presence_of sparkly_config.key
|
52
|
+
validates_uniqueness_of sparkly_config.key
|
53
|
+
validates_presence_of :password
|
54
|
+
|
55
|
+
include Auth::Behavior::Core::AuthenticatedModelMethods
|
56
|
+
|
57
|
+
after_save do |record|
|
58
|
+
# clear out old passwords so we're conforming to Auth.password_history_length
|
59
|
+
while record.passwords.length > Auth.password_history_length
|
60
|
+
record.passwords.shift.destroy
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
validate do |account|
|
65
|
+
account.errors.rename_attribute("passwords.secret", "password")
|
66
|
+
account.errors.rename_attribute("passwords.secret_confirmation", "password_confirmation")
|
67
|
+
|
68
|
+
# the various salts make it impossible to do this:
|
69
|
+
# validates_uniqueness_of :secret, :scope => [ :authenticatable_type, :authenticatable_id ],
|
70
|
+
# :message => Auth.password_uniqueness_message
|
71
|
+
# so we have to do it programmatically.
|
72
|
+
if account.password_changed?
|
73
|
+
secret = account.password_model.unencrypted_secret
|
74
|
+
account.passwords.each do |password|
|
75
|
+
unless password.new_record? # unless it's the one we're creating
|
76
|
+
if password.matches?(secret)
|
77
|
+
account.errors.add(:password, Auth.password_uniqueness_message)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Auth::Behavior::Core::AuthenticatedModelMethods
|
2
|
+
def self.included(base)
|
3
|
+
base.instance_eval do
|
4
|
+
delegate :persistence_token, :single_access_token, :perishable_token, :to => :password_model
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def password_expired?
|
9
|
+
passwords.empty? || passwords.last.created_at < sparkly_config.password_update_frequency.ago
|
10
|
+
end
|
11
|
+
|
12
|
+
def password_model
|
13
|
+
passwords.empty? || @new_password ? new_password : passwords.last
|
14
|
+
end
|
15
|
+
|
16
|
+
def password_required?
|
17
|
+
new_record? || passwords.empty? || passwords.last.secret.blank?
|
18
|
+
end
|
19
|
+
|
20
|
+
def password_matches?(phrase)
|
21
|
+
password_model.matches?(phrase)
|
22
|
+
end
|
23
|
+
|
24
|
+
def password
|
25
|
+
password_model.secret
|
26
|
+
end
|
27
|
+
|
28
|
+
def password=(value)
|
29
|
+
new_password.secret = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def password_confirmation=(value)
|
33
|
+
new_password.secret_confirmation = value
|
34
|
+
end
|
35
|
+
|
36
|
+
def password_confirmation
|
37
|
+
password_model.secret_confirmation
|
38
|
+
end
|
39
|
+
|
40
|
+
def password_changed?
|
41
|
+
password_model.new_record? || password_model.secret_changed?
|
42
|
+
end
|
43
|
+
|
44
|
+
def after_save
|
45
|
+
@new_password = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def new_password
|
50
|
+
@new_password ||= returning(Password.new) { |p| passwords << p }
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Auth::Behavior::Core::ControllerExtensions
|
2
|
+
def self.included(base)
|
3
|
+
base.instance_eval do
|
4
|
+
include Auth::Behavior::Core::ControllerExtensions::CurrentUser
|
5
|
+
extend Auth::Behavior::Core::ControllerExtensions::ClassMethods
|
6
|
+
helper_method :new_session_path, :current_user
|
7
|
+
hide_action :current_user, :find_current_session, :require_login, :require_logout, :login!, :logout!,
|
8
|
+
:redirect_back_or_default, :new_session_path, :store_location
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def require_login
|
13
|
+
unless current_user
|
14
|
+
store_location
|
15
|
+
flash[:notice] = @session_timeout_message || Auth.login_required_message
|
16
|
+
login_path = Auth.default_login_path ? send(Auth.default_login_path) : Auth.default_destination
|
17
|
+
redirect_to login_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def store_location(url = request.request_uri)
|
22
|
+
session[:destination] = url
|
23
|
+
end
|
24
|
+
|
25
|
+
def require_logout
|
26
|
+
redirect_back_or_default Auth.default_destination, Auth.logout_required_message if current_user
|
27
|
+
end
|
28
|
+
|
29
|
+
# Forcibly logs in the current client as the specified user.
|
30
|
+
#
|
31
|
+
# The options hash is unused, and is reserved for other behaviors to make use of.
|
32
|
+
# For instance, the "remember me" behavior checks for a :remember option and, if true, sets a remembrance token
|
33
|
+
# cookie.
|
34
|
+
def login!(user, options = {})
|
35
|
+
session[:session_token] = user.persistence_token
|
36
|
+
session[:active_at] = Time.now
|
37
|
+
@current_user = user
|
38
|
+
end
|
39
|
+
|
40
|
+
# Forcibly logs out the current client.
|
41
|
+
#
|
42
|
+
# The options hash is unused, and is reserved for other behaviors to make use of.
|
43
|
+
#
|
44
|
+
def logout!(options = {})
|
45
|
+
session[:session_token] = session[:active_at] = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def redirect_back_or_default(path, notice = nil)
|
49
|
+
flash[:notice] = notice if notice
|
50
|
+
redirect_to session.delete(:destination) || path
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Auth::Behavior::Core::ControllerExtensions::ClassMethods
|
2
|
+
def require_login_for(*actions)
|
3
|
+
before_filter :require_login, actions.extract_options!.merge(:only => actions)
|
4
|
+
end
|
5
|
+
|
6
|
+
def require_logout_for(*actions)
|
7
|
+
before_filter :require_logout, actions.extract_options!.merge(:only => actions)
|
8
|
+
end
|
9
|
+
|
10
|
+
def require_login(*args)
|
11
|
+
before_filter :require_login, *args
|
12
|
+
end
|
13
|
+
|
14
|
+
def require_logout(*args)
|
15
|
+
before_filter :require_logout, *args
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :requires_login, :require_login
|
19
|
+
alias_method :require_user, :require_login
|
20
|
+
alias_method :requires_user, :require_login
|
21
|
+
alias_method :requires_logout, :require_logout
|
22
|
+
alias_method :require_no_user, :require_logout
|
23
|
+
alias_method :requires_no_user, :require_logout
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Auth::Behavior::Core::ControllerExtensions::CurrentUser
|
2
|
+
def self.included(base)
|
3
|
+
base.send(:hide_action, :current_user_from_session, :timeout_current_session, :authenticate_with_persistence_token,
|
4
|
+
:authenticate_with_single_access_token, :authenticate_with_session_cookie, :authenticate_current_user)
|
5
|
+
end
|
6
|
+
|
7
|
+
def current_user
|
8
|
+
return @current_user unless @current_user.nil?
|
9
|
+
@current_user = false
|
10
|
+
authenticate_current_user
|
11
|
+
@current_user
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate_current_user
|
15
|
+
if session && session[:session_token]
|
16
|
+
authenticate_with_session_cookie
|
17
|
+
elsif params && params[:single_access_token] # single access token, useful for WS APIs
|
18
|
+
authenticate_with_single_access_token
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def authenticate_with_session_cookie
|
23
|
+
if Auth.session_duration.nil? || session[:active_at] > Auth.session_duration.ago
|
24
|
+
authenticate_with_persistence_token
|
25
|
+
else
|
26
|
+
timeout_current_session
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def authenticate_with_single_access_token
|
31
|
+
# There is no session duration because this works per-request.
|
32
|
+
password = Password.find_by_single_access_token(params[:single_access_token], :include => :authenticatable)
|
33
|
+
@current_user = password.authenticatable if password
|
34
|
+
end
|
35
|
+
|
36
|
+
def authenticate_with_persistence_token
|
37
|
+
password = Password.find_by_persistence_token(session[:session_token], :include => :authenticatable)
|
38
|
+
if password
|
39
|
+
@current_user = password.authenticatable
|
40
|
+
login! @current_user # to refresh session timeout
|
41
|
+
else
|
42
|
+
# Something weird happened and the user's password data can no longer be found. Log him out to prevent
|
43
|
+
# anything else from going wrong.
|
44
|
+
logout!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def timeout_current_session
|
49
|
+
logout!
|
50
|
+
# We'll put the message in the notice, but if the current page requires a login, the flash will be over
|
51
|
+
# written. That's where @session_timeout_message comes in.
|
52
|
+
flash[:notice] = @session_timeout_message = Auth.session_timeout_message
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Auth::Behavior::Core::PasswordMethods
|
2
|
+
def self.included(base)
|
3
|
+
# so apparently dynamic methods haven't been generated by AR yet, so this stuff's been moved to
|
4
|
+
# #after_initialize. Less than ideal but whatever.
|
5
|
+
# base.send(:alias_method_chain, :secret=, :encryption)
|
6
|
+
# base.send(:alias_method_chain, :secret_confirmation=, :encryption)
|
7
|
+
end
|
8
|
+
|
9
|
+
def after_initialize
|
10
|
+
# FIXME: HACK - see self.included(base)
|
11
|
+
|
12
|
+
if attributes.keys.include?('secret')
|
13
|
+
self.secret # uh, makes AR define the method, I guess? This feels clunky...
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
alias_method_chain :secret=, :encryption
|
18
|
+
alias_method_chain :secret_confirmation=, :encryption
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def expired?
|
23
|
+
authenticatable.password_expired?
|
24
|
+
end
|
25
|
+
|
26
|
+
def encrypt(p)
|
27
|
+
self.salt ||= Auth::Token.new.to_s
|
28
|
+
Auth.encryptor.encrypt(p, salt)
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches?(phrase)
|
32
|
+
Auth.encryptor.matches?(secret, phrase, salt)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset_persistence_token
|
36
|
+
self.persistence_token = Auth::Token.new.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_single_access_token
|
40
|
+
self.single_access_token = Auth::Token.new.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset_perishable_token
|
44
|
+
self.perishable_token = Auth::Token.new.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def secret_with_encryption=(phrase)
|
48
|
+
@unencrypted_secret = phrase
|
49
|
+
encrypted_phrase = phrase.blank? ? phrase : encrypt(phrase)
|
50
|
+
returning self.secret_without_encryption = encrypted_phrase do
|
51
|
+
reset_persistence_token
|
52
|
+
reset_single_access_token unless single_access_token # don't reset after it has a value
|
53
|
+
reset_perishable_token
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def secret_confirmation_with_encryption=(phrase)
|
58
|
+
encrypted_phrase = phrase.blank? ? phrase : encrypt(phrase)
|
59
|
+
self.secret_confirmation_without_encryption = encrypted_phrase
|
60
|
+
end
|
61
|
+
|
62
|
+
def unencrypted_secret
|
63
|
+
@unencrypted_secret
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Auth
|
2
|
+
module Behavior
|
3
|
+
class RememberMe < Auth::Behavior::Base
|
4
|
+
migration "create_sparkly_remembered_tokens"
|
5
|
+
|
6
|
+
def apply_to_passwords(password)
|
7
|
+
# no effect
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply_to_accounts(model_config)
|
11
|
+
model_config.target.instance_eval do
|
12
|
+
has_many :remembrance_tokens, :dependent => :destroy, :as => :authenticatable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Auth::Behavior::RememberMe::Configuration
|
2
|
+
# Message to be displayed in flash[:error] when a likely theft of the remember token has been detected.
|
3
|
+
attr_accessor :token_theft_message
|
4
|
+
|
5
|
+
# How long can a user stay logged in?
|
6
|
+
attr_accessor :duration
|
7
|
+
|
8
|
+
# Provides a handle back to the root configuration object.
|
9
|
+
attr_reader :configuration
|
10
|
+
|
11
|
+
# Returns true if the root configuration object's behaviors include :remember_me.
|
12
|
+
def enabled?
|
13
|
+
configuration.behaviors.include? :remember_me
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(configuration)
|
17
|
+
@configuration = configuration
|
18
|
+
@token_theft_message = "Your account may have been hijacked recently! Verify that all settings are correct."
|
19
|
+
@duration = 6.months
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Auth::Behavior::RememberMe::ControllerExtensions
|
2
|
+
# If a :remember option is given, a remembrance cookie will be set. If omitted, the cookie will be, too.
|
3
|
+
def login_with_remembrance!(user, options = {})
|
4
|
+
login_without_remembrance!(user)
|
5
|
+
|
6
|
+
if options[:remember]
|
7
|
+
token = RemembranceToken.create!(:authenticatable => user)
|
8
|
+
set_remembrance_token_cookie(token)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# If a :forget option is given, the remembrance cookie will also be deleted.
|
13
|
+
def logout_with_remembrance!(options = {})
|
14
|
+
logout_without_remembrance!
|
15
|
+
if options[:forget]
|
16
|
+
cookies.delete(:remembrance_token)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def authenticate_current_user_with_remembrance
|
21
|
+
authenticate_current_user_without_remembrance
|
22
|
+
if @current_user == false
|
23
|
+
authenticate_with_remembrance_token
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def authenticate_with_remembrance_token
|
28
|
+
if token_value = cookies[:remembrance_token]
|
29
|
+
token = RemembranceToken.find_by_value(token_value)
|
30
|
+
if token
|
31
|
+
@current_user = token.authenticatable
|
32
|
+
handle_remembrance_token_theft(token) if token.theft?
|
33
|
+
token.regenerate
|
34
|
+
token.save
|
35
|
+
set_remembrance_token_cookie(token)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle_remembrance_token_theft(token)
|
41
|
+
flash[:error] = Auth.remember_me.token_theft_message
|
42
|
+
token.authenticatable.remembrance_tokens.destroy_all
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_remembrance_token_cookie(token)
|
46
|
+
cookies[:remembrance_token] = {
|
47
|
+
:value => token.value,
|
48
|
+
:expires => Auth.remember_me.duration.from_now
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.included(base)
|
53
|
+
base.class_eval do
|
54
|
+
hide_action :login_with_remembrance!, :login_without_remembrance!, :authenticate_current_user_without_remembrance,
|
55
|
+
:authenticate_current_user_with_remembrance, :authenticate_with_remembrance_token,
|
56
|
+
:set_remembrance_token_cookie, :handle_remembrance_token_theft, :logout_with_remembrance!,
|
57
|
+
:logout_without_remembrance!
|
58
|
+
|
59
|
+
unless method_defined?(:login_without_remembrance!)
|
60
|
+
alias_method_chain :login!, :remembrance
|
61
|
+
alias_method_chain :logout!, :remembrance
|
62
|
+
alias_method_chain :authenticate_current_user, :remembrance
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|