sorcery 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sorcery might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +0 -26
- data/CHANGELOG.md +13 -0
- data/Gemfile +1 -1
- data/README.md +2 -1
- data/lib/generators/sorcery/templates/initializer.rb +85 -85
- 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 +3 -3
- data/lib/generators/sorcery/templates/migration/remember_me.rb +2 -2
- data/lib/generators/sorcery/templates/migration/reset_password.rb +4 -4
- data/lib/generators/sorcery/templates/migration/user_activation.rb +3 -3
- data/lib/sorcery/controller/submodules/activity_logging.rb +10 -3
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +7 -3
- data/lib/sorcery/controller/submodules/external.rb +1 -0
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +4 -1
- data/lib/sorcery/controller/submodules/remember_me.rb +7 -2
- data/lib/sorcery/controller/submodules/session_timeout.rb +7 -2
- data/lib/sorcery/crypto_providers/aes256.rb +1 -1
- data/lib/sorcery/crypto_providers/bcrypt.rb +6 -1
- data/lib/sorcery/model.rb +1 -0
- data/lib/sorcery/model/config.rb +5 -0
- data/lib/sorcery/model/submodules/magic_login.rb +7 -4
- data/lib/sorcery/model/submodules/reset_password.rb +6 -2
- data/lib/sorcery/providers/line.rb +47 -0
- data/lib/sorcery/providers/linkedin.rb +20 -36
- data/lib/sorcery/version.rb +1 -1
- data/spec/controllers/controller_oauth2_spec.rb +8 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +20 -0
- data/spec/rails_app/config/routes.rb +3 -0
- data/spec/shared_examples/user_reset_password_shared_examples.rb +18 -2
- data/spec/shared_examples/user_shared_examples.rb +63 -0
- data/spec/sorcery_crypto_providers_spec.rb +60 -0
- metadata +3 -5
- data/gemfiles/active_record_rails_40.gemfile +0 -6
- data/gemfiles/active_record_rails_41.gemfile +0 -6
- data/gemfiles/active_record_rails_42.gemfile +0 -6
@@ -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]
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class SorceryMagicLogin < <%= migration_class_name %>
|
2
2
|
def change
|
3
|
-
add_column :<%= model_class_name.tableize %>, :magic_login_token, :string, :
|
4
|
-
add_column :<%= model_class_name.tableize %>, :magic_login_token_expires_at, :datetime, :
|
5
|
-
add_column :<%= model_class_name.tableize %>, :magic_login_email_sent_at, :datetime, :
|
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
6
|
|
7
7
|
add_index :<%= model_class_name.tableize %>, :magic_login_token
|
8
8
|
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,9 +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, :
|
6
|
-
add_column :<%= model_class_name.tableize %>, :access_count_to_reset_password_page, :integer, :
|
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
|
7
7
|
|
8
8
|
add_index :<%= model_class_name.tableize %>, :reset_password_token
|
9
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
|
@@ -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
|
|
@@ -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
|
@@ -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
|
@@ -17,8 +17,13 @@ module Sorcery
|
|
17
17
|
end
|
18
18
|
merge_remember_me_defaults!
|
19
19
|
end
|
20
|
-
|
21
|
-
Config.
|
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
|
22
27
|
end
|
23
28
|
|
24
29
|
module InstanceMethods
|
@@ -23,8 +23,13 @@ module Sorcery
|
|
23
23
|
end
|
24
24
|
merge_session_timeout_defaults!
|
25
25
|
end
|
26
|
-
|
27
|
-
Config.
|
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
|
28
33
|
base.prepend_before_action :validate_session
|
29
34
|
end
|
30
35
|
|
@@ -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
|
@@ -77,12 +81,13 @@ module Sorcery
|
|
77
81
|
|
78
82
|
def reset!
|
79
83
|
@cost = 10
|
84
|
+
@pepper = ''
|
80
85
|
end
|
81
86
|
|
82
87
|
private
|
83
88
|
|
84
89
|
def join_tokens(tokens)
|
85
|
-
tokens.flatten.join
|
90
|
+
tokens.flatten.join.concat(pepper.to_s) # make sure to add pepper in case tokens have only one element
|
86
91
|
end
|
87
92
|
|
88
93
|
def new_from_hash(hash)
|
data/lib/sorcery/model.rb
CHANGED
@@ -142,6 +142,7 @@ module Sorcery
|
|
142
142
|
def set_encryption_attributes
|
143
143
|
@sorcery_config.encryption_provider.stretches = @sorcery_config.stretches if @sorcery_config.encryption_provider.respond_to?(:stretches) && @sorcery_config.stretches
|
144
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
|
145
146
|
end
|
146
147
|
|
147
148
|
def add_config_inheritance
|
data/lib/sorcery/model/config.rb
CHANGED
@@ -12,7 +12,11 @@ module Sorcery
|
|
12
12
|
attr_accessor :downcase_username_before_authenticating
|
13
13
|
# change default crypted_password attribute.
|
14
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
|
15
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.
|
16
20
|
attr_accessor :salt_join_token
|
17
21
|
# change default salt attribute.
|
18
22
|
attr_accessor :salt_attribute_name
|
@@ -57,6 +61,7 @@ module Sorcery
|
|
57
61
|
:@encryption_provider => CryptoProviders::BCrypt,
|
58
62
|
:@custom_encryption_provider => nil,
|
59
63
|
:@encryption_key => nil,
|
64
|
+
:@pepper => '',
|
60
65
|
:@salt_join_token => '',
|
61
66
|
:@salt_attribute_name => :salt,
|
62
67
|
:@stretches => nil,
|
@@ -52,10 +52,13 @@ module Sorcery
|
|
52
52
|
module ClassMethods
|
53
53
|
# Find user by token, also checks for expiration.
|
54
54
|
# Returns the user if token found and is valid.
|
55
|
-
def load_from_magic_login_token(token)
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
+
)
|
59
62
|
end
|
60
63
|
|
61
64
|
protected
|
@@ -124,10 +124,14 @@ module Sorcery
|
|
124
124
|
end
|
125
125
|
|
126
126
|
# Clears token and tries to update the new password for the user.
|
127
|
-
def change_password
|
127
|
+
def change_password(new_password, raise_on_failure: false)
|
128
128
|
clear_reset_password_token
|
129
129
|
send(:"#{sorcery_config.password_attribute_name}=", new_password)
|
130
|
-
sorcery_adapter.save raise_on_failure:
|
130
|
+
sorcery_adapter.save raise_on_failure: raise_on_failure
|
131
|
+
end
|
132
|
+
|
133
|
+
def change_password!(new_password)
|
134
|
+
change_password(new_password, raise_on_failure: true)
|
131
135
|
end
|
132
136
|
|
133
137
|
protected
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Providers
|
3
|
+
# This class adds support for OAuth with line.com.
|
4
|
+
#
|
5
|
+
# config.line.key = <key>
|
6
|
+
# config.line.secret = <secret>
|
7
|
+
# ...
|
8
|
+
#
|
9
|
+
class Line < Base
|
10
|
+
include Protocols::Oauth2
|
11
|
+
|
12
|
+
attr_accessor :token_url, :user_info_path, :auth_path
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
super
|
16
|
+
|
17
|
+
@site = 'https://access.line.me'
|
18
|
+
@user_info_path = 'https://api.line.me/v2/profile'
|
19
|
+
@token_url = 'https://api.line.me/v2/oauth/accessToken'
|
20
|
+
@auth_path = 'dialog/oauth/weblogin'
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_user_hash(access_token)
|
24
|
+
response = access_token.get(user_info_path)
|
25
|
+
auth_hash(access_token).tap do |h|
|
26
|
+
h[:user_info] = JSON.parse(response.body)
|
27
|
+
h[:uid] = h[:user_info]['userId'].to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# calculates and returns the url to which the user should be redirected,
|
32
|
+
# to get authenticated at the external provider's site.
|
33
|
+
def login_url(_params, _session)
|
34
|
+
@state = SecureRandom.hex(16)
|
35
|
+
authorize_url(authorize_url: auth_path)
|
36
|
+
end
|
37
|
+
# tries to login the user from access token
|
38
|
+
def process_callback(params, _session)
|
39
|
+
args = {}.tap do |a|
|
40
|
+
a[:code] = params[:code] if params[:code]
|
41
|
+
end
|
42
|
+
|
43
|
+
get_access_token(args, token_url: token_url, token_method: :post)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,65 +1,49 @@
|
|
1
1
|
module Sorcery
|
2
2
|
module Providers
|
3
|
-
# This class adds support for OAuth with
|
3
|
+
# This class adds support for OAuth with LinkedIn.
|
4
4
|
#
|
5
5
|
# config.linkedin.key = <key>
|
6
6
|
# config.linkedin.secret = <secret>
|
7
7
|
# ...
|
8
8
|
#
|
9
9
|
class Linkedin < Base
|
10
|
-
include Protocols::
|
10
|
+
include Protocols::Oauth2
|
11
11
|
|
12
|
-
attr_accessor :
|
13
|
-
:request_token_path, :user_info_fields, :user_info_path
|
12
|
+
attr_accessor :auth_url, :scope, :token_url, :user_info_url
|
14
13
|
|
15
14
|
def initialize
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@
|
23
|
-
|
24
|
-
|
25
|
-
# Override included get_consumer method to provide authorize_path
|
26
|
-
def get_consumer
|
27
|
-
# Add access permissions to request token path
|
28
|
-
@configuration[:request_token_path] += '?scope=' + access_permissions.join('+') unless access_permissions.blank? || @configuration[:request_token_path].include?('?scope=')
|
29
|
-
::OAuth::Consumer.new(@key, @secret, @configuration)
|
15
|
+
super
|
16
|
+
|
17
|
+
@site = 'https://api.linkedin.com'
|
18
|
+
@auth_url = '/oauth/v2/authorization'
|
19
|
+
@token_url = '/oauth/v2/accessToken'
|
20
|
+
@user_info_url = 'https://api.linkedin.com/v2/me'
|
21
|
+
@scope = 'r_liteprofile'
|
22
|
+
@state = SecureRandom.hex(16)
|
30
23
|
end
|
31
24
|
|
32
25
|
def get_user_hash(access_token)
|
33
|
-
|
34
|
-
info_fields = user_info_fields ? user_info_fields.reject { |n| n == 'id' } : []
|
35
|
-
fields = info_fields.any? ? 'id,' + info_fields.join(',') : 'id'
|
36
|
-
response = access_token.get("#{@user_info_path}:(#{fields})", 'x-li-format' => 'json')
|
26
|
+
response = access_token.get(user_info_url)
|
37
27
|
|
38
28
|
auth_hash(access_token).tap do |h|
|
39
29
|
h[:user_info] = JSON.parse(response.body)
|
40
|
-
h[:uid] = h[:user_info]['id']
|
30
|
+
h[:uid] = h[:user_info]['id']
|
41
31
|
end
|
42
32
|
end
|
43
33
|
|
44
34
|
# calculates and returns the url to which the user should be redirected,
|
45
35
|
# to get authenticated at the external provider's site.
|
46
|
-
def login_url(_params,
|
47
|
-
|
48
|
-
session[:request_token] = req_token.token
|
49
|
-
session[:request_token_secret] = req_token.secret
|
50
|
-
authorize_url(request_token: req_token.token, request_token_secret: req_token.secret)
|
36
|
+
def login_url(_params, _session)
|
37
|
+
authorize_url(authorize_url: auth_url)
|
51
38
|
end
|
52
39
|
|
53
40
|
# tries to login the user from access token
|
54
|
-
def process_callback(params,
|
55
|
-
args = {
|
56
|
-
|
57
|
-
|
58
|
-
request_token_secret: session[:request_token_secret]
|
59
|
-
}
|
41
|
+
def process_callback(params, _session)
|
42
|
+
args = {}.tap do |a|
|
43
|
+
a[:code] = params[:code] if params[:code]
|
44
|
+
end
|
60
45
|
|
61
|
-
args
|
62
|
-
get_access_token(args)
|
46
|
+
get_access_token(args, token_url: token_url, token_method: :post)
|
63
47
|
end
|
64
48
|
end
|
65
49
|
end
|
data/lib/sorcery/version.rb
CHANGED