sparkly-auth 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|