sorcery 0.11.0 → 0.12.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/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +435 -0
- data/.travis.yml +15 -24
- data/CHANGELOG.md +19 -0
- data/Gemfile +1 -1
- data/README.md +25 -5
- data/lib/generators/sorcery/templates/initializer.rb +66 -3
- data/lib/generators/sorcery/templates/migration/magic_login.rb +9 -0
- data/lib/generators/sorcery/templates/migration/reset_password.rb +1 -0
- data/lib/sorcery.rb +2 -0
- data/lib/sorcery/adapters/active_record_adapter.rb +2 -1
- data/lib/sorcery/controller.rb +2 -0
- data/lib/sorcery/controller/submodules/external.rb +9 -0
- data/lib/sorcery/controller/submodules/session_timeout.rb +1 -1
- data/lib/sorcery/model/config.rb +4 -1
- data/lib/sorcery/model/submodules/external.rb +15 -0
- data/lib/sorcery/model/submodules/magic_login.rb +134 -0
- data/lib/sorcery/model/submodules/reset_password.rb +18 -0
- data/lib/sorcery/model/temporary_token.rb +3 -1
- data/lib/sorcery/providers/vk.rb +3 -2
- data/lib/sorcery/test_helpers/rails/request.rb +20 -0
- data/lib/sorcery/version.rb +1 -1
- data/sorcery.gemspec +6 -5
- data/spec/active_record/user_magic_login_spec.rb +15 -0
- data/spec/providers/vk_spec.rb +41 -0
- 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/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/shared_examples/user_magic_login_shared_examples.rb +150 -0
- data/spec/shared_examples/user_reset_password_shared_examples.rb +16 -0
- data/spec/sorcery_temporary_token_spec.rb +27 -0
- metadata +44 -13
@@ -16,6 +16,8 @@ module Sorcery
|
|
16
16
|
attr_accessor :reset_password_token_attribute_name
|
17
17
|
# Expires at attribute name.
|
18
18
|
attr_accessor :reset_password_token_expires_at_attribute_name
|
19
|
+
# Counter access to reset password page
|
20
|
+
attr_accessor :reset_password_page_access_count_attribute_name
|
19
21
|
# When was email sent, used for hammering protection.
|
20
22
|
attr_accessor :reset_password_email_sent_at_attribute_name
|
21
23
|
# Mailer class (needed)
|
@@ -34,6 +36,8 @@ module Sorcery
|
|
34
36
|
base.sorcery_config.instance_eval do
|
35
37
|
@defaults.merge!(:@reset_password_token_attribute_name => :reset_password_token,
|
36
38
|
:@reset_password_token_expires_at_attribute_name => :reset_password_token_expires_at,
|
39
|
+
:@reset_password_page_access_count_attribute_name =>
|
40
|
+
:access_count_to_reset_password_page,
|
37
41
|
:@reset_password_email_sent_at_attribute_name => :reset_password_email_sent_at,
|
38
42
|
:@reset_password_mailer => nil,
|
39
43
|
:@reset_password_mailer_disabled => false,
|
@@ -103,6 +107,20 @@ module Sorcery
|
|
103
107
|
end
|
104
108
|
mail
|
105
109
|
end
|
110
|
+
|
111
|
+
# Increment access_count_to_reset_password_page attribute.
|
112
|
+
# For example, access_count_to_reset_password_page attribute is over 1, which
|
113
|
+
# means the user doesn't have a right to access.
|
114
|
+
def increment_password_reset_page_access_counter
|
115
|
+
sorcery_adapter.increment(self.sorcery_config.reset_password_page_access_count_attribute_name)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Reset access_count_to_reset_password_page attribute into 0.
|
119
|
+
# This is expected to be used after sending an instruction email.
|
120
|
+
def reset_password_reset_page_access_counter
|
121
|
+
send(:"#{sorcery_config.reset_password_page_access_count_attribute_name}=", 0)
|
122
|
+
sorcery_adapter.save
|
123
|
+
end
|
106
124
|
|
107
125
|
# Clears token and tries to update the new password for the user.
|
108
126
|
def change_password!(new_password)
|
@@ -7,12 +7,14 @@ module Sorcery
|
|
7
7
|
# such as reseting password and activating the user by email.
|
8
8
|
module TemporaryToken
|
9
9
|
def self.included(base)
|
10
|
+
# FIXME: This may not be the ideal way of passing sorcery_config to generate_random_token.
|
11
|
+
@sorcery_config = base.sorcery_config
|
10
12
|
base.extend(ClassMethods)
|
11
13
|
end
|
12
14
|
|
13
15
|
# Random code, used for salt and temporary tokens.
|
14
16
|
def self.generate_random_token
|
15
|
-
SecureRandom.urlsafe_base64(
|
17
|
+
SecureRandom.urlsafe_base64(@sorcery_config.token_randomness).tr('lIO0', 'sxyz')
|
16
18
|
end
|
17
19
|
|
18
20
|
module ClassMethods
|
data/lib/sorcery/providers/vk.rb
CHANGED
@@ -9,7 +9,7 @@ module Sorcery
|
|
9
9
|
class Vk < Base
|
10
10
|
include Protocols::Oauth2
|
11
11
|
|
12
|
-
attr_accessor :auth_path, :token_path, :user_info_url, :scope
|
12
|
+
attr_accessor :auth_path, :token_path, :user_info_url, :scope, :api_version
|
13
13
|
|
14
14
|
def initialize
|
15
15
|
super
|
@@ -28,7 +28,8 @@ module Sorcery
|
|
28
28
|
access_token: access_token.token,
|
29
29
|
uids: access_token.params['user_id'],
|
30
30
|
fields: user_info_mapping.values.join(','),
|
31
|
-
scope: scope
|
31
|
+
scope: scope,
|
32
|
+
v: api_version.to_s
|
32
33
|
}
|
33
34
|
|
34
35
|
response = access_token.get(user_info_url, params: params)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module TestHelpers
|
3
|
+
module Rails
|
4
|
+
module Request
|
5
|
+
# Accepts arguments for user to login, the password, route to use and HTTP method
|
6
|
+
# Defaults - @user, 'secret', 'user_sessions_url' and http_method: POST
|
7
|
+
def login_user(user = nil, password = 'secret', route = nil, http_method = :post)
|
8
|
+
user ||= @user
|
9
|
+
route ||= user_sessions_url
|
10
|
+
|
11
|
+
username_attr = user.sorcery_config.username_attribute_names.first
|
12
|
+
username = user.send(username_attr)
|
13
|
+
password_attr = user.sorcery_config.password_attribute_name
|
14
|
+
|
15
|
+
send(http_method, route, params: { "#{username_attr}": username, "#{password_attr}": password })
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/sorcery/version.rb
CHANGED
data/sorcery.gemspec
CHANGED
@@ -18,16 +18,17 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.licenses = ['MIT']
|
20
20
|
|
21
|
-
s.required_ruby_version = '>= 2.
|
21
|
+
s.required_ruby_version = '>= 2.2.2'
|
22
22
|
|
23
23
|
s.add_dependency 'oauth', '~> 0.4', '>= 0.4.4'
|
24
24
|
s.add_dependency 'oauth2', '~> 1.0', '>= 0.8.0'
|
25
25
|
s.add_dependency 'bcrypt', '~> 3.1'
|
26
26
|
|
27
|
-
s.add_development_dependency 'yard', '~> 0.
|
27
|
+
s.add_development_dependency 'yard', '~> 0.9.0', '>= 0.9.12'
|
28
28
|
s.add_development_dependency 'timecop'
|
29
29
|
s.add_development_dependency 'simplecov', '>= 0.3.8'
|
30
|
-
s.add_development_dependency 'rspec-rails', '~> 3.
|
31
|
-
s.add_development_dependency 'test-unit', '~> 3.
|
32
|
-
s.add_development_dependency 'byebug', '~>
|
30
|
+
s.add_development_dependency 'rspec-rails', '~> 3.7.0'
|
31
|
+
s.add_development_dependency 'test-unit', '~> 3.2.0'
|
32
|
+
s.add_development_dependency 'byebug', '~> 10.0.0'
|
33
|
+
s.add_development_dependency 'webmock', '~> 3.3.0'
|
33
34
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shared_examples/user_magic_login_shared_examples'
|
3
|
+
|
4
|
+
describe User, 'with magic_login submodule', active_record: true do
|
5
|
+
before(:all) do
|
6
|
+
ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate/magic_login")
|
7
|
+
User.reset_column_information
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
ActiveRecord::Migrator.rollback("#{Rails.root}/db/migrate/magic_login")
|
12
|
+
end
|
13
|
+
|
14
|
+
it_behaves_like 'magic_login_model'
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sorcery/providers/base'
|
3
|
+
require 'sorcery/providers/vk'
|
4
|
+
require 'webmock/rspec'
|
5
|
+
|
6
|
+
describe Sorcery::Providers::Vk do
|
7
|
+
include WebMock::API
|
8
|
+
|
9
|
+
let(:provider) { Sorcery::Controller::Config.vk }
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
sorcery_reload!([:external])
|
13
|
+
sorcery_controller_property_set(:external_providers, [:vk])
|
14
|
+
sorcery_controller_external_property_set(:vk, :key, "KEY")
|
15
|
+
sorcery_controller_external_property_set(:vk, :secret, "SECRET")
|
16
|
+
end
|
17
|
+
|
18
|
+
def stub_vk_authorize
|
19
|
+
stub_request(:post, /https\:\/\/oauth\.vk\.com\/access_token/)
|
20
|
+
.to_return(
|
21
|
+
status: 200,
|
22
|
+
body: '{"access_token":"TOKEN","expires_in":86329,"user_id":1}',
|
23
|
+
headers: {'content-type' => 'application/json'})
|
24
|
+
end
|
25
|
+
|
26
|
+
context "getting user info hash" do
|
27
|
+
it "should provide VK API version" do
|
28
|
+
stub_vk_authorize
|
29
|
+
sorcery_controller_external_property_set(:vk, :api_version, '5.71')
|
30
|
+
|
31
|
+
get_user = stub_request(:get, "https://api.vk.com/method/getProfiles?access_token=TOKEN&fields=&scope=email&uids=1&v=5.71")
|
32
|
+
.to_return(body: '{"response":[{"id":1}]}')
|
33
|
+
|
34
|
+
token = provider.process_callback({ code: 'CODE' }, nil)
|
35
|
+
provider.get_user_hash(token)
|
36
|
+
|
37
|
+
expect(get_user).to have_been_requested
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -28,4 +28,11 @@ class SorceryMailer < ActionMailer::Base
|
|
28
28
|
mail(to: user.email,
|
29
29
|
subject: 'Your account has been locked due to many wrong logins')
|
30
30
|
end
|
31
|
+
|
32
|
+
def magic_login_email(user)
|
33
|
+
@user = user
|
34
|
+
@url = 'http://example.com/login'
|
35
|
+
mail(to: user.email,
|
36
|
+
subject: 'Magic Login')
|
37
|
+
end
|
31
38
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>Hello, <%= @user.username %></h1>
|
8
|
+
<p>
|
9
|
+
To login without a password, just follow this link: <%= @url %>.
|
10
|
+
</p>
|
11
|
+
<p>Have a great day!</p>
|
12
|
+
</body>
|
13
|
+
</html>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class AddMagicLoginToUsers < ActiveRecord::CompatibleLegacyMigration.migration_class
|
2
|
+
def self.up
|
3
|
+
add_column :users, :magic_login_token, :string, default: nil
|
4
|
+
add_column :users, :magic_login_token_expires_at, :datetime, default: nil
|
5
|
+
add_column :users, :magic_login_email_sent_at, :datetime, default: nil
|
6
|
+
|
7
|
+
add_index :users, :magic_login_token
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.down
|
11
|
+
remove_index :users, :magic_login_token
|
12
|
+
|
13
|
+
remove_column :users, :magic_login_token
|
14
|
+
remove_column :users, :magic_login_token_expires_at
|
15
|
+
remove_column :users, :magic_login_email_sent_at
|
16
|
+
end
|
17
|
+
end
|
@@ -3,11 +3,13 @@ class AddResetPasswordToUsers < ActiveRecord::CompatibleLegacyMigration.migratio
|
|
3
3
|
add_column :users, :reset_password_token, :string, default: nil
|
4
4
|
add_column :users, :reset_password_token_expires_at, :datetime, default: nil
|
5
5
|
add_column :users, :reset_password_email_sent_at, :datetime, default: nil
|
6
|
+
add_column :users, :access_count_to_reset_password_page, :integer, default: 0
|
6
7
|
end
|
7
8
|
|
8
9
|
def self.down
|
9
10
|
remove_column :users, :reset_password_email_sent_at
|
10
11
|
remove_column :users, :reset_password_token_expires_at
|
11
12
|
remove_column :users, :reset_password_token
|
13
|
+
remove_column :users, :access_count_to_reset_password_page
|
12
14
|
end
|
13
15
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
shared_examples_for "magic_login_model" do
|
2
|
+
let(:user) {create_new_user}
|
3
|
+
before(:each) do
|
4
|
+
User.sorcery_adapter.delete_all
|
5
|
+
end
|
6
|
+
|
7
|
+
context 'loaded plugin configuration' do
|
8
|
+
let(:config) {User.sorcery_config}
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
sorcery_reload!([:magic_login])
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:each) do
|
15
|
+
User.sorcery_config.reset!
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "enables configuration options" do
|
19
|
+
it do
|
20
|
+
sorcery_model_property_set(:magic_login_token_attribute_name, :test_magic_login_token)
|
21
|
+
expect(config.magic_login_token_attribute_name).to eq :test_magic_login_token
|
22
|
+
end
|
23
|
+
|
24
|
+
it do
|
25
|
+
sorcery_model_property_set(:magic_login_token_expires_at_attribute_name, :test_magic_login_token_expires_at)
|
26
|
+
expect(config.magic_login_token_expires_at_attribute_name).to eq :test_magic_login_token_expires_at
|
27
|
+
end
|
28
|
+
|
29
|
+
it do
|
30
|
+
sorcery_model_property_set(:magic_login_email_sent_at_attribute_name, :test_magic_login_email_sent_at)
|
31
|
+
expect(config.magic_login_email_sent_at_attribute_name).to eq :test_magic_login_email_sent_at
|
32
|
+
end
|
33
|
+
|
34
|
+
it do
|
35
|
+
TestMailerClass = Class.new # need a mailer class to test
|
36
|
+
sorcery_model_property_set(:magic_login_mailer_class, TestMailerClass)
|
37
|
+
expect(config.magic_login_mailer_class).to eq TestMailerClass
|
38
|
+
end
|
39
|
+
|
40
|
+
it do
|
41
|
+
sorcery_model_property_set(:magic_login_mailer_disabled, false)
|
42
|
+
expect(config.magic_login_mailer_disabled).to eq false
|
43
|
+
end
|
44
|
+
|
45
|
+
it do
|
46
|
+
sorcery_model_property_set(:magic_login_email_method_name, :test_magic_login_email)
|
47
|
+
expect(config.magic_login_email_method_name).to eq :test_magic_login_email
|
48
|
+
end
|
49
|
+
|
50
|
+
it do
|
51
|
+
sorcery_model_property_set(:magic_login_expiration_period, 100000000)
|
52
|
+
expect(config.magic_login_expiration_period).to eq 100000000
|
53
|
+
end
|
54
|
+
|
55
|
+
it do
|
56
|
+
sorcery_model_property_set(:magic_login_time_between_emails, 100000000)
|
57
|
+
expect(config.magic_login_time_between_emails).to eq 100000000
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#generate_magic_login_token!" do
|
62
|
+
context "magic_login_token is nil" do
|
63
|
+
it "magic_login_token_expires_at and magic_login_email_sent_at aren't nil " do
|
64
|
+
user.generate_magic_login_token!
|
65
|
+
expect(user.magic_login_token_expires_at).not_to be_nil
|
66
|
+
expect(user.magic_login_email_sent_at).not_to be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "magic_login_token is different from the one before" do
|
70
|
+
token_before = user.magic_login_token
|
71
|
+
user.generate_magic_login_token!
|
72
|
+
expect(user.magic_login_token).not_to eq token_before
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "magic_login_token is not nil" do
|
77
|
+
it "changes `user.magic_login_token`" do
|
78
|
+
token_before = user.magic_login_token
|
79
|
+
user.generate_magic_login_token!
|
80
|
+
expect(user.magic_login_token).not_to eq token_before
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#deliver_magic_login_instructions!" do
|
86
|
+
context "success" do
|
87
|
+
before do
|
88
|
+
sorcery_model_property_set(:magic_login_time_between_emails, 30*60)
|
89
|
+
sorcery_model_property_set(:magic_login_mailer_disabled, false)
|
90
|
+
Timecop.travel(10.days.ago) do
|
91
|
+
user.send(:"#{config.magic_login_email_sent_at_attribute_name}=", DateTime.now)
|
92
|
+
end
|
93
|
+
sorcery_model_property_set(:magic_login_mailer_class, ::SorceryMailer)
|
94
|
+
end
|
95
|
+
|
96
|
+
it do
|
97
|
+
user.deliver_magic_login_instructions!
|
98
|
+
expect(ActionMailer::Base.deliveries.size).to eq 1
|
99
|
+
end
|
100
|
+
|
101
|
+
it do
|
102
|
+
expect(user.deliver_magic_login_instructions!).to eq true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "failure" do
|
107
|
+
context "magic_login_time_between_emails is nil" do
|
108
|
+
it "returns false" do
|
109
|
+
sorcery_model_property_set(:magic_login_time_between_emails, nil)
|
110
|
+
expect(user.deliver_magic_login_instructions!).to eq false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "magic_login_email_sent_at is nil" do
|
115
|
+
it "returns false" do
|
116
|
+
user.send(:"#{config.magic_login_email_sent_at_attribute_name}=", nil)
|
117
|
+
expect(user.deliver_magic_login_instructions!).to eq false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "now is before magic_login_email_sent_at plus the interval" do
|
122
|
+
it "returns false" do
|
123
|
+
user.send(:"#{config.magic_login_email_sent_at_attribute_name}=", DateTime.now)
|
124
|
+
sorcery_model_property_set(:magic_login_time_between_emails, 30*60)
|
125
|
+
expect(user.deliver_magic_login_instructions!).to eq false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "magic_login_mailer_disabled is true" do
|
130
|
+
it "returns false" do
|
131
|
+
sorcery_model_property_set(:magic_login_mailer_disabled, true)
|
132
|
+
expect(user.deliver_magic_login_instructions!).to eq false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#clear_magic_login_token!" do
|
139
|
+
it "makes magic_login_token_attribute_name and magic_login_token_expires_at_attribute_name nil" do
|
140
|
+
user.magic_login_token = "test_token"
|
141
|
+
user.magic_login_token_expires_at = Time.now
|
142
|
+
|
143
|
+
user.clear_magic_login_token!
|
144
|
+
|
145
|
+
expect(user.magic_login_token).to eq nil
|
146
|
+
expect(user.magic_login_token_expires_at).to eq nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -214,6 +214,22 @@ shared_examples_for 'rails_3_reset_password_model' do
|
|
214
214
|
expect(user.reset_password_token).not_to eq old_password_code
|
215
215
|
end
|
216
216
|
|
217
|
+
describe '#increment_password_reset_page_access_counter' do
|
218
|
+
it 'increments reset_password_page_access_count_attribute_name' do
|
219
|
+
expected_count = user.access_count_to_reset_password_page + 1
|
220
|
+
user.increment_password_reset_page_access_counter
|
221
|
+
expect(user.access_count_to_reset_password_page).to eq expected_count
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe '#reset_password_reset_page_access_counter' do
|
226
|
+
it 'reset reset_password_page_access_count_attribute_name into 0' do
|
227
|
+
user.update(access_count_to_reset_password_page: 10)
|
228
|
+
user.reset_password_reset_page_access_counter
|
229
|
+
expect(user.access_count_to_reset_password_page).to eq 0
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
217
233
|
context 'mailer is enabled' do
|
218
234
|
it 'sends an email on reset' do
|
219
235
|
old_size = ActionMailer::Base.deliveries.size
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sorcery::Model::TemporaryToken do
|
4
|
+
describe '.generate_random_token' do
|
5
|
+
before do
|
6
|
+
sorcery_reload!
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { Sorcery::Model::TemporaryToken.generate_random_token.length }
|
10
|
+
|
11
|
+
context 'token_randomness is 3' do
|
12
|
+
before do
|
13
|
+
sorcery_model_property_set(:token_randomness, 3)
|
14
|
+
end
|
15
|
+
|
16
|
+
it { is_expected.to eq 4 }
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'token_randomness is 15' do
|
20
|
+
before do
|
21
|
+
sorcery_model_property_set(:token_randomness, 15)
|
22
|
+
end
|
23
|
+
|
24
|
+
it { is_expected.to eq 20 }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|