two_factor_authentication 1.1.5 → 2.0.0
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 +4 -4
- data/.codeclimate.yml +21 -0
- data/.rubocop.yml +295 -0
- data/.travis.yml +4 -5
- data/CHANGELOG.md +24 -14
- data/README.md +57 -65
- data/app/controllers/devise/two_factor_authentication_controller.rb +28 -12
- data/app/views/devise/two_factor_authentication/show.html.erb +10 -1
- data/config/locales/en.yml +1 -0
- data/config/locales/es.yml +8 -0
- data/config/locales/fr.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/lib/generators/active_record/templates/migration.rb +3 -0
- data/lib/two_factor_authentication.rb +9 -0
- data/lib/two_factor_authentication/controllers/helpers.rb +1 -1
- data/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +4 -23
- data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +68 -19
- data/lib/two_factor_authentication/routes.rb +3 -1
- data/lib/two_factor_authentication/schema.rb +12 -0
- data/lib/two_factor_authentication/version.rb +1 -1
- data/spec/controllers/two_factor_authentication_controller_spec.rb +2 -2
- data/spec/features/two_factor_authenticatable_spec.rb +36 -73
- data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +137 -80
- data/spec/rails_app/app/controllers/home_controller.rb +1 -1
- data/spec/rails_app/app/models/admin.rb +6 -0
- data/spec/rails_app/app/models/encrypted_user.rb +2 -1
- data/spec/rails_app/app/models/guest_user.rb +8 -1
- data/spec/rails_app/app/models/user.rb +2 -2
- data/spec/rails_app/config/initializers/devise.rb +2 -2
- data/spec/rails_app/config/routes.rb +1 -0
- data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +1 -1
- data/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +42 -0
- data/spec/rails_app/db/schema.rb +19 -1
- data/spec/support/authenticated_model_helper.rb +22 -15
- data/spec/support/controller_helper.rb +1 -1
- data/spec/support/totp_helper.rb +11 -0
- data/two_factor_authentication.gemspec +1 -1
- metadata +74 -7
@@ -1,6 +1,8 @@
|
|
1
|
+
require 'devise/version'
|
2
|
+
|
1
3
|
class Devise::TwoFactorAuthenticationController < DeviseController
|
2
|
-
|
3
|
-
|
4
|
+
prepend_before_action :authenticate_scope!
|
5
|
+
before_action :prepare_and_validate, :handle_two_factor_authentication
|
4
6
|
|
5
7
|
def show
|
6
8
|
end
|
@@ -15,24 +17,39 @@ class Devise::TwoFactorAuthenticationController < DeviseController
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
20
|
+
def resend_code
|
21
|
+
resource.send_new_otp
|
22
|
+
redirect_to send("#{resource_name}_two_factor_authentication_path"), notice: I18n.t('devise.two_factor_authentication.code_has_been_sent')
|
23
|
+
end
|
24
|
+
|
18
25
|
private
|
19
26
|
|
20
27
|
def after_two_factor_success_for(resource)
|
28
|
+
set_remember_two_factor_cookie(resource)
|
29
|
+
|
30
|
+
warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false
|
31
|
+
# For compatability with devise versions below v4.2.0
|
32
|
+
# https://github.com/plataformatec/devise/commit/2044fffa25d781fcbaf090e7728b48b65c854ccb
|
33
|
+
if respond_to?(:bypass_sign_in)
|
34
|
+
bypass_sign_in(resource, scope: resource_name)
|
35
|
+
else
|
36
|
+
sign_in(resource_name, resource, bypass: true)
|
37
|
+
end
|
38
|
+
set_flash_message :notice, :success
|
39
|
+
resource.update_attribute(:second_factor_attempts_count, 0)
|
40
|
+
|
41
|
+
redirect_to after_two_factor_success_path_for(resource)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_remember_two_factor_cookie(resource)
|
21
45
|
expires_seconds = resource.class.remember_otp_session_for_seconds
|
22
46
|
|
23
47
|
if expires_seconds && expires_seconds > 0
|
24
48
|
cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] = {
|
25
|
-
value: "#{resource.class}-#{resource.
|
49
|
+
value: "#{resource.class}-#{resource.public_send(Devise.second_factor_resource_id)}",
|
26
50
|
expires: expires_seconds.from_now
|
27
51
|
}
|
28
52
|
end
|
29
|
-
|
30
|
-
warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false
|
31
|
-
sign_in resource_name, resource, :bypass => true
|
32
|
-
set_flash_message :notice, :success
|
33
|
-
resource.update_attribute(:second_factor_attempts_count, 0)
|
34
|
-
|
35
|
-
redirect_to after_two_factor_success_path_for(resource)
|
36
53
|
end
|
37
54
|
|
38
55
|
def after_two_factor_success_path_for(resource)
|
@@ -42,12 +59,11 @@ class Devise::TwoFactorAuthenticationController < DeviseController
|
|
42
59
|
def after_two_factor_fail_for(resource)
|
43
60
|
resource.second_factor_attempts_count += 1
|
44
61
|
resource.save
|
45
|
-
|
62
|
+
set_flash_message :alert, :attempt_failed, now: true
|
46
63
|
|
47
64
|
if resource.max_login_attempts?
|
48
65
|
sign_out(resource)
|
49
66
|
render :max_login_attempts_reached
|
50
|
-
|
51
67
|
else
|
52
68
|
render :show
|
53
69
|
end
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
<% if resource.direct_otp %>
|
2
|
+
<h2>Enter the code that was sent to you</h2>
|
3
|
+
<% else %>
|
4
|
+
<h2>Enter the code from your authenticator app</h2>
|
5
|
+
<% end %>
|
2
6
|
|
3
7
|
<p><%= flash[:notice] %></p>
|
4
8
|
|
@@ -7,4 +11,9 @@
|
|
7
11
|
<%= submit_tag "Submit" %>
|
8
12
|
<% end %>
|
9
13
|
|
14
|
+
<% if resource.direct_otp %>
|
15
|
+
<%= link_to "Resend Code", resend_code_user_two_factor_authentication_path, action: :get %>
|
16
|
+
<% else %>
|
17
|
+
<%= link_to "Send me a code instead", resend_code_user_two_factor_authentication_path, action: :get %>
|
18
|
+
<% end %>
|
10
19
|
<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>
|
data/config/locales/en.yml
CHANGED
@@ -0,0 +1,8 @@
|
|
1
|
+
es:
|
2
|
+
devise:
|
3
|
+
two_factor_authentication:
|
4
|
+
success: "Autenticación multi-factor realizada exitosamente."
|
5
|
+
attempt_failed: "La autenticación ha fallado."
|
6
|
+
max_login_attempts_reached: "Has llegado al límite de intentos fallidos, acceso denegado."
|
7
|
+
contact_administrator: "Contacte a su administrador de sistema."
|
8
|
+
code_has_been_sent: "El código de autenticación ha sido enviado."
|
data/config/locales/fr.yml
CHANGED
data/config/locales/ru.yml
CHANGED
@@ -4,6 +4,9 @@ class TwoFactorAuthenticationAddTo<%= table_name.camelize %> < ActiveRecord::Mig
|
|
4
4
|
add_column :<%= table_name %>, :encrypted_otp_secret_key, :string
|
5
5
|
add_column :<%= table_name %>, :encrypted_otp_secret_key_iv, :string
|
6
6
|
add_column :<%= table_name %>, :encrypted_otp_secret_key_salt, :string
|
7
|
+
add_column :<%= table_name %>, :direct_otp, :string
|
8
|
+
add_column :<%= table_name %>, :direct_otp_sent_at, :datetime
|
9
|
+
add_column :<%= table_name %>, :totp_timestamp, :timestamp
|
7
10
|
|
8
11
|
add_index :<%= table_name %>, :encrypted_otp_secret_key, unique: true
|
9
12
|
end
|
@@ -16,11 +16,20 @@ module Devise
|
|
16
16
|
mattr_accessor :otp_length
|
17
17
|
@@otp_length = 6
|
18
18
|
|
19
|
+
mattr_accessor :direct_otp_length
|
20
|
+
@@direct_otp_length = 6
|
21
|
+
|
22
|
+
mattr_accessor :direct_otp_valid_for
|
23
|
+
@@direct_otp_valid_for = 5.minutes
|
24
|
+
|
19
25
|
mattr_accessor :remember_otp_session_for_seconds
|
20
26
|
@@remember_otp_session_for_seconds = 0
|
21
27
|
|
22
28
|
mattr_accessor :otp_secret_encryption_key
|
23
29
|
@@otp_secret_encryption_key = ''
|
30
|
+
|
31
|
+
mattr_accessor :second_factor_resource_id
|
32
|
+
@@second_factor_resource_id = 'id'
|
24
33
|
end
|
25
34
|
|
26
35
|
module TwoFactorAuthentication
|
@@ -1,32 +1,13 @@
|
|
1
1
|
Warden::Manager.after_authentication do |user, auth, options|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
actual_cookie_value = auth.env["action_dispatch.cookies"].signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME]
|
6
|
-
if actual_cookie_value.nil?
|
7
|
-
bypass_by_cookie = false
|
8
|
-
else
|
2
|
+
if auth.env["action_dispatch.cookies"]
|
3
|
+
expected_cookie_value = "#{user.class}-#{user.public_send(Devise.second_factor_resource_id)}"
|
4
|
+
actual_cookie_value = auth.env["action_dispatch.cookies"].signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME]
|
9
5
|
bypass_by_cookie = actual_cookie_value == expected_cookie_value
|
10
6
|
end
|
11
7
|
|
12
8
|
if user.respond_to?(:need_two_factor_authentication?) && !bypass_by_cookie
|
13
9
|
if auth.session(options[:scope])[TwoFactorAuthentication::NEED_AUTHENTICATION] = user.need_two_factor_authentication?(auth.request)
|
14
|
-
user.
|
10
|
+
user.send_new_otp unless user.totp_enabled?
|
15
11
|
end
|
16
12
|
end
|
17
13
|
end
|
18
|
-
|
19
|
-
Warden::Manager.before_logout do |user, _auth, _options|
|
20
|
-
reset_otp_state_for(user)
|
21
|
-
end
|
22
|
-
|
23
|
-
def reset_otp_state_for(user)
|
24
|
-
klass_string = "#{user.class}OtpSender"
|
25
|
-
return unless Object.const_defined?(klass_string)
|
26
|
-
|
27
|
-
klass = Object.const_get(klass_string)
|
28
|
-
|
29
|
-
otp_sender = klass.new(user)
|
30
|
-
|
31
|
-
otp_sender.reset_otp_state if otp_sender.respond_to?(:reset_otp_state)
|
32
|
-
end
|
@@ -11,42 +11,57 @@ module Devise
|
|
11
11
|
def has_one_time_password(options = {})
|
12
12
|
include InstanceMethodsOnActivation
|
13
13
|
include EncryptionInstanceMethods if options[:encrypted] == true
|
14
|
-
|
15
|
-
before_create { populate_otp_column }
|
16
14
|
end
|
17
15
|
|
18
16
|
::Devise::Models.config(
|
19
17
|
self, :max_login_attempts, :allowed_otp_drift_seconds, :otp_length,
|
20
|
-
:remember_otp_session_for_seconds, :otp_secret_encryption_key
|
18
|
+
:remember_otp_session_for_seconds, :otp_secret_encryption_key,
|
19
|
+
:direct_otp_length, :direct_otp_valid_for, :totp_timestamp)
|
21
20
|
end
|
22
21
|
|
23
22
|
module InstanceMethodsOnActivation
|
24
23
|
def authenticate_otp(code, options = {})
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
return true if direct_otp && authenticate_direct_otp(code)
|
25
|
+
return true if totp_enabled? && authenticate_totp(code, options)
|
26
|
+
false
|
27
|
+
end
|
29
28
|
|
30
|
-
|
29
|
+
def authenticate_direct_otp(code)
|
30
|
+
return false if direct_otp.nil? || direct_otp != code || direct_otp_expired?
|
31
|
+
clear_direct_otp
|
32
|
+
true
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
def authenticate_totp(code, options = {})
|
36
|
+
totp_secret = options[:otp_secret_key] || otp_secret_key
|
37
|
+
digits = options[:otp_length] || self.class.otp_length
|
38
|
+
drift = options[:drift] || self.class.allowed_otp_drift_seconds
|
39
|
+
raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil?
|
40
|
+
totp = ROTP::TOTP.new(totp_secret, digits: digits)
|
41
|
+
new_timestamp = totp.verify_with_drift_and_prior(code, drift, totp_timestamp)
|
42
|
+
return false unless new_timestamp
|
43
|
+
self.totp_timestamp = new_timestamp
|
44
|
+
true
|
38
45
|
end
|
39
46
|
|
40
47
|
def provisioning_uri(account = nil, options = {})
|
41
|
-
|
42
|
-
|
48
|
+
totp_secret = options[:otp_secret_key] || otp_secret_key
|
49
|
+
options[:digits] ||= options[:otp_length] || self.class.otp_length
|
50
|
+
raise "provisioning_uri called with no otp_secret_key set" if totp_secret.nil?
|
51
|
+
account ||= email if respond_to?(:email)
|
52
|
+
ROTP::TOTP.new(totp_secret, options).provisioning_uri(account)
|
43
53
|
end
|
44
54
|
|
45
55
|
def need_two_factor_authentication?(request)
|
46
56
|
true
|
47
57
|
end
|
48
58
|
|
49
|
-
def
|
59
|
+
def send_new_otp(options = {})
|
60
|
+
create_direct_otp options
|
61
|
+
send_two_factor_authentication_code(direct_otp)
|
62
|
+
end
|
63
|
+
|
64
|
+
def send_two_factor_authentication_code(code)
|
50
65
|
raise NotImplementedError.new("No default implementation - please define in your class.")
|
51
66
|
end
|
52
67
|
|
@@ -58,8 +73,41 @@ module Devise
|
|
58
73
|
self.class.max_login_attempts
|
59
74
|
end
|
60
75
|
|
61
|
-
def
|
62
|
-
|
76
|
+
def totp_enabled?
|
77
|
+
respond_to?(:otp_secret_key) && !otp_secret_key.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def confirm_totp_secret(secret, code, options = {})
|
81
|
+
return false unless authenticate_totp(code, {otp_secret_key: secret})
|
82
|
+
self.otp_secret_key = secret
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_totp_secret
|
87
|
+
ROTP::Base32.random_base32
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_direct_otp(options = {})
|
91
|
+
# Create a new random OTP and store it in the database
|
92
|
+
digits = options[:length] || self.class.direct_otp_length || 6
|
93
|
+
update_attributes(
|
94
|
+
direct_otp: random_base10(digits),
|
95
|
+
direct_otp_sent_at: Time.now.utc
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def random_base10(digits)
|
102
|
+
SecureRandom.random_number(10**digits).to_s.rjust(digits, '0')
|
103
|
+
end
|
104
|
+
|
105
|
+
def direct_otp_expired?
|
106
|
+
Time.now.utc > direct_otp_sent_at + self.class.direct_otp_valid_for
|
107
|
+
end
|
108
|
+
|
109
|
+
def clear_direct_otp
|
110
|
+
update_attributes(direct_otp: nil, direct_otp_sent_at: nil)
|
63
111
|
end
|
64
112
|
end
|
65
113
|
|
@@ -105,7 +153,8 @@ module Devise
|
|
105
153
|
value: value,
|
106
154
|
key: Devise.otp_secret_encryption_key,
|
107
155
|
iv: iv_for_attribute,
|
108
|
-
salt: salt_for_attribute
|
156
|
+
salt: salt_for_attribute,
|
157
|
+
algorithm: 'aes-256-cbc'
|
109
158
|
}
|
110
159
|
end
|
111
160
|
|
@@ -3,7 +3,9 @@ module ActionDispatch::Routing
|
|
3
3
|
protected
|
4
4
|
|
5
5
|
def devise_two_factor_authentication(mapping, controllers)
|
6
|
-
resource :two_factor_authentication, :only => [:show, :update], :path => mapping.path_names[:two_factor_authentication], :controller => controllers[:two_factor_authentication]
|
6
|
+
resource :two_factor_authentication, :only => [:show, :update, :resend_code], :path => mapping.path_names[:two_factor_authentication], :controller => controllers[:two_factor_authentication] do
|
7
|
+
collection { get "resend_code" }
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -15,5 +15,17 @@ module TwoFactorAuthentication
|
|
15
15
|
def encrypted_otp_secret_key_salt
|
16
16
|
apply_devise_schema :encrypted_otp_secret_key_salt, String
|
17
17
|
end
|
18
|
+
|
19
|
+
def direct_otp
|
20
|
+
apply_devise_schema :direct_otp, String
|
21
|
+
end
|
22
|
+
|
23
|
+
def direct_otp_sent_at
|
24
|
+
apply_devise_schema :direct_otp_sent_at, DateTime
|
25
|
+
end
|
26
|
+
|
27
|
+
def totp_timestamp
|
28
|
+
apply_devise_schema :totp_timestamp, Timestamp
|
29
|
+
end
|
18
30
|
end
|
19
31
|
end
|
@@ -8,8 +8,8 @@ describe Devise::TwoFactorAuthenticationController, type: :controller do
|
|
8
8
|
|
9
9
|
context 'after user enters valid OTP code' do
|
10
10
|
it 'returns true' do
|
11
|
-
|
12
|
-
|
11
|
+
controller.current_user.send_new_otp
|
12
|
+
post :update, code: controller.current_user.direct_otp
|
13
13
|
expect(subject.is_fully_authenticated?).to eq true
|
14
14
|
end
|
15
15
|
end
|
@@ -5,6 +5,7 @@ feature "User of two factor authentication" do
|
|
5
5
|
context 'sending two factor authentication code via SMS' do
|
6
6
|
shared_examples 'sends and authenticates code' do |user, type|
|
7
7
|
before do
|
8
|
+
user.reload
|
8
9
|
if type == 'encrypted'
|
9
10
|
allow(User).to receive(:has_one_time_password).with(encrypted: true)
|
10
11
|
end
|
@@ -18,12 +19,12 @@ feature "User of two factor authentication" do
|
|
18
19
|
visit new_user_session_path
|
19
20
|
complete_sign_in_form_for(user)
|
20
21
|
|
21
|
-
expect(page).to have_content 'Enter
|
22
|
+
expect(page).to have_content 'Enter the code that was sent to you'
|
22
23
|
|
23
24
|
expect(SMSProvider.messages.size).to eq(1)
|
24
25
|
message = SMSProvider.last_message
|
25
26
|
expect(message.to).to eq(user.phone_number)
|
26
|
-
expect(message.body).to eq(user.
|
27
|
+
expect(message.body).to eq(user.reload.direct_otp)
|
27
28
|
end
|
28
29
|
|
29
30
|
it 'authenticates a valid OTP code' do
|
@@ -32,7 +33,7 @@ feature "User of two factor authentication" do
|
|
32
33
|
|
33
34
|
expect(page).to have_content('You are signed in as Marissa')
|
34
35
|
|
35
|
-
fill_in 'code', with:
|
36
|
+
fill_in 'code', with: SMSProvider.last_message.body
|
36
37
|
click_button 'Submit'
|
37
38
|
|
38
39
|
within('.flash.notice') do
|
@@ -66,7 +67,7 @@ feature "User of two factor authentication" do
|
|
66
67
|
|
67
68
|
expect(page).to_not have_content("Your Personal Dashboard")
|
68
69
|
|
69
|
-
fill_in "code", with:
|
70
|
+
fill_in "code", with: SMSProvider.last_message.body
|
70
71
|
click_button "Submit"
|
71
72
|
|
72
73
|
expect(page).to have_content("Your Personal Dashboard")
|
@@ -84,7 +85,7 @@ feature "User of two factor authentication" do
|
|
84
85
|
fill_in "code", with: "incorrect#{rand(100)}"
|
85
86
|
click_button "Submit"
|
86
87
|
|
87
|
-
within(".flash.
|
88
|
+
within(".flash.alert") do
|
88
89
|
expect(page).to have_content("Attempt failed")
|
89
90
|
end
|
90
91
|
end
|
@@ -113,9 +114,7 @@ feature "User of two factor authentication" do
|
|
113
114
|
end
|
114
115
|
|
115
116
|
scenario "doesn't require TFA code again within 30 days" do
|
116
|
-
|
117
|
-
fill_in "code", with: user.otp_code
|
118
|
-
click_button "Submit"
|
117
|
+
sms_sign_in
|
119
118
|
|
120
119
|
logout
|
121
120
|
|
@@ -126,9 +125,7 @@ feature "User of two factor authentication" do
|
|
126
125
|
end
|
127
126
|
|
128
127
|
scenario "requires TFA code again after 30 days" do
|
129
|
-
|
130
|
-
fill_in "code", with: user.otp_code
|
131
|
-
click_button "Submit"
|
128
|
+
sms_sign_in
|
132
129
|
|
133
130
|
logout
|
134
131
|
|
@@ -136,13 +133,11 @@ feature "User of two factor authentication" do
|
|
136
133
|
login_as user
|
137
134
|
visit dashboard_path
|
138
135
|
expect(page).to have_content("You are signed in as Marissa")
|
139
|
-
expect(page).to have_content("Enter
|
136
|
+
expect(page).to have_content("Enter the code that was sent to you")
|
140
137
|
end
|
141
138
|
|
142
139
|
scenario 'TFA should be different for different users' do
|
143
|
-
|
144
|
-
fill_in 'code', with: user.otp_code
|
145
|
-
click_button 'Submit'
|
140
|
+
sms_sign_in
|
146
141
|
|
147
142
|
tfa_cookie1 = get_tfa_cookie()
|
148
143
|
|
@@ -151,19 +146,22 @@ feature "User of two factor authentication" do
|
|
151
146
|
|
152
147
|
user2 = create_user()
|
153
148
|
login_as(user2)
|
154
|
-
|
155
|
-
fill_in 'code', with: user2.otp_code
|
156
|
-
click_button 'Submit'
|
149
|
+
sms_sign_in
|
157
150
|
|
158
151
|
tfa_cookie2 = get_tfa_cookie()
|
159
152
|
|
160
153
|
expect(tfa_cookie1).not_to eq tfa_cookie2
|
161
154
|
end
|
162
155
|
|
163
|
-
|
156
|
+
def sms_sign_in
|
157
|
+
SMSProvider.messages.clear()
|
164
158
|
visit user_two_factor_authentication_path
|
165
|
-
fill_in 'code', with:
|
159
|
+
fill_in 'code', with: SMSProvider.last_message.body
|
166
160
|
click_button 'Submit'
|
161
|
+
end
|
162
|
+
|
163
|
+
scenario 'TFA should be unique for specific user' do
|
164
|
+
sms_sign_in
|
167
165
|
|
168
166
|
tfa_cookie1 = get_tfa_cookie()
|
169
167
|
|
@@ -174,7 +172,7 @@ feature "User of two factor authentication" do
|
|
174
172
|
set_tfa_cookie(tfa_cookie1)
|
175
173
|
login_as(user2)
|
176
174
|
visit dashboard_path
|
177
|
-
expect(page).to have_content(
|
175
|
+
expect(page).to have_content("Enter the code that was sent to you")
|
178
176
|
end
|
179
177
|
end
|
180
178
|
|
@@ -187,76 +185,41 @@ feature "User of two factor authentication" do
|
|
187
185
|
|
188
186
|
describe 'signing in' do
|
189
187
|
let(:user) { create_user }
|
188
|
+
let(:admin) { create_admin }
|
190
189
|
|
191
|
-
scenario '
|
192
|
-
klass = stub_const 'UserOtpSender', Class.new
|
193
|
-
|
194
|
-
klass.class_eval do
|
195
|
-
def reset_otp_state; end
|
196
|
-
end
|
197
|
-
|
198
|
-
otp_sender = instance_double(UserOtpSender)
|
199
|
-
expect(UserOtpSender).to receive(:new).with(user).and_return(otp_sender)
|
200
|
-
expect(otp_sender).to receive(:reset_otp_state)
|
201
|
-
|
190
|
+
scenario 'user signs is' do
|
202
191
|
visit new_user_session_path
|
203
192
|
complete_sign_in_form_for(user)
|
204
|
-
end
|
205
|
-
|
206
|
-
scenario 'when UserOtpSender#reset_otp_state is not defined' do
|
207
|
-
klass = stub_const 'UserOtpSender', Class.new
|
208
193
|
|
209
|
-
|
210
|
-
|
211
|
-
end
|
212
|
-
|
213
|
-
otp_sender = instance_double(UserOtpSender)
|
214
|
-
allow(otp_sender).to receive(:respond_to?).with(:reset_otp_state).and_return(false)
|
194
|
+
expect(page).to have_content('Signed in successfully.')
|
195
|
+
end
|
215
196
|
|
216
|
-
|
217
|
-
|
197
|
+
scenario 'admin signs in' do
|
198
|
+
visit new_admin_session_path
|
199
|
+
complete_sign_in_form_for(admin)
|
218
200
|
|
219
|
-
|
220
|
-
complete_sign_in_form_for(user)
|
201
|
+
expect(page).to have_content('Signed in successfully.')
|
221
202
|
end
|
222
203
|
end
|
223
204
|
|
224
205
|
describe 'signing out' do
|
225
206
|
let(:user) { create_user }
|
207
|
+
let(:admin) { create_admin }
|
226
208
|
|
227
|
-
scenario '
|
209
|
+
scenario 'user signs out' do
|
228
210
|
visit new_user_session_path
|
229
211
|
complete_sign_in_form_for(user)
|
230
|
-
|
231
|
-
klass = stub_const 'UserOtpSender', Class.new
|
232
|
-
klass.class_eval do
|
233
|
-
def reset_otp_state; end
|
234
|
-
end
|
235
|
-
|
236
|
-
otp_sender = instance_double(UserOtpSender)
|
237
|
-
|
238
|
-
expect(UserOtpSender).to receive(:new).with(user).and_return(otp_sender)
|
239
|
-
expect(otp_sender).to receive(:reset_otp_state)
|
240
|
-
|
241
212
|
visit destroy_user_session_path
|
242
|
-
end
|
243
|
-
|
244
|
-
scenario 'when UserOtpSender#reset_otp_state is not defined' do
|
245
|
-
visit new_user_session_path
|
246
|
-
complete_sign_in_form_for(user)
|
247
213
|
|
248
|
-
|
249
|
-
|
250
|
-
def reset_otp_state; end
|
251
|
-
end
|
252
|
-
|
253
|
-
otp_sender = instance_double(UserOtpSender)
|
254
|
-
allow(otp_sender).to receive(:respond_to?).with(:reset_otp_state).and_return(false)
|
214
|
+
expect(page).to have_content('Signed out successfully.')
|
215
|
+
end
|
255
216
|
|
256
|
-
|
257
|
-
|
217
|
+
scenario 'admin signs out' do
|
218
|
+
visit new_admin_session_path
|
219
|
+
complete_sign_in_form_for(admin)
|
220
|
+
visit destroy_admin_session_path
|
258
221
|
|
259
|
-
|
222
|
+
expect(page).to have_content('Signed out successfully.')
|
260
223
|
end
|
261
224
|
end
|
262
225
|
end
|