two_factor_authentication 1.1.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|