two_factor_authentication 1.0 → 1.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +20 -0
  3. data/Gemfile +17 -0
  4. data/README.md +27 -18
  5. data/Rakefile +13 -0
  6. data/app/controllers/devise/two_factor_authentication_controller.rb +5 -4
  7. data/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb +2 -2
  8. data/config/locales/en.yml +3 -0
  9. data/config/locales/ru.yml +6 -0
  10. data/lib/two_factor_authentication/controllers/helpers.rb +11 -1
  11. data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +12 -4
  12. data/lib/two_factor_authentication/version.rb +1 -1
  13. data/spec/controllers/two_factor_auth_spec.rb +20 -0
  14. data/spec/features/two_factor_authenticatable_spec.rb +86 -0
  15. data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +92 -6
  16. data/spec/rails_app/.gitignore +3 -0
  17. data/spec/rails_app/README.md +3 -0
  18. data/spec/rails_app/Rakefile +7 -0
  19. data/spec/rails_app/app/assets/javascripts/application.js +1 -0
  20. data/spec/rails_app/app/assets/stylesheets/application.css +4 -0
  21. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  22. data/spec/rails_app/app/controllers/home_controller.rb +10 -0
  23. data/spec/rails_app/app/helpers/application_helper.rb +8 -0
  24. data/spec/rails_app/app/mailers/.gitkeep +0 -0
  25. data/spec/rails_app/app/models/.gitkeep +0 -0
  26. data/spec/rails_app/app/models/guest_user.rb +10 -0
  27. data/spec/rails_app/app/models/user.rb +15 -0
  28. data/spec/rails_app/app/views/home/dashboard.html.erb +7 -0
  29. data/spec/rails_app/app/views/home/index.html.erb +3 -0
  30. data/spec/rails_app/app/views/layouts/application.html.erb +20 -0
  31. data/spec/rails_app/config.ru +4 -0
  32. data/spec/rails_app/config/application.rb +63 -0
  33. data/spec/rails_app/config/boot.rb +10 -0
  34. data/spec/rails_app/config/database.yml +19 -0
  35. data/spec/rails_app/config/environment.rb +5 -0
  36. data/spec/rails_app/config/environments/development.rb +28 -0
  37. data/spec/rails_app/config/environments/production.rb +68 -0
  38. data/spec/rails_app/config/environments/test.rb +32 -0
  39. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/rails_app/config/initializers/devise.rb +256 -0
  41. data/spec/rails_app/config/initializers/inflections.rb +15 -0
  42. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  43. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  44. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  45. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  46. data/spec/rails_app/config/locales/devise.en.yml +59 -0
  47. data/spec/rails_app/config/locales/en.yml +5 -0
  48. data/spec/rails_app/config/routes.rb +64 -0
  49. data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +42 -0
  50. data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +15 -0
  51. data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +7 -0
  52. data/spec/rails_app/db/schema.rb +38 -0
  53. data/spec/rails_app/lib/assets/.gitkeep +0 -0
  54. data/spec/rails_app/lib/sms_provider.rb +17 -0
  55. data/spec/rails_app/public/404.html +26 -0
  56. data/spec/rails_app/public/422.html +26 -0
  57. data/spec/rails_app/public/500.html +25 -0
  58. data/spec/rails_app/public/favicon.ico +0 -0
  59. data/spec/rails_app/script/rails +6 -0
  60. data/spec/spec_helper.rb +7 -7
  61. data/spec/support/authenticated_model_helper.rb +18 -19
  62. data/spec/support/capybara.rb +3 -0
  63. data/spec/support/features_spec_helper.rb +19 -0
  64. data/spec/support/sms_provider.rb +5 -0
  65. data/two_factor_authentication.gemspec +3 -1
  66. metadata +141 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39274f1841e29f847f6c2900a9228493e6103243
4
- data.tar.gz: c0c3e1aebe06d8981301dfa2ccd85ab6f347b6e0
3
+ metadata.gz: 86b64f6f26de2115a9fb9c35afd02072d4d668ab
4
+ data.tar.gz: b6448de97e61ce3d00910f3a6b5b165ad683e642
5
5
  SHA512:
6
- metadata.gz: 3e4bf30aa6f90afd6ee31400b4acd5e75dfc6fa49162b9ceb18042e406c55b956a0841466b9881c289b842870cb56ca985005e639578861259862c9c04209e3c
7
- data.tar.gz: 91f57824cfc4ea2588cd7658c50eb8ee0b221b6dff7c45362d2d2542c51911c61f3f88483e9d79951d14c1c61fb3a4c80a11065bf77a513a0cc5f15d41bd713f
6
+ metadata.gz: 17445687f19fe7f427b15c788423e1d7e26b90298e9e1c174a56168f3a7e6bb16172e4edb2fa6b3b7d27f17d54bc46119267524ce0fb6cf4a5946d0e8639eda9
7
+ data.tar.gz: 3538d6195e8529e753eaaf5bf85a85f9c65eac7af9cf51c227fea2bfe2b42a01eb91be4909196bfe749a8e78e23c8e87ba12791255327e592ae54fd415a94ce7
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+
3
+ env:
4
+ - "RAILS_VERSION=3.2.0"
5
+ - "RAILS_VERSION=4.0.0"
6
+ - "RAILS_VERSION=master"
7
+
8
+ rvm:
9
+ - 1.9.3
10
+ - 2.0
11
+ - 2.1
12
+
13
+ matrix:
14
+ allow_failures:
15
+ - env: "RAILS_VERSION=master"
16
+
17
+ before_script:
18
+ - bundle exec rake app:db:migrate
19
+
20
+ script: bundle exec rake spec
data/Gemfile CHANGED
@@ -2,3 +2,20 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in devise_ip_filter.gemspec
4
4
  gemspec
5
+
6
+ rails_version = ENV["RAILS_VERSION"] || "default"
7
+
8
+ rails = case rails_version
9
+ when "master"
10
+ {github: "rails/rails"}
11
+ when "default"
12
+ "~> 3.2"
13
+ else
14
+ "~> #{rails_version}"
15
+ end
16
+
17
+ gem "rails", rails
18
+
19
+ group :test do
20
+ gem "sqlite3"
21
+ end
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
- ## Two factor authentication for Devise
1
+ # Two factor authentication for Devise
2
+
3
+ [![Build Status](https://travis-ci.org/Houdini/two_factor_authentication.svg?branch=master)](https://travis-ci.org/Houdini/two_factor_authentication)
2
4
 
3
5
  ## Features
4
6
 
@@ -19,7 +21,6 @@ Once that's done, run:
19
21
 
20
22
  bundle install
21
23
 
22
-
23
24
  ### Automatic installation
24
25
 
25
26
  In order to add two factor authorisation to a model, run the command:
@@ -27,7 +28,7 @@ In order to add two factor authorisation to a model, run the command:
27
28
  bundle exec rails g two_factor_authentication MODEL
28
29
 
29
30
  Where MODEL is your model name (e.g. User or Admin). This generator will add `:two_factor_authenticatable` to your model
30
- and create a migration in `db/migrate/`, which will add `::second_factor_pass_code` and `:second_factor_attempts_count` to your table.
31
+ and create a migration in `db/migrate/`, which will add `:otp_secret_key` and `:second_factor_attempts_count` to your table.
31
32
  Finally, run the migration with:
32
33
 
33
34
  bundle exec rake db:migrate
@@ -38,22 +39,26 @@ Add the following line to your model to fully enable two-factor auth:
38
39
 
39
40
  Set config values if desired for maximum second factor attempts count and allowed time drift for one-time passwords:
40
41
 
41
- config.max_login_attempts = 3
42
- config.allowed_otp_drift_seconds = 30
42
+ ```ruby
43
+ config.max_login_attempts = 3
44
+ config.allowed_otp_drift_seconds = 30
45
+ ```
43
46
 
44
47
  Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
45
48
 
46
- def send_two_factor_authentication_code
47
- # use Model#otp_code and send via SMS, etc.
48
- end
49
+ ```ruby
50
+ def send_two_factor_authentication_code
51
+ # use Model#otp_code and send via SMS, etc.
52
+ end
53
+ ```
49
54
 
50
55
  ### Manual installation
51
56
 
52
57
  To manually enable two factor authentication for the User model, you should add two_factor_authentication to your devise line, like:
53
58
 
54
59
  ```ruby
55
- devise :database_authenticatable, :registerable,
56
- :recoverable, :rememberable, :trackable, :validatable, :two_factor_authenticatable
60
+ devise :database_authenticatable, :registerable,
61
+ :recoverable, :rememberable, :trackable, :validatable, :two_factor_authenticatable
57
62
  ```
58
63
 
59
64
  Add the following line to your model to fully enable two-factor auth:
@@ -62,23 +67,27 @@ Add the following line to your model to fully enable two-factor auth:
62
67
 
63
68
  Set config values if desired for maximum second factor attempts count and allowed time drift for one-time passwords:
64
69
 
65
- config.max_login_attempts = 3
66
- config.allowed_otp_drift_seconds = 30
70
+ ```ruby
71
+ config.max_login_attempts = 3
72
+ config.allowed_otp_drift_seconds = 30
73
+ ```
67
74
 
68
75
  Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
69
76
 
70
- def send_two_factor_authentication_code
71
- # use Model#otp_code and send via SMS, etc.
72
- end
77
+ ```ruby
78
+ def send_two_factor_authentication_code
79
+ # use Model#otp_code and send via SMS, etc.
80
+ end
81
+ ```
73
82
 
74
83
  ### Customisation and Usage
75
84
 
76
85
  By default second factor authentication enabled for each user, you can change it with this method in your User model:
77
86
 
78
87
  ```ruby
79
- def need_two_factor_authentication?(request)
80
- request.ip != '127.0.0.1'
81
- end
88
+ def need_two_factor_authentication?(request)
89
+ request.ip != '127.0.0.1'
90
+ end
82
91
  ```
83
92
 
84
93
  this will disable two factor authentication for local users
data/Rakefile CHANGED
@@ -1 +1,14 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ APP_RAKEFILE = File.expand_path("../spec/rails_app/Rakefile", __FILE__)
4
+ load 'rails/tasks/engine.rake'
5
+
6
+ require 'rspec/core/rake_task'
7
+
8
+ desc "Run all specs in spec directory (excluding plugin specs)"
9
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
10
+
11
+ task :default => :spec
12
+
13
+ # To test against a specific version of Rails
14
+ # export RAILS_VERSION=3.2.0; bundle update; rake
@@ -11,15 +11,16 @@ class Devise::TwoFactorAuthenticationController < DeviseController
11
11
  if resource.authenticate_otp(params[:code])
12
12
  warden.session(resource_name)[:need_two_factor_authentication] = false
13
13
  sign_in resource_name, resource, :bypass => true
14
+ set_flash_message :notice, :success
14
15
  redirect_to stored_location_for(resource_name) || :root
15
16
  resource.update_attribute(:second_factor_attempts_count, 0)
16
17
  else
17
18
  resource.second_factor_attempts_count += 1
18
19
  resource.save
19
- set_flash_message :error, :attempt_failed
20
+ flash.now[:error] = find_message(:attempt_failed)
20
21
  if resource.max_login_attempts?
21
22
  sign_out(resource)
22
- render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return
23
+ render :max_login_attempts_reached
23
24
  else
24
25
  render :show
25
26
  end
@@ -34,10 +35,10 @@ class Devise::TwoFactorAuthenticationController < DeviseController
34
35
 
35
36
  def prepare_and_validate
36
37
  redirect_to :root and return if resource.nil?
37
- @limit = resource.class.max_login_attempts
38
+ @limit = resource.max_login_attempts
38
39
  if resource.max_login_attempts?
39
40
  sign_out(resource)
40
- render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return
41
+ render :max_login_attempts_reached and return
41
42
  end
42
43
  end
43
44
  end
@@ -1,3 +1,3 @@
1
- <h2>Access completely denied as you have reached your attempts limit = <%= @limit %>.</h2>
2
- <p>Please contact your system administrator.</p>
1
+ <h2><%= I18n.t("devise.two_factor_authentication.max_login_attempts_reached") %> = <%= @limit %>.</h2>
2
+ <p><%= I18n.t("devise.two_factor_authentication.contact_administrator") %></p>
3
3
 
@@ -1,4 +1,7 @@
1
1
  en:
2
2
  devise:
3
3
  two_factor_authentication:
4
+ success: "Two factor authentication successful."
4
5
  attempt_failed: "Attempt failed."
6
+ max_login_attempts_reached: "Access completely denied as you have reached your attempts limit"
7
+ contact_administrator: "Please contact your system administrator."
@@ -0,0 +1,6 @@
1
+ ru:
2
+ devise:
3
+ two_factor_authentication:
4
+ attempt_failed: "Неверный код."
5
+ max_login_attempts_reached: "Доступ заблокирован. Превышено число попыток авторизации"
6
+ contact_administrator: "Пожалуйста, свяжитесь с системным администратором."
@@ -21,7 +21,7 @@ module TwoFactorAuthentication
21
21
 
22
22
  def handle_failed_second_factor(scope)
23
23
  if request.format.present? and request.format.html?
24
- session["#{scope}_return_tor"] = request.path if request.get?
24
+ session["#{scope}_return_to"] = request.path if request.get?
25
25
  redirect_to two_factor_authentication_path_for(scope)
26
26
  else
27
27
  render nothing: true, status: :unauthorized
@@ -37,3 +37,13 @@ module TwoFactorAuthentication
37
37
  end
38
38
  end
39
39
  end
40
+
41
+ module Devise
42
+ module Controllers
43
+ module Helpers
44
+ def is_fully_authenticated?
45
+ !session["warden.user.user.session"].try(:[], :need_two_factor_authentication)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -12,7 +12,7 @@ module Devise
12
12
 
13
13
  include InstanceMethodsOnActivation
14
14
 
15
- before_create { self.otp_column = ROTP::Base32.random_base32 }
15
+ before_create { populate_otp_column }
16
16
 
17
17
  if respond_to?(:attributes_protected_by_default)
18
18
  def self.attributes_protected_by_default #:nodoc:
@@ -35,9 +35,9 @@ module Devise
35
35
  ROTP::TOTP.new(self.otp_column).at(time)
36
36
  end
37
37
 
38
- def provisioning_uri(account = nil)
38
+ def provisioning_uri(account = nil, options = {})
39
39
  account ||= self.email if self.respond_to?(:email)
40
- ROTP::TOTP.new(self.otp_column).provisioning_uri(account)
40
+ ROTP::TOTP.new(self.otp_column, options).provisioning_uri(account)
41
41
  end
42
42
 
43
43
  def otp_column
@@ -57,7 +57,15 @@ module Devise
57
57
  end
58
58
 
59
59
  def max_login_attempts?
60
- second_factor_attempts_count >= self.class.max_login_attempts
60
+ second_factor_attempts_count.to_i >= max_login_attempts.to_i
61
+ end
62
+
63
+ def max_login_attempts
64
+ self.class.max_login_attempts
65
+ end
66
+
67
+ def populate_otp_column
68
+ self.otp_column = ROTP::Base32.random_base32
61
69
  end
62
70
 
63
71
  end
@@ -1,3 +1,3 @@
1
1
  module TwoFactorAuthentication
2
- VERSION = "1.0".freeze
2
+ VERSION = "1.1".freeze
3
3
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ include Warden::Test::Helpers
4
+
5
+ describe HomeController do
6
+ context "passed only 1st factor auth" do
7
+ let(:user) { create_user }
8
+
9
+ describe "is_fully_authenticated helper" do
10
+ it "should be true" do
11
+ login_as user, scope: :user
12
+ visit user_two_factor_authentication_path
13
+
14
+
15
+ controller.is_fully_authenticated?.should be_true
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ feature "User of two factor authentication" do
4
+ let(:user) { create_user }
5
+
6
+ scenario "must be logged in" do
7
+ visit user_two_factor_authentication_path
8
+
9
+ expect(page).to have_content("Welcome Home")
10
+ expect(page).to have_content("You are signed out")
11
+ end
12
+
13
+ scenario "sends two factor authentication code after sign in" do
14
+ SMSProvider.messages.should be_empty
15
+
16
+ visit new_user_session_path
17
+ complete_sign_in_form_for(user)
18
+
19
+ expect(page).to have_content "Enter your personal code"
20
+
21
+ SMSProvider.messages.size.should eq(1)
22
+ message = SMSProvider.last_message
23
+ expect(message.to).to eq(user.phone_number)
24
+ expect(message.body).to eq(user.otp_code)
25
+ end
26
+
27
+ context "when logged in" do
28
+
29
+ background do
30
+ login_as user
31
+ end
32
+
33
+ scenario "can fill in TFA code" do
34
+ visit user_two_factor_authentication_path
35
+
36
+ expect(page).to have_content("You are signed in as Marissa")
37
+ expect(page).to have_content("Enter your personal code")
38
+
39
+ fill_in "code", with: user.otp_code
40
+ click_button "Submit"
41
+
42
+ within(".flash.notice") do
43
+ expect(page).to have_content("Two factor authentication successful.")
44
+ end
45
+ end
46
+
47
+ scenario "is redirected to TFA when path requires authentication" do
48
+ visit dashboard_path
49
+
50
+ expect(page).to_not have_content("Your Personal Dashboard")
51
+
52
+ fill_in "code", with: user.otp_code
53
+ click_button "Submit"
54
+
55
+ expect(page).to have_content("Your Personal Dashboard")
56
+ expect(page).to have_content("You are signed in as Marissa")
57
+ end
58
+
59
+ scenario "is locked out after max failed attempts" do
60
+ visit user_two_factor_authentication_path
61
+
62
+ max_attempts = User.max_login_attempts
63
+
64
+ max_attempts.times do
65
+ fill_in "code", with: "incorrect#{rand(100)}"
66
+ click_button "Submit"
67
+
68
+ within(".flash.error") do
69
+ expect(page).to have_content("Attempt failed")
70
+ end
71
+ end
72
+
73
+ expect(page).to have_content("Access completely denied")
74
+ expect(page).to have_content("You are signed out")
75
+ end
76
+
77
+ scenario "cannot retry authentication after max attempts" do
78
+ user.update_attribute(:second_factor_attempts_count, User.max_login_attempts)
79
+
80
+ visit user_two_factor_authentication_path
81
+
82
+ expect(page).to have_content("Access completely denied")
83
+ expect(page).to have_content("You are signed out")
84
+ end
85
+ end
86
+ end
@@ -1,9 +1,8 @@
1
1
  require 'spec_helper'
2
2
  include AuthenticatedModelHelper
3
3
 
4
-
5
4
  describe Devise::Models::TwoFactorAuthenticatable, '#otp_code' do
6
- let(:instance) { AuthenticatedModelHelper.create_new_user }
5
+ let(:instance) { build_guest_user }
7
6
  subject { instance.otp_code(time) }
8
7
  let(:time) { 1392852456 }
9
8
 
@@ -33,7 +32,7 @@ describe Devise::Models::TwoFactorAuthenticatable, '#otp_code' do
33
32
  end
34
33
 
35
34
  describe Devise::Models::TwoFactorAuthenticatable, '#authenticate_otp' do
36
- let(:instance) { AuthenticatedModelHelper.create_new_user }
35
+ let(:instance) { build_guest_user }
37
36
 
38
37
  before :each do
39
38
  instance.otp_secret_key = "2z6hxkdwi3uvrnpn"
@@ -55,16 +54,103 @@ describe Devise::Models::TwoFactorAuthenticatable, '#authenticate_otp' do
55
54
  end
56
55
 
57
56
  describe Devise::Models::TwoFactorAuthenticatable, '#send_two_factor_authentication_code' do
57
+ let(:instance) { build_guest_user }
58
58
 
59
59
  it "should raise an error by default" do
60
- instance = AuthenticatedModelHelper.create_new_user
61
60
  expect {
62
61
  instance.send_two_factor_authentication_code
63
62
  }.to raise_error(NotImplementedError)
64
63
  end
65
64
 
66
65
  it "should be overrideable" do
67
- instance = AuthenticatedModelHelper.create_new_user_with_overrides
66
+ def instance.send_two_factor_authentication_code
67
+ "Code sent"
68
+ end
68
69
  expect(instance.send_two_factor_authentication_code).to eq("Code sent")
69
70
  end
70
- end
71
+ end
72
+
73
+ describe Devise::Models::TwoFactorAuthenticatable, '#provisioning_uri' do
74
+ let(:instance) { build_guest_user }
75
+
76
+ before do
77
+ instance.email = "houdini@example.com"
78
+ instance.run_callbacks :create
79
+ end
80
+
81
+ it "should return uri with user's email" do
82
+ expect(instance.provisioning_uri).to match(%r{otpauth://totp/houdini@example.com\?secret=\w{16}})
83
+ end
84
+
85
+ it "should return uri with issuer option" do
86
+ expect(instance.provisioning_uri("houdini")).to match(%r{otpauth://totp/houdini\?secret=\w{16}$})
87
+ end
88
+
89
+ it "should return uri with issuer option" do
90
+ require 'cgi'
91
+
92
+ uri = URI.parse(instance.provisioning_uri("houdini", issuer: 'Magic'))
93
+ params = CGI::parse(uri.query)
94
+
95
+ expect(uri.scheme).to eq("otpauth")
96
+ expect(uri.host).to eq("totp")
97
+ expect(uri.path).to eq("/houdini")
98
+ expect(params['issuer'].shift).to eq('Magic')
99
+ expect(params['secret'].shift).to match(%r{\w{16}})
100
+ end
101
+ end
102
+
103
+ describe Devise::Models::TwoFactorAuthenticatable, '#populate_otp_column' do
104
+ let(:instance) { build_guest_user }
105
+
106
+ it "populates otp_column on create" do
107
+ expect(instance.otp_secret_key).to be_nil
108
+
109
+ instance.run_callbacks :create # populate_otp_column called via before_create
110
+
111
+ expect(instance.otp_secret_key).to match(%r{\w{16}})
112
+ end
113
+
114
+ it "repopulates otp_column" do
115
+ instance.run_callbacks :create
116
+ original_key = instance.otp_secret_key
117
+
118
+ instance.populate_otp_column
119
+
120
+ expect(instance.otp_secret_key).to match(%r{\w{16}})
121
+ expect(instance.otp_secret_key).to_not eq(original_key)
122
+ end
123
+ end
124
+
125
+ describe Devise::Models::TwoFactorAuthenticatable, '#max_login_attempts' do
126
+ let(:instance) { build_guest_user }
127
+
128
+ before do
129
+ @original_max_login_attempts = GuestUser.max_login_attempts
130
+ GuestUser.max_login_attempts = 3
131
+ end
132
+
133
+ after { GuestUser.max_login_attempts = @original_max_login_attempts }
134
+
135
+ it "returns class setting" do
136
+ expect(instance.max_login_attempts).to eq(3)
137
+ end
138
+
139
+ it "returns false as boolean" do
140
+ instance.second_factor_attempts_count = nil
141
+ expect(instance.max_login_attempts?).to be_false
142
+ instance.second_factor_attempts_count = 0
143
+ expect(instance.max_login_attempts?).to be_false
144
+ instance.second_factor_attempts_count = 1
145
+ expect(instance.max_login_attempts?).to be_false
146
+ instance.second_factor_attempts_count = 2
147
+ expect(instance.max_login_attempts?).to be_false
148
+ end
149
+
150
+ it "returns true as boolean after too many attempts" do
151
+ instance.second_factor_attempts_count = 3
152
+ expect(instance.max_login_attempts?).to be_true
153
+ instance.second_factor_attempts_count = 4
154
+ expect(instance.max_login_attempts?).to be_true
155
+ end
156
+ end