two_factor_authentication 1.0 → 1.1

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