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
@@ -1,9 +1,9 @@
|
|
1
1
|
class SorceryActivityLogging < <%= migration_class_name %>
|
2
2
|
def change
|
3
|
-
add_column :<%= model_class_name.tableize %>, :last_login_at, :datetime, :
|
4
|
-
add_column :<%= model_class_name.tableize %>, :last_logout_at, :datetime, :
|
5
|
-
add_column :<%= model_class_name.tableize %>, :last_activity_at, :datetime, :
|
6
|
-
add_column :<%= model_class_name.tableize %>, :last_login_from_ip_address, :string, :
|
3
|
+
add_column :<%= model_class_name.tableize %>, :last_login_at, :datetime, default: nil
|
4
|
+
add_column :<%= model_class_name.tableize %>, :last_logout_at, :datetime, default: nil
|
5
|
+
add_column :<%= model_class_name.tableize %>, :last_activity_at, :datetime, default: nil
|
6
|
+
add_column :<%= model_class_name.tableize %>, :last_login_from_ip_address, :string, default: nil
|
7
7
|
|
8
8
|
add_index :<%= model_class_name.tableize %>, [:last_logout_at, :last_activity_at]
|
9
9
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class SorceryBruteForceProtection < <%= migration_class_name %>
|
2
2
|
def change
|
3
|
-
add_column :<%= model_class_name.tableize %>, :failed_logins_count, :integer, :
|
4
|
-
add_column :<%= model_class_name.tableize %>, :lock_expires_at, :datetime, :
|
5
|
-
add_column :<%= model_class_name.tableize %>, :unlock_token, :string, :
|
3
|
+
add_column :<%= model_class_name.tableize %>, :failed_logins_count, :integer, default: 0
|
4
|
+
add_column :<%= model_class_name.tableize %>, :lock_expires_at, :datetime, default: nil
|
5
|
+
add_column :<%= model_class_name.tableize %>, :unlock_token, :string, default: nil
|
6
6
|
|
7
7
|
add_index :<%= model_class_name.tableize %>, :unlock_token
|
8
8
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
class SorceryCore < <%= migration_class_name %>
|
2
2
|
def change
|
3
3
|
create_table :<%= model_class_name.tableize %> do |t|
|
4
|
-
t.string :email, :
|
4
|
+
t.string :email, null: false
|
5
5
|
t.string :crypted_password
|
6
6
|
t.string :salt
|
7
7
|
|
8
|
-
t.timestamps :
|
8
|
+
t.timestamps null: false
|
9
9
|
end
|
10
10
|
|
11
11
|
add_index :<%= model_class_name.tableize %>, :email, unique: true
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class SorceryExternal < <%= migration_class_name %>
|
2
2
|
def change
|
3
3
|
create_table :authentications do |t|
|
4
|
-
t.integer :<%= model_class_name.tableize.singularize %>_id, :
|
5
|
-
t.string :provider, :uid, :
|
4
|
+
t.integer :<%= model_class_name.tableize.singularize %>_id, null: false
|
5
|
+
t.string :provider, :uid, null: false
|
6
6
|
|
7
|
-
t.timestamps :
|
7
|
+
t.timestamps null: false
|
8
8
|
end
|
9
9
|
|
10
10
|
add_index :authentications, [:provider, :uid]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class SorceryMagicLogin < <%= migration_class_name %>
|
2
|
+
def change
|
3
|
+
add_column :<%= model_class_name.tableize %>, :magic_login_token, :string, default: nil
|
4
|
+
add_column :<%= model_class_name.tableize %>, :magic_login_token_expires_at, :datetime, default: nil
|
5
|
+
add_column :<%= model_class_name.tableize %>, :magic_login_email_sent_at, :datetime, default: nil
|
6
|
+
|
7
|
+
add_index :<%= model_class_name.tableize %>, :magic_login_token
|
8
|
+
end
|
9
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class SorceryRememberMe < <%= migration_class_name %>
|
2
2
|
def change
|
3
|
-
add_column :<%= model_class_name.tableize %>, :remember_me_token, :string, :
|
4
|
-
add_column :<%= model_class_name.tableize %>, :remember_me_token_expires_at, :datetime, :
|
3
|
+
add_column :<%= model_class_name.tableize %>, :remember_me_token, :string, default: nil
|
4
|
+
add_column :<%= model_class_name.tableize %>, :remember_me_token_expires_at, :datetime, default: nil
|
5
5
|
|
6
6
|
add_index :<%= model_class_name.tableize %>, :remember_me_token
|
7
7
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
class SorceryResetPassword < <%= migration_class_name %>
|
2
2
|
def change
|
3
|
-
add_column :<%= model_class_name.tableize %>, :reset_password_token, :string, :
|
4
|
-
add_column :<%= model_class_name.tableize %>, :reset_password_token_expires_at, :datetime, :
|
5
|
-
add_column :<%= model_class_name.tableize %>, :reset_password_email_sent_at, :datetime, :
|
3
|
+
add_column :<%= model_class_name.tableize %>, :reset_password_token, :string, default: nil
|
4
|
+
add_column :<%= model_class_name.tableize %>, :reset_password_token_expires_at, :datetime, default: nil
|
5
|
+
add_column :<%= model_class_name.tableize %>, :reset_password_email_sent_at, :datetime, default: nil
|
6
|
+
add_column :<%= model_class_name.tableize %>, :access_count_to_reset_password_page, :integer, default: 0
|
6
7
|
|
7
8
|
add_index :<%= model_class_name.tableize %>, :reset_password_token
|
8
9
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class SorceryUserActivation < <%= migration_class_name %>
|
2
2
|
def change
|
3
|
-
add_column :<%= model_class_name.tableize %>, :activation_state, :string, :
|
4
|
-
add_column :<%= model_class_name.tableize %>, :activation_token, :string, :
|
5
|
-
add_column :<%= model_class_name.tableize %>, :activation_token_expires_at, :datetime, :
|
3
|
+
add_column :<%= model_class_name.tableize %>, :activation_state, :string, default: nil
|
4
|
+
add_column :<%= model_class_name.tableize %>, :activation_token, :string, default: nil
|
5
|
+
add_column :<%= model_class_name.tableize %>, :activation_token_expires_at, :datetime, default: nil
|
6
6
|
|
7
7
|
add_index :<%= model_class_name.tableize %>, :activation_token
|
8
8
|
end
|
data/lib/sorcery.rb
CHANGED
@@ -18,6 +18,7 @@ module Sorcery
|
|
18
18
|
require 'sorcery/model/submodules/activity_logging'
|
19
19
|
require 'sorcery/model/submodules/brute_force_protection'
|
20
20
|
require 'sorcery/model/submodules/external'
|
21
|
+
require 'sorcery/model/submodules/magic_login'
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
@@ -56,6 +57,7 @@ module Sorcery
|
|
56
57
|
module Rails
|
57
58
|
require 'sorcery/test_helpers/rails/controller'
|
58
59
|
require 'sorcery/test_helpers/rails/integration'
|
60
|
+
require 'sorcery/test_helpers/rails/request'
|
59
61
|
end
|
60
62
|
|
61
63
|
module Internal
|
@@ -6,7 +6,8 @@ module Sorcery
|
|
6
6
|
@model.send(:"#{name}=", value)
|
7
7
|
end
|
8
8
|
primary_key = @model.class.primary_key
|
9
|
-
@model.class.where(:"#{primary_key}" => @model.send(:"#{primary_key}")).update_all(attrs)
|
9
|
+
updated_count = @model.class.where(:"#{primary_key}" => @model.send(:"#{primary_key}")).update_all(attrs)
|
10
|
+
updated_count == 1
|
10
11
|
end
|
11
12
|
|
12
13
|
def save(options = {})
|
@@ -34,7 +35,7 @@ module Sorcery
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def define_callback(time, event, method_name, options = {})
|
37
|
-
@klass.send "#{time}_#{event}", method_name, options.slice(:if)
|
38
|
+
@klass.send "#{time}_#{event}", method_name, options.slice(:if, :on)
|
38
39
|
end
|
39
40
|
|
40
41
|
def find_by_oauth_credentials(provider, uid)
|
@@ -10,7 +10,7 @@ module Sorcery
|
|
10
10
|
attrs[name] = value.utc if value.is_a?(ActiveSupport::TimeWithZone)
|
11
11
|
@model.send(:"#{name}=", value)
|
12
12
|
end
|
13
|
-
@model.class.where(:
|
13
|
+
@model.class.where(_id: @model.id).update_all(attrs)
|
14
14
|
end
|
15
15
|
|
16
16
|
def update_attribute(name, value)
|
@@ -23,21 +23,29 @@ module Sorcery
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def mongoid_4?
|
26
|
-
Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new(
|
26
|
+
Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('4.0.0.alpha')
|
27
27
|
end
|
28
28
|
|
29
29
|
class << self
|
30
|
-
|
31
|
-
def define_field(name, type, options={})
|
30
|
+
def define_field(name, type, options = {})
|
32
31
|
@klass.field name, options.slice(:default).merge(type: type)
|
33
32
|
end
|
34
33
|
|
35
|
-
def define_callback(time, event, method_name, options={})
|
36
|
-
@klass.send
|
34
|
+
def define_callback(time, event, method_name, options = {})
|
35
|
+
@klass.send callback_name(time, event, options), method_name, options.slice(:if)
|
36
|
+
end
|
37
|
+
|
38
|
+
def callback_name(time, event, options)
|
39
|
+
if event == :commit
|
40
|
+
options[:on] == :create ? "#{time}_create" : "#{time}_save"
|
41
|
+
else
|
42
|
+
"#{time}_#{event}"
|
43
|
+
end
|
37
44
|
end
|
38
45
|
|
39
46
|
def credential_regex(credential)
|
40
|
-
return { :$regex =>
|
47
|
+
return { :$regex => /^#{Regexp.escape(credential)}$/i } if @klass.sorcery_config.downcase_username_before_authenticating
|
48
|
+
|
41
49
|
credential
|
42
50
|
end
|
43
51
|
|
@@ -73,7 +81,7 @@ module Sorcery
|
|
73
81
|
end
|
74
82
|
|
75
83
|
def find_by_username(username)
|
76
|
-
query = @klass.sorcery_config.username_attribute_names.map {|name| {name => username}}
|
84
|
+
query = @klass.sorcery_config.username_attribute_names.map { |name| { name => username } }
|
77
85
|
@klass.any_of(*query).first
|
78
86
|
end
|
79
87
|
|
@@ -87,9 +95,13 @@ module Sorcery
|
|
87
95
|
|
88
96
|
def get_current_users
|
89
97
|
config = @klass.sorcery_config
|
90
|
-
@klass.where(
|
91
|
-
|
92
|
-
.where(
|
98
|
+
@klass.where(
|
99
|
+
config.last_activity_at_attribute_name.ne => nil
|
100
|
+
).where(
|
101
|
+
"this.#{config.last_logout_at_attribute_name} == null || this.#{config.last_activity_at_attribute_name} > this.#{config.last_logout_at_attribute_name}"
|
102
|
+
).where(
|
103
|
+
config.last_activity_at_attribute_name.gt => config.activity_timeout.seconds.ago.utc
|
104
|
+
).order_by(%i[_id asc])
|
93
105
|
end
|
94
106
|
end
|
95
107
|
end
|
data/lib/sorcery/controller.rb
CHANGED
@@ -4,11 +4,14 @@ module Sorcery
|
|
4
4
|
klass.class_eval do
|
5
5
|
include InstanceMethods
|
6
6
|
Config.submodules.each do |mod|
|
7
|
+
# FIXME: Is there a cleaner way to handle missing submodules?
|
8
|
+
# rubocop:disable Lint/HandleExceptions
|
7
9
|
begin
|
8
10
|
include Submodules.const_get(mod.to_s.split('_').map(&:capitalize).join)
|
9
11
|
rescue NameError
|
10
12
|
# don't stop on a missing submodule.
|
11
13
|
end
|
14
|
+
# rubocop:enable Lint/HandleExceptions
|
12
15
|
end
|
13
16
|
end
|
14
17
|
Config.update!
|
@@ -20,10 +23,13 @@ module Sorcery
|
|
20
23
|
# Will trigger auto-login attempts via the call to logged_in?
|
21
24
|
# If all attempts to auto-login fail, the failure callback will be called.
|
22
25
|
def require_login
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
return if logged_in?
|
27
|
+
|
28
|
+
if Config.save_return_to_url && request.get? && !request.xhr? && !request.format.json?
|
29
|
+
session[:return_to_url] = request.url
|
26
30
|
end
|
31
|
+
|
32
|
+
send(Config.not_authenticated_action)
|
27
33
|
end
|
28
34
|
|
29
35
|
# Takes credentials and returns a user on successful authentication.
|
@@ -37,7 +43,10 @@ module Sorcery
|
|
37
43
|
|
38
44
|
yield(user, failure_reason) if block_given?
|
39
45
|
|
46
|
+
# FIXME: Does using `break` or `return nil` change functionality?
|
47
|
+
# rubocop:disable Lint/NonLocalExitFromIterator
|
40
48
|
return
|
49
|
+
# rubocop:enable Lint/NonLocalExitFromIterator
|
41
50
|
end
|
42
51
|
|
43
52
|
old_session = session.dup.to_hash
|
@@ -47,30 +56,26 @@ module Sorcery
|
|
47
56
|
end
|
48
57
|
form_authenticity_token
|
49
58
|
|
50
|
-
auto_login(user)
|
59
|
+
auto_login(user, credentials[2])
|
51
60
|
after_login!(user, credentials)
|
52
61
|
|
53
62
|
block_given? ? yield(current_user, nil) : current_user
|
54
63
|
end
|
55
64
|
end
|
56
65
|
|
57
|
-
# put this into the catch block to rescue undefined method `destroy_session'
|
58
|
-
# hotfix for https://github.com/NoamB/sorcery/issues/464
|
59
|
-
# can be removed when Rails 4.1 is out
|
60
66
|
def reset_sorcery_session
|
61
67
|
reset_session # protect from session fixation attacks
|
62
|
-
rescue NoMethodError
|
63
68
|
end
|
64
69
|
|
65
70
|
# Resets the session and runs hooks before and after.
|
66
71
|
def logout
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
return unless logged_in?
|
73
|
+
|
74
|
+
user = current_user
|
75
|
+
before_logout!
|
76
|
+
@current_user = nil
|
77
|
+
reset_sorcery_session
|
78
|
+
after_logout!(user)
|
74
79
|
end
|
75
80
|
|
76
81
|
def logged_in?
|
@@ -153,8 +158,14 @@ module Sorcery
|
|
153
158
|
Config.after_logout.each { |c| send(c, user) }
|
154
159
|
end
|
155
160
|
|
161
|
+
def after_remember_me!(user)
|
162
|
+
Config.after_remember_me.each { |c| send(c, user) }
|
163
|
+
end
|
164
|
+
|
156
165
|
def user_class
|
157
166
|
@user_class ||= Config.user_class.to_s.constantize
|
167
|
+
rescue NameError
|
168
|
+
raise ArgumentError, 'You have incorrectly defined user_class or have forgotten to define it in intitializer file (config.user_class = \'User\').'
|
158
169
|
end
|
159
170
|
end
|
160
171
|
end
|
@@ -18,6 +18,7 @@ module Sorcery
|
|
18
18
|
attr_accessor :after_failed_login
|
19
19
|
attr_accessor :before_logout
|
20
20
|
attr_accessor :after_logout
|
21
|
+
attr_accessor :after_remember_me
|
21
22
|
|
22
23
|
def init!
|
23
24
|
@defaults = {
|
@@ -29,6 +30,7 @@ module Sorcery
|
|
29
30
|
:@after_failed_login => [],
|
30
31
|
:@before_logout => [],
|
31
32
|
:@after_logout => [],
|
33
|
+
:@after_remember_me => [],
|
32
34
|
:@save_return_to_url => true,
|
33
35
|
:@cookie_domain => nil
|
34
36
|
}
|
@@ -30,9 +30,16 @@ module Sorcery
|
|
30
30
|
end
|
31
31
|
merge_activity_logging_defaults!
|
32
32
|
end
|
33
|
-
|
34
|
-
Config.after_login
|
35
|
-
|
33
|
+
# FIXME: There is likely a more elegant way to safeguard these callbacks.
|
34
|
+
unless Config.after_login.include?(:register_login_time_to_db)
|
35
|
+
Config.after_login << :register_login_time_to_db
|
36
|
+
end
|
37
|
+
unless Config.after_login.include?(:register_last_ip_address)
|
38
|
+
Config.after_login << :register_last_ip_address
|
39
|
+
end
|
40
|
+
unless Config.before_logout.include?(:register_logout_time_to_db)
|
41
|
+
Config.before_logout << :register_logout_time_to_db
|
42
|
+
end
|
36
43
|
base.after_action :register_last_activity_time_to_db
|
37
44
|
end
|
38
45
|
|
@@ -43,6 +50,7 @@ module Sorcery
|
|
43
50
|
# This runs as a hook just after a successful login.
|
44
51
|
def register_login_time_to_db(user, _credentials)
|
45
52
|
return unless Config.register_login_time
|
53
|
+
|
46
54
|
user.set_last_login_at(Time.now.in_time_zone)
|
47
55
|
end
|
48
56
|
|
@@ -50,6 +58,7 @@ module Sorcery
|
|
50
58
|
# This runs as a hook just before a logout.
|
51
59
|
def register_logout_time_to_db
|
52
60
|
return unless Config.register_logout_time
|
61
|
+
|
53
62
|
current_user.set_last_logout_at(Time.now.in_time_zone)
|
54
63
|
end
|
55
64
|
|
@@ -58,6 +67,7 @@ module Sorcery
|
|
58
67
|
def register_last_activity_time_to_db
|
59
68
|
return unless Config.register_last_activity_time
|
60
69
|
return unless logged_in?
|
70
|
+
|
61
71
|
current_user.set_last_activity_at(Time.now.in_time_zone)
|
62
72
|
end
|
63
73
|
|
@@ -65,6 +75,7 @@ module Sorcery
|
|
65
75
|
# This runs as a hook just after a successful login.
|
66
76
|
def register_last_ip_address(_user, _credentials)
|
67
77
|
return unless Config.register_last_ip_address
|
78
|
+
|
68
79
|
current_user.set_last_ip_address(request.remote_ip)
|
69
80
|
end
|
70
81
|
end
|
@@ -10,9 +10,13 @@ module Sorcery
|
|
10
10
|
module BruteForceProtection
|
11
11
|
def self.included(base)
|
12
12
|
base.send(:include, InstanceMethods)
|
13
|
-
|
14
|
-
Config.after_login
|
15
|
-
|
13
|
+
# FIXME: There is likely a more elegant way to safeguard these callbacks.
|
14
|
+
unless Config.after_login.include?(:reset_failed_logins_count!)
|
15
|
+
Config.after_login << :reset_failed_logins_count!
|
16
|
+
end
|
17
|
+
unless Config.after_failed_login.include?(:update_failed_logins_count!)
|
18
|
+
Config.after_failed_login << :update_failed_logins_count!
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
module InstanceMethods
|
@@ -23,6 +23,10 @@ module Sorcery
|
|
23
23
|
require 'sorcery/providers/slack'
|
24
24
|
require 'sorcery/providers/wechat'
|
25
25
|
require 'sorcery/providers/microsoft'
|
26
|
+
require 'sorcery/providers/instagram'
|
27
|
+
require 'sorcery/providers/auth0'
|
28
|
+
require 'sorcery/providers/line'
|
29
|
+
require 'sorcery/providers/discord'
|
26
30
|
|
27
31
|
Config.module_eval do
|
28
32
|
class << self
|
@@ -33,17 +37,17 @@ module Sorcery
|
|
33
37
|
@external_providers = providers
|
34
38
|
|
35
39
|
providers.each do |name|
|
36
|
-
class_eval <<-
|
40
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
37
41
|
def self.#{name}
|
38
|
-
@#{name} ||= Sorcery::Providers.const_get('#{name}'.to_s.
|
42
|
+
@#{name} ||= Sorcery::Providers.const_get('#{name}'.to_s.classify).new
|
39
43
|
end
|
40
|
-
|
44
|
+
RUBY
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
def merge_external_defaults!
|
45
49
|
@defaults.merge!(:@external_providers => [],
|
46
|
-
:@ca_file => File.join(
|
50
|
+
:@ca_file => File.join(__dir__, '../../protocols/certs/ca-bundle.crt'))
|
47
51
|
end
|
48
52
|
end
|
49
53
|
merge_external_defaults!
|
@@ -56,6 +60,7 @@ module Sorcery
|
|
56
60
|
# save the singleton ProviderClient instance into @provider
|
57
61
|
def sorcery_get_provider(provider_name)
|
58
62
|
return unless Config.external_providers.include?(provider_name.to_sym)
|
63
|
+
|
59
64
|
Config.send(provider_name.to_sym)
|
60
65
|
end
|
61
66
|
|
@@ -64,12 +69,11 @@ module Sorcery
|
|
64
69
|
def sorcery_login_url(provider_name, args = {})
|
65
70
|
@provider = sorcery_get_provider provider_name
|
66
71
|
sorcery_fixup_callback_url @provider
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
72
|
+
|
73
|
+
return nil unless @provider.respond_to?(:login_url) && @provider.has_callback?
|
74
|
+
|
75
|
+
@provider.state = args[:state]
|
76
|
+
@provider.login_url(params, session)
|
73
77
|
end
|
74
78
|
|
75
79
|
# get the user hash from a provider using information from the params and session.
|
@@ -88,6 +92,7 @@ module Sorcery
|
|
88
92
|
# cache them in instance variables.
|
89
93
|
@access_token ||= @provider.process_callback(params, session) # sends request to oauth agent to get the token
|
90
94
|
@user_hash ||= @provider.get_user_hash(@access_token) # uses the token to send another request to the oauth agent requesting user info
|
95
|
+
nil
|
91
96
|
end
|
92
97
|
|
93
98
|
# for backwards compatibility
|
@@ -98,14 +103,15 @@ module Sorcery
|
|
98
103
|
# this method should be somewhere else. It only does something once per application per provider.
|
99
104
|
def sorcery_fixup_callback_url(provider)
|
100
105
|
provider.original_callback_url ||= provider.callback_url
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
|
107
|
+
return unless provider.original_callback_url.present? && provider.original_callback_url[0] == '/'
|
108
|
+
|
109
|
+
uri = URI.parse(request.url.gsub(/\?.*$/, ''))
|
110
|
+
uri.path = ''
|
111
|
+
uri.query = nil
|
112
|
+
uri.scheme = 'https' if request.env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
113
|
+
host = uri.to_s
|
114
|
+
provider.callback_url = "#{host}#{@provider.original_callback_url}"
|
109
115
|
end
|
110
116
|
|
111
117
|
# sends user to authenticate at the provider's website.
|
@@ -118,26 +124,26 @@ module Sorcery
|
|
118
124
|
def login_from(provider_name, should_remember = false)
|
119
125
|
sorcery_fetch_user_hash provider_name
|
120
126
|
|
121
|
-
|
122
|
-
# we found the user.
|
123
|
-
# clear the session
|
124
|
-
return_to_url = session[:return_to_url]
|
125
|
-
reset_sorcery_session
|
126
|
-
session[:return_to_url] = return_to_url
|
127
|
+
return unless (user = user_class.load_from_provider(provider_name, @user_hash[:uid].to_s))
|
127
128
|
|
128
|
-
|
129
|
-
|
130
|
-
|
129
|
+
# we found the user.
|
130
|
+
# clear the session
|
131
|
+
return_to_url = session[:return_to_url]
|
132
|
+
reset_sorcery_session
|
133
|
+
session[:return_to_url] = return_to_url
|
131
134
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
+
# sign in the user
|
136
|
+
auto_login(user, should_remember)
|
137
|
+
after_login!(user)
|
138
|
+
|
139
|
+
# return the user
|
140
|
+
user
|
135
141
|
end
|
136
142
|
|
137
143
|
# If user is logged, he can add all available providers into his account
|
138
144
|
def add_provider_to_user(provider_name)
|
139
145
|
sorcery_fetch_user_hash provider_name
|
140
|
-
config = user_class.sorcery_config
|
146
|
+
# config = user_class.sorcery_config # TODO: Unused, remove?
|
141
147
|
|
142
148
|
current_user.add_provider_to_user(provider_name.to_s, @user_hash[:uid].to_s)
|
143
149
|
end
|
@@ -181,19 +187,28 @@ module Sorcery
|
|
181
187
|
#
|
182
188
|
def create_from(provider_name, &block)
|
183
189
|
sorcery_fetch_user_hash provider_name
|
184
|
-
config = user_class.sorcery_config
|
190
|
+
# config = user_class.sorcery_config # TODO: Unused, remove?
|
185
191
|
|
186
192
|
attrs = user_attrs(@provider.user_info_mapping, @user_hash)
|
187
193
|
@user = user_class.create_from_provider(provider_name, @user_hash[:uid], attrs, &block)
|
188
194
|
end
|
189
195
|
|
196
|
+
# follows the same patterns as create_from, but builds the user instead of creating
|
197
|
+
def build_from(provider_name, &block)
|
198
|
+
sorcery_fetch_user_hash provider_name
|
199
|
+
# config = user_class.sorcery_config # TODO: Unused, remove?
|
200
|
+
|
201
|
+
attrs = user_attrs(@provider.user_info_mapping, @user_hash)
|
202
|
+
@user = user_class.build_from_provider(attrs, &block)
|
203
|
+
end
|
204
|
+
|
190
205
|
def user_attrs(user_info_mapping, user_hash)
|
191
206
|
attrs = {}
|
192
207
|
user_info_mapping.each do |k, v|
|
193
208
|
if (varr = v.split('/')).size > 1
|
194
209
|
attribute_value = begin
|
195
210
|
varr.inject(user_hash[:user_info]) { |hash, value| hash[value] }
|
196
|
-
rescue
|
211
|
+
rescue StandardError
|
197
212
|
nil
|
198
213
|
end
|
199
214
|
attribute_value.nil? ? attrs : attrs.merge!(k => attribute_value)
|