sorcery 0.11.0 → 0.15.1
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.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE.md +20 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +145 -0
- data/.travis.yml +3 -52
- data/CHANGELOG.md +69 -0
- data/Gemfile +3 -3
- data/{LICENSE.txt → LICENSE.md} +1 -1
- data/README.md +34 -7
- data/lib/generators/sorcery/USAGE +1 -1
- data/lib/generators/sorcery/install_generator.rb +21 -21
- data/lib/generators/sorcery/templates/initializer.rb +164 -69
- data/lib/generators/sorcery/templates/migration/activity_logging.rb +4 -4
- data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +3 -3
- data/lib/generators/sorcery/templates/migration/core.rb +2 -2
- data/lib/generators/sorcery/templates/migration/external.rb +3 -3
- data/lib/generators/sorcery/templates/migration/magic_login.rb +9 -0
- data/lib/generators/sorcery/templates/migration/remember_me.rb +2 -2
- data/lib/generators/sorcery/templates/migration/reset_password.rb +4 -3
- data/lib/generators/sorcery/templates/migration/user_activation.rb +3 -3
- data/lib/sorcery.rb +2 -0
- data/lib/sorcery/adapters/active_record_adapter.rb +3 -2
- data/lib/sorcery/adapters/mongoid_adapter.rb +23 -11
- data/lib/sorcery/controller.rb +26 -15
- data/lib/sorcery/controller/config.rb +2 -0
- data/lib/sorcery/controller/submodules/activity_logging.rb +14 -3
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +7 -3
- data/lib/sorcery/controller/submodules/external.rb +48 -33
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +5 -1
- data/lib/sorcery/controller/submodules/remember_me.rb +9 -10
- data/lib/sorcery/controller/submodules/session_timeout.rb +32 -6
- data/lib/sorcery/crypto_providers/aes256.rb +2 -1
- data/lib/sorcery/crypto_providers/bcrypt.rb +8 -2
- data/lib/sorcery/engine.rb +16 -3
- data/lib/sorcery/model.rb +14 -10
- data/lib/sorcery/model/config.rb +12 -4
- data/lib/sorcery/model/submodules/brute_force_protection.rb +6 -7
- data/lib/sorcery/model/submodules/external.rb +19 -3
- data/lib/sorcery/model/submodules/magic_login.rb +130 -0
- data/lib/sorcery/model/submodules/reset_password.rb +25 -2
- data/lib/sorcery/model/submodules/user_activation.rb +1 -1
- data/lib/sorcery/model/temporary_token.rb +3 -1
- data/lib/sorcery/protocols/oauth.rb +1 -0
- data/lib/sorcery/providers/auth0.rb +46 -0
- data/lib/sorcery/providers/discord.rb +52 -0
- data/lib/sorcery/providers/heroku.rb +1 -0
- data/lib/sorcery/providers/instagram.rb +73 -0
- data/lib/sorcery/providers/line.rb +47 -0
- data/lib/sorcery/providers/linkedin.rb +45 -36
- data/lib/sorcery/providers/vk.rb +5 -4
- data/lib/sorcery/providers/wechat.rb +8 -6
- data/lib/sorcery/test_helpers/internal.rb +5 -4
- data/lib/sorcery/test_helpers/internal/rails.rb +11 -11
- data/lib/sorcery/test_helpers/rails/request.rb +20 -0
- data/lib/sorcery/version.rb +1 -1
- data/sorcery.gemspec +28 -11
- data/spec/active_record/user_activation_spec.rb +2 -2
- data/spec/active_record/user_activity_logging_spec.rb +2 -2
- data/spec/active_record/user_brute_force_protection_spec.rb +2 -2
- data/spec/active_record/user_magic_login_spec.rb +15 -0
- data/spec/active_record/user_oauth_spec.rb +2 -2
- data/spec/active_record/user_remember_me_spec.rb +2 -2
- data/spec/active_record/user_reset_password_spec.rb +2 -2
- data/spec/active_record/user_spec.rb +0 -10
- data/spec/controllers/controller_http_basic_auth_spec.rb +1 -1
- data/spec/controllers/controller_oauth2_spec.rb +212 -123
- data/spec/controllers/controller_oauth_spec.rb +7 -7
- data/spec/controllers/controller_remember_me_spec.rb +16 -8
- data/spec/controllers/controller_session_timeout_spec.rb +90 -3
- data/spec/controllers/controller_spec.rb +13 -3
- data/spec/orm/active_record.rb +2 -2
- data/spec/providers/example_provider_spec.rb +17 -0
- data/spec/providers/example_spec.rb +17 -0
- data/spec/providers/vk_spec.rb +42 -0
- data/spec/rails_app/app/assets/config/manifest.js +1 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +131 -32
- data/spec/rails_app/app/mailers/sorcery_mailer.rb +7 -0
- data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb +13 -0
- data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb +6 -0
- data/spec/rails_app/config/application.rb +8 -3
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environment.rb +1 -1
- data/spec/rails_app/config/routes.rb +14 -0
- data/spec/rails_app/config/secrets.yml +4 -0
- data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +2 -2
- data/spec/rails_app/db/migrate/invalidate_active_sessions/20180221093235_add_invalidate_active_sessions_before_to_users.rb +9 -0
- data/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb +17 -0
- data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +2 -0
- data/spec/rails_app/db/schema.rb +7 -9
- data/spec/shared_examples/user_magic_login_shared_examples.rb +150 -0
- data/spec/shared_examples/user_oauth_shared_examples.rb +1 -1
- data/spec/shared_examples/user_remember_me_shared_examples.rb +1 -1
- data/spec/shared_examples/user_reset_password_shared_examples.rb +37 -5
- data/spec/shared_examples/user_shared_examples.rb +104 -43
- data/spec/sorcery_crypto_providers_spec.rb +61 -1
- data/spec/sorcery_temporary_token_spec.rb +27 -0
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +2 -2
- data/spec/support/migration_helper.rb +19 -0
- data/spec/support/providers/example.rb +11 -0
- data/spec/support/providers/example_provider.rb +11 -0
- metadata +89 -33
- data/gemfiles/active_record-rails40.gemfile +0 -7
- data/gemfiles/active_record-rails41.gemfile +0 -7
- data/gemfiles/active_record-rails42.gemfile +0 -7
- data/spec/rails_app/config/initializers/secret_token.rb +0 -7
@@ -19,7 +19,10 @@ module Sorcery
|
|
19
19
|
end
|
20
20
|
merge_http_basic_auth_defaults!
|
21
21
|
end
|
22
|
-
|
22
|
+
# FIXME: There is likely a more elegant way to safeguard these callbacks.
|
23
|
+
unless Config.login_sources.include?(:login_from_basic_auth)
|
24
|
+
Config.login_sources << :login_from_basic_auth
|
25
|
+
end
|
23
26
|
end
|
24
27
|
|
25
28
|
module InstanceMethods
|
@@ -57,6 +60,7 @@ module Sorcery
|
|
57
60
|
while current_controller != ActionController::Base
|
58
61
|
result = Config.controller_to_realm_map[current_controller.controller_name]
|
59
62
|
return result if result
|
63
|
+
|
60
64
|
current_controller = current_controller.superclass
|
61
65
|
end
|
62
66
|
nil
|
@@ -17,9 +17,13 @@ module Sorcery
|
|
17
17
|
end
|
18
18
|
merge_remember_me_defaults!
|
19
19
|
end
|
20
|
-
|
21
|
-
Config.
|
22
|
-
|
20
|
+
# FIXME: There is likely a more elegant way to safeguard these callbacks.
|
21
|
+
unless Config.login_sources.include?(:login_from_cookie)
|
22
|
+
Config.login_sources << :login_from_cookie
|
23
|
+
end
|
24
|
+
unless Config.before_logout.include?(:forget_me!)
|
25
|
+
Config.before_logout << :forget_me!
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
module InstanceMethods
|
@@ -51,20 +55,15 @@ module Sorcery
|
|
51
55
|
|
52
56
|
protected
|
53
57
|
|
54
|
-
# calls remember_me! if a third credential was passed to the login method.
|
55
|
-
# Runs as a hook after login.
|
56
|
-
def remember_me_if_asked_to(_user, credentials)
|
57
|
-
remember_me! if credentials.size == 3 && credentials[2] && credentials[2] != '0'
|
58
|
-
end
|
59
|
-
|
60
58
|
# Checks the cookie for a remember me token, tried to find a user with that token
|
61
59
|
# and logs the user in if found.
|
62
60
|
# Runs as a login source. See 'current_user' method for how it is used.
|
63
61
|
def login_from_cookie
|
64
|
-
user = cookies.signed[:remember_me_token] && user_class.sorcery_adapter.find_by_remember_me_token(cookies.signed[:remember_me_token])
|
62
|
+
user = cookies.signed[:remember_me_token] && user_class.sorcery_adapter.find_by_remember_me_token(cookies.signed[:remember_me_token]) if defined? cookies
|
65
63
|
if user && user.has_remember_me_token?
|
66
64
|
set_remember_me_cookie!(user)
|
67
65
|
session[:user_id] = user.id.to_s
|
66
|
+
after_remember_me!(user)
|
68
67
|
@current_user = user
|
69
68
|
else
|
70
69
|
@current_user = false
|
@@ -12,24 +12,41 @@ module Sorcery
|
|
12
12
|
attr_accessor :session_timeout
|
13
13
|
# use the last action as the beginning of session timeout.
|
14
14
|
attr_accessor :session_timeout_from_last_action
|
15
|
+
# allow users to invalidate active sessions
|
16
|
+
attr_accessor :session_timeout_invalidate_active_sessions_enabled
|
15
17
|
|
16
18
|
def merge_session_timeout_defaults!
|
17
|
-
@defaults.merge!(:@session_timeout
|
18
|
-
:@session_timeout_from_last_action
|
19
|
+
@defaults.merge!(:@session_timeout => 3600, # 1.hour
|
20
|
+
:@session_timeout_from_last_action => false,
|
21
|
+
:@session_timeout_invalidate_active_sessions_enabled => false)
|
19
22
|
end
|
20
23
|
end
|
21
24
|
merge_session_timeout_defaults!
|
22
25
|
end
|
23
|
-
|
26
|
+
# FIXME: There is likely a more elegant way to safeguard these callbacks.
|
27
|
+
unless Config.after_login.include?(:register_login_time)
|
28
|
+
Config.after_login << :register_login_time
|
29
|
+
end
|
30
|
+
unless Config.after_remember_me.include?(:register_login_time)
|
31
|
+
Config.after_remember_me << :register_login_time
|
32
|
+
end
|
24
33
|
base.prepend_before_action :validate_session
|
25
34
|
end
|
26
35
|
|
27
36
|
module InstanceMethods
|
37
|
+
def invalidate_active_sessions!
|
38
|
+
return unless Config.session_timeout_invalidate_active_sessions_enabled
|
39
|
+
return unless current_user.present?
|
40
|
+
|
41
|
+
current_user.send(:invalidate_sessions_before=, Time.now.in_time_zone)
|
42
|
+
current_user.save
|
43
|
+
end
|
44
|
+
|
28
45
|
protected
|
29
46
|
|
30
47
|
# Registers last login to be used as the timeout starting point.
|
31
48
|
# Runs as a hook after a successful login.
|
32
|
-
def register_login_time(_user, _credentials)
|
49
|
+
def register_login_time(_user, _credentials = nil)
|
33
50
|
session[:login_time] = session[:last_action_time] = Time.now.in_time_zone
|
34
51
|
end
|
35
52
|
|
@@ -37,9 +54,9 @@ module Sorcery
|
|
37
54
|
# To be used as a before_action, before require_login
|
38
55
|
def validate_session
|
39
56
|
session_to_use = Config.session_timeout_from_last_action ? session[:last_action_time] : session[:login_time]
|
40
|
-
if session_to_use && sorcery_session_expired?(session_to_use.to_time)
|
57
|
+
if (session_to_use && sorcery_session_expired?(session_to_use.to_time)) || sorcery_session_invalidated?
|
41
58
|
reset_sorcery_session
|
42
|
-
|
59
|
+
remove_instance_variable :@current_user if defined? @current_user
|
43
60
|
else
|
44
61
|
session[:last_action_time] = Time.now.in_time_zone
|
45
62
|
end
|
@@ -48,6 +65,15 @@ module Sorcery
|
|
48
65
|
def sorcery_session_expired?(time)
|
49
66
|
Time.now.in_time_zone - time > Config.session_timeout
|
50
67
|
end
|
68
|
+
|
69
|
+
# Use login time if present, otherwise use last action time.
|
70
|
+
def sorcery_session_invalidated?
|
71
|
+
return false unless Config.session_timeout_invalidate_active_sessions_enabled
|
72
|
+
return false unless current_user.present? && current_user.try(:invalidate_sessions_before).present?
|
73
|
+
|
74
|
+
time = session[:login_time] || session[:last_action_time] || Time.now.in_time_zone
|
75
|
+
time < current_user.invalidate_sessions_before
|
76
|
+
end
|
51
77
|
end
|
52
78
|
end
|
53
79
|
end
|
@@ -29,7 +29,7 @@ module Sorcery
|
|
29
29
|
|
30
30
|
def matches?(crypted, *tokens)
|
31
31
|
decrypt(crypted) == tokens.join
|
32
|
-
rescue OpenSSL::CipherError
|
32
|
+
rescue OpenSSL::Cipher::CipherError
|
33
33
|
false
|
34
34
|
end
|
35
35
|
|
@@ -43,6 +43,7 @@ module Sorcery
|
|
43
43
|
|
44
44
|
def aes
|
45
45
|
raise ArgumentError, "#{name} expects a 32 bytes long key. Please use Sorcery::Model::Config.encryption_key to set it." if @key.nil? || @key == ''
|
46
|
+
|
46
47
|
@aes ||= OpenSSL::Cipher.new('AES-256-ECB')
|
47
48
|
end
|
48
49
|
end
|
@@ -40,6 +40,10 @@ module Sorcery
|
|
40
40
|
# You are good to go!
|
41
41
|
class BCrypt
|
42
42
|
class << self
|
43
|
+
# Setting the option :pepper allows users to append an app-specific secret token.
|
44
|
+
# Basically it's equivalent to :salt_join_token option, but have a different name to ensure
|
45
|
+
# backward compatibility in generating/matching passwords.
|
46
|
+
attr_accessor :pepper
|
43
47
|
# This is the :cost option for the BCrpyt library.
|
44
48
|
# The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
|
45
49
|
# Set this to whatever you want, play around with it to get that perfect balance between
|
@@ -60,6 +64,7 @@ module Sorcery
|
|
60
64
|
def matches?(hash, *tokens)
|
61
65
|
hash = new_from_hash(hash)
|
62
66
|
return false if hash.nil? || hash == {}
|
67
|
+
|
63
68
|
hash == join_tokens(tokens)
|
64
69
|
end
|
65
70
|
|
@@ -76,18 +81,19 @@ module Sorcery
|
|
76
81
|
|
77
82
|
def reset!
|
78
83
|
@cost = 10
|
84
|
+
@pepper = ''
|
79
85
|
end
|
80
86
|
|
81
87
|
private
|
82
88
|
|
83
89
|
def join_tokens(tokens)
|
84
|
-
tokens.flatten.join
|
90
|
+
tokens.flatten.join.concat(pepper.to_s) # make sure to add pepper in case tokens have only one element
|
85
91
|
end
|
86
92
|
|
87
93
|
def new_from_hash(hash)
|
88
94
|
::BCrypt::Password.new(hash)
|
89
95
|
rescue ::BCrypt::Errors::InvalidHash
|
90
|
-
|
96
|
+
nil
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
data/lib/sorcery/engine.rb
CHANGED
@@ -7,10 +7,23 @@ module Sorcery
|
|
7
7
|
class Engine < Rails::Engine
|
8
8
|
config.sorcery = ::Sorcery::Controller::Config
|
9
9
|
|
10
|
+
# TODO: Should this include a modified version of the helper methods?
|
10
11
|
initializer 'extend Controller with sorcery' do
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
# FIXME: on_load is needed to fix Rails 6 deprecations, but it breaks
|
13
|
+
# applications due to undefined method errors.
|
14
|
+
# ActiveSupport.on_load(:action_controller_api) do
|
15
|
+
if defined?(ActionController::API)
|
16
|
+
ActionController::API.send(:include, Sorcery::Controller)
|
17
|
+
end
|
18
|
+
|
19
|
+
# FIXME: on_load is needed to fix Rails 6 deprecations, but it breaks
|
20
|
+
# applications due to undefined method errors.
|
21
|
+
# ActiveSupport.on_load(:action_controller_base) do
|
22
|
+
if defined?(ActionController::Base)
|
23
|
+
ActionController::Base.send(:include, Sorcery::Controller)
|
24
|
+
ActionController::Base.helper_method :current_user
|
25
|
+
ActionController::Base.helper_method :logged_in?
|
26
|
+
end
|
14
27
|
end
|
15
28
|
end
|
16
29
|
end
|
data/lib/sorcery/model.rb
CHANGED
@@ -47,12 +47,15 @@ module Sorcery
|
|
47
47
|
class_eval do
|
48
48
|
@sorcery_config.submodules = ::Sorcery::Controller::Config.submodules
|
49
49
|
@sorcery_config.submodules.each do |mod|
|
50
|
+
# TODO: Is there a cleaner way to handle missing submodules?
|
51
|
+
# rubocop:disable Lint/HandleExceptions
|
50
52
|
begin
|
51
53
|
include Submodules.const_get(mod.to_s.split('_').map(&:capitalize).join)
|
52
54
|
rescue NameError
|
53
55
|
# don't stop on a missing submodule. Needed because some submodules are only defined
|
54
56
|
# in the controller side.
|
55
57
|
end
|
58
|
+
# rubocop:enable Lint/HandleExceptions
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
@@ -99,10 +102,6 @@ module Sorcery
|
|
99
102
|
|
100
103
|
set_encryption_attributes
|
101
104
|
|
102
|
-
unless user.valid_password?(credentials[1])
|
103
|
-
return authentication_response(user: user, failure: :invalid_password, &block)
|
104
|
-
end
|
105
|
-
|
106
105
|
if user.respond_to?(:active_for_authentication?) && !user.active_for_authentication?
|
107
106
|
return authentication_response(user: user, failure: :inactive, &block)
|
108
107
|
end
|
@@ -115,6 +114,10 @@ module Sorcery
|
|
115
114
|
end
|
116
115
|
end
|
117
116
|
|
117
|
+
unless user.valid_password?(credentials[1])
|
118
|
+
return authentication_response(user: user, failure: :invalid_password, &block)
|
119
|
+
end
|
120
|
+
|
118
121
|
authentication_response(user: user, return_value: user, &block)
|
119
122
|
end
|
120
123
|
|
@@ -139,6 +142,7 @@ module Sorcery
|
|
139
142
|
def set_encryption_attributes
|
140
143
|
@sorcery_config.encryption_provider.stretches = @sorcery_config.stretches if @sorcery_config.encryption_provider.respond_to?(:stretches) && @sorcery_config.stretches
|
141
144
|
@sorcery_config.encryption_provider.join_token = @sorcery_config.salt_join_token if @sorcery_config.encryption_provider.respond_to?(:join_token) && @sorcery_config.salt_join_token
|
145
|
+
@sorcery_config.encryption_provider.pepper = @sorcery_config.pepper if @sorcery_config.encryption_provider.respond_to?(:pepper) && @sorcery_config.pepper
|
142
146
|
end
|
143
147
|
|
144
148
|
def add_config_inheritance
|
@@ -192,9 +196,9 @@ module Sorcery
|
|
192
196
|
config = sorcery_config
|
193
197
|
send(:"#{config.password_attribute_name}=", nil)
|
194
198
|
|
195
|
-
|
196
|
-
|
197
|
-
|
199
|
+
return unless respond_to?(:"#{config.password_attribute_name}_confirmation=")
|
200
|
+
|
201
|
+
send(:"#{config.password_attribute_name}_confirmation=", nil)
|
198
202
|
end
|
199
203
|
|
200
204
|
# calls the requested email method on the configured mailer
|
@@ -202,9 +206,9 @@ module Sorcery
|
|
202
206
|
def generic_send_email(method, mailer)
|
203
207
|
config = sorcery_config
|
204
208
|
mail = config.send(mailer).send(config.send(method), self)
|
205
|
-
|
206
|
-
|
207
|
-
|
209
|
+
return unless mail.respond_to?(config.email_delivery_method)
|
210
|
+
|
211
|
+
mail.send(config.email_delivery_method)
|
208
212
|
end
|
209
213
|
end
|
210
214
|
end
|
data/lib/sorcery/model/config.rb
CHANGED
@@ -4,8 +4,6 @@
|
|
4
4
|
module Sorcery
|
5
5
|
module Model
|
6
6
|
class Config
|
7
|
-
# change default username attribute, for example, to use :email as the login.
|
8
|
-
attr_accessor :username_attribute_names
|
9
7
|
# change *virtual* password attribute, the one which is used until an encrypted one is generated.
|
10
8
|
attr_accessor :password_attribute_name
|
11
9
|
# change default email attribute.
|
@@ -14,7 +12,11 @@ module Sorcery
|
|
14
12
|
attr_accessor :downcase_username_before_authenticating
|
15
13
|
# change default crypted_password attribute.
|
16
14
|
attr_accessor :crypted_password_attribute_name
|
15
|
+
# application-specific secret token that is joined with the password and its salt.
|
16
|
+
# Currently available with BCrypt (default crypt provider) only.
|
17
|
+
attr_accessor :pepper
|
17
18
|
# what pattern to use to join the password with the salt
|
19
|
+
# APPLICABLE TO MD5, SHA1, SHA256, SHA512. Other crypt providers (incl. BCrypt) ignore this parameter.
|
18
20
|
attr_accessor :salt_join_token
|
19
21
|
# change default salt attribute.
|
20
22
|
attr_accessor :salt_attribute_name
|
@@ -35,7 +37,11 @@ module Sorcery
|
|
35
37
|
attr_accessor :email_delivery_method
|
36
38
|
# an array of method names to call after configuration by user. used internally.
|
37
39
|
attr_accessor :after_config
|
40
|
+
# Set token randomness
|
41
|
+
attr_accessor :token_randomness
|
38
42
|
|
43
|
+
# change default username attribute, for example, to use :email as the login. See 'username_attribute_names=' below.
|
44
|
+
attr_reader :username_attribute_names
|
39
45
|
# change default encryption_provider.
|
40
46
|
attr_reader :encryption_provider
|
41
47
|
# use an external encryption class.
|
@@ -55,13 +61,15 @@ module Sorcery
|
|
55
61
|
:@encryption_provider => CryptoProviders::BCrypt,
|
56
62
|
:@custom_encryption_provider => nil,
|
57
63
|
:@encryption_key => nil,
|
64
|
+
:@pepper => '',
|
58
65
|
:@salt_join_token => '',
|
59
66
|
:@salt_attribute_name => :salt,
|
60
67
|
:@stretches => nil,
|
61
68
|
:@subclasses_inherit_config => false,
|
62
69
|
:@before_authenticate => [],
|
63
70
|
:@after_config => [],
|
64
|
-
:@email_delivery_method => default_email_delivery_method
|
71
|
+
:@email_delivery_method => default_email_delivery_method,
|
72
|
+
:@token_randomness => 15
|
65
73
|
}
|
66
74
|
reset!
|
67
75
|
end
|
@@ -93,7 +101,7 @@ module Sorcery
|
|
93
101
|
when :bcrypt then CryptoProviders::BCrypt
|
94
102
|
when :custom then @custom_encryption_provider
|
95
103
|
else raise ArgumentError, "Encryption algorithm supplied, #{algo}, is invalid"
|
96
|
-
|
104
|
+
end
|
97
105
|
end
|
98
106
|
|
99
107
|
private
|
@@ -14,7 +14,6 @@ module Sorcery
|
|
14
14
|
:consecutive_login_retries_amount_limit, # how many failed logins allowed.
|
15
15
|
:login_lock_time_period, # how long the user should be banned.
|
16
16
|
# in seconds. 0 for permanent.
|
17
|
-
|
18
17
|
:unlock_token_attribute_name, # Unlock token attribute name
|
19
18
|
:unlock_token_email_method_name, # Mailer method name
|
20
19
|
:unlock_token_mailer_disabled, # When true, dont send unlock token via email
|
@@ -70,9 +69,9 @@ module Sorcery
|
|
70
69
|
|
71
70
|
sorcery_adapter.increment(config.failed_logins_count_attribute_name)
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
return unless send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_limit
|
73
|
+
|
74
|
+
login_lock!
|
76
75
|
end
|
77
76
|
|
78
77
|
# /!\
|
@@ -98,9 +97,9 @@ module Sorcery
|
|
98
97
|
config.unlock_token_attribute_name => TemporaryToken.generate_random_token }
|
99
98
|
sorcery_adapter.update_attributes(attributes)
|
100
99
|
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
return if config.unlock_token_mailer_disabled || config.unlock_token_mailer.nil?
|
101
|
+
|
102
|
+
send_unlock_token_email!
|
104
103
|
end
|
105
104
|
|
106
105
|
def login_unlocked?
|
@@ -40,12 +40,13 @@ module Sorcery
|
|
40
40
|
def load_from_provider(provider, uid)
|
41
41
|
config = sorcery_config
|
42
42
|
authentication = config.authentications_class.sorcery_adapter.find_by_oauth_credentials(provider, uid)
|
43
|
-
|
43
|
+
# Return user if matching authentication found
|
44
|
+
sorcery_adapter.find_by_id(authentication.send(config.authentications_user_id_attribute_name)) if authentication
|
44
45
|
end
|
45
46
|
|
46
47
|
def create_and_validate_from_provider(provider, uid, attrs)
|
47
48
|
user = new(attrs)
|
48
|
-
user.send(sorcery_config.authentications_class.
|
49
|
+
user.send(sorcery_config.authentications_class.name.demodulize.underscore.pluralize).build(
|
49
50
|
sorcery_config.provider_uid_attribute_name => uid,
|
50
51
|
sorcery_config.provider_attribute_name => provider
|
51
52
|
)
|
@@ -73,11 +74,26 @@ module Sorcery
|
|
73
74
|
end
|
74
75
|
user
|
75
76
|
end
|
77
|
+
|
78
|
+
# NOTE: Should this build the authentication as well and return [user, auth]?
|
79
|
+
# Currently, users call this function for the user and call add_provider_to_user after saving
|
80
|
+
def build_from_provider(attrs)
|
81
|
+
user = new
|
82
|
+
attrs.each do |k, v|
|
83
|
+
user.send(:"#{k}=", v)
|
84
|
+
end
|
85
|
+
|
86
|
+
if block_given?
|
87
|
+
return false unless yield user
|
88
|
+
end
|
89
|
+
|
90
|
+
user
|
91
|
+
end
|
76
92
|
end
|
77
93
|
|
78
94
|
module InstanceMethods
|
79
95
|
def add_provider_to_user(provider, uid)
|
80
|
-
authentications = sorcery_config.authentications_class.name.underscore.pluralize
|
96
|
+
authentications = sorcery_config.authentications_class.name.demodulize.underscore.pluralize
|
81
97
|
# first check to see if user has a particular authentication already
|
82
98
|
if sorcery_adapter.find_authentication_by_oauth_credentials(authentications, provider, uid).nil?
|
83
99
|
user = send(authentications).build(sorcery_config.provider_uid_attribute_name => uid,
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Model
|
3
|
+
module Submodules
|
4
|
+
# This submodule adds the ability to login via email without password.
|
5
|
+
# When the user requests an email is sent to him with a url.
|
6
|
+
# The url includes a token, which is also saved with the user's record in the db.
|
7
|
+
# The token has configurable expiration.
|
8
|
+
# When the user clicks the url in the email, providing the token has not yet expired,
|
9
|
+
# he will be able to login.
|
10
|
+
#
|
11
|
+
# When using this submodule, supplying a mailer is mandatory.
|
12
|
+
module MagicLogin
|
13
|
+
def self.included(base)
|
14
|
+
base.sorcery_config.class_eval do
|
15
|
+
attr_accessor :magic_login_token_attribute_name, # magic login code attribute name.
|
16
|
+
:magic_login_token_expires_at_attribute_name, # expires at attribute name.
|
17
|
+
:magic_login_email_sent_at_attribute_name, # when was email sent, used for hammering
|
18
|
+
# protection.
|
19
|
+
:magic_login_mailer_class, # mailer class. Needed.
|
20
|
+
:magic_login_mailer_disabled, # when true sorcery will not automatically
|
21
|
+
# email magic login details and allow you to
|
22
|
+
# manually handle how and when email is sent
|
23
|
+
:magic_login_email_method_name, # magic login email method on your
|
24
|
+
# mailer class.
|
25
|
+
:magic_login_expiration_period, # how many seconds before the request
|
26
|
+
# expires. nil for never expires.
|
27
|
+
:magic_login_time_between_emails # hammering protection, how long to wait
|
28
|
+
# before allowing another email to be sent.
|
29
|
+
end
|
30
|
+
|
31
|
+
base.sorcery_config.instance_eval do
|
32
|
+
@defaults.merge!(:@magic_login_token_attribute_name => :magic_login_token,
|
33
|
+
:@magic_login_token_expires_at_attribute_name => :magic_login_token_expires_at,
|
34
|
+
:@magic_login_email_sent_at_attribute_name => :magic_login_email_sent_at,
|
35
|
+
:@magic_login_mailer_class => nil,
|
36
|
+
:@magic_login_mailer_disabled => true,
|
37
|
+
:@magic_login_email_method_name => :magic_login_email,
|
38
|
+
:@magic_login_expiration_period => 15 * 60,
|
39
|
+
:@magic_login_time_between_emails => 5 * 60)
|
40
|
+
|
41
|
+
reset!
|
42
|
+
end
|
43
|
+
|
44
|
+
base.extend(ClassMethods)
|
45
|
+
|
46
|
+
base.sorcery_config.after_config << :validate_mailer_defined
|
47
|
+
base.sorcery_config.after_config << :define_magic_login_fields
|
48
|
+
|
49
|
+
base.send(:include, InstanceMethods)
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
# Find user by token, also checks for expiration.
|
54
|
+
# Returns the user if token found and is valid.
|
55
|
+
def load_from_magic_login_token(token, &block)
|
56
|
+
load_from_token(
|
57
|
+
token,
|
58
|
+
@sorcery_config.magic_login_token_attribute_name,
|
59
|
+
@sorcery_config.magic_login_token_expires_at_attribute_name,
|
60
|
+
&block
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# This submodule requires the developer to define his own mailer class to be used by it
|
67
|
+
# when magic_login_mailer_disabled is false
|
68
|
+
def validate_mailer_defined
|
69
|
+
msg = 'To use magic_login submodule, you must define a mailer (config.magic_login_mailer_class = YourMailerClass).'
|
70
|
+
raise ArgumentError, msg if @sorcery_config.magic_login_mailer_class.nil? && @sorcery_config.magic_login_mailer_disabled == false
|
71
|
+
end
|
72
|
+
|
73
|
+
def define_magic_login_fields
|
74
|
+
sorcery_adapter.define_field sorcery_config.magic_login_token_attribute_name, String
|
75
|
+
sorcery_adapter.define_field sorcery_config.magic_login_token_expires_at_attribute_name, Time
|
76
|
+
sorcery_adapter.define_field sorcery_config.magic_login_email_sent_at_attribute_name, Time
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module InstanceMethods
|
81
|
+
# generates a reset code with expiration
|
82
|
+
def generate_magic_login_token!
|
83
|
+
config = sorcery_config
|
84
|
+
attributes = {
|
85
|
+
config.magic_login_token_attribute_name => TemporaryToken.generate_random_token,
|
86
|
+
config.magic_login_email_sent_at_attribute_name => Time.now.in_time_zone
|
87
|
+
}
|
88
|
+
attributes[config.magic_login_token_expires_at_attribute_name] = Time.now.in_time_zone + config.magic_login_expiration_period if config.magic_login_expiration_period
|
89
|
+
|
90
|
+
sorcery_adapter.update_attributes(attributes)
|
91
|
+
end
|
92
|
+
|
93
|
+
# generates a magic login code with expiration and sends an email to the user.
|
94
|
+
def deliver_magic_login_instructions!
|
95
|
+
mail = false
|
96
|
+
config = sorcery_config
|
97
|
+
# hammering protection
|
98
|
+
return false if !config.magic_login_time_between_emails.nil? &&
|
99
|
+
send(config.magic_login_email_sent_at_attribute_name) &&
|
100
|
+
send(config.magic_login_email_sent_at_attribute_name) > config.magic_login_time_between_emails.seconds.ago
|
101
|
+
|
102
|
+
self.class.sorcery_adapter.transaction do
|
103
|
+
generate_magic_login_token!
|
104
|
+
unless config.magic_login_mailer_disabled
|
105
|
+
send_magic_login_email!
|
106
|
+
mail = true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
mail
|
110
|
+
end
|
111
|
+
|
112
|
+
# Clears the token.
|
113
|
+
def clear_magic_login_token!
|
114
|
+
config = sorcery_config
|
115
|
+
sorcery_adapter.update_attributes(
|
116
|
+
config.magic_login_token_attribute_name => nil,
|
117
|
+
config.magic_login_token_expires_at_attribute_name => nil
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
def send_magic_login_email!
|
124
|
+
generic_send_email(:magic_login_email_method_name, :magic_login_mailer_class)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|