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.
- checksums.yaml +4 -4
- data/.travis.yml +20 -0
- data/Gemfile +17 -0
- data/README.md +27 -18
- data/Rakefile +13 -0
- data/app/controllers/devise/two_factor_authentication_controller.rb +5 -4
- data/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb +2 -2
- data/config/locales/en.yml +3 -0
- data/config/locales/ru.yml +6 -0
- data/lib/two_factor_authentication/controllers/helpers.rb +11 -1
- data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +12 -4
- data/lib/two_factor_authentication/version.rb +1 -1
- data/spec/controllers/two_factor_auth_spec.rb +20 -0
- data/spec/features/two_factor_authenticatable_spec.rb +86 -0
- data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +92 -6
- data/spec/rails_app/.gitignore +3 -0
- data/spec/rails_app/README.md +3 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/javascripts/application.js +1 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +4 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/home_controller.rb +10 -0
- data/spec/rails_app/app/helpers/application_helper.rb +8 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/models/guest_user.rb +10 -0
- data/spec/rails_app/app/models/user.rb +15 -0
- data/spec/rails_app/app/views/home/dashboard.html.erb +7 -0
- data/spec/rails_app/app/views/home/index.html.erb +3 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +20 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +63 -0
- data/spec/rails_app/config/boot.rb +10 -0
- data/spec/rails_app/config/database.yml +19 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +28 -0
- data/spec/rails_app/config/environments/production.rb +68 -0
- data/spec/rails_app/config/environments/test.rb +32 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/devise.rb +256 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/devise.en.yml +59 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +64 -0
- data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +42 -0
- data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +15 -0
- data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +7 -0
- data/spec/rails_app/db/schema.rb +38 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/sms_provider.rb +17 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/spec_helper.rb +7 -7
- data/spec/support/authenticated_model_helper.rb +18 -19
- data/spec/support/capybara.rb +3 -0
- data/spec/support/features_spec_helper.rb +19 -0
- data/spec/support/sms_provider.rb +5 -0
- data/two_factor_authentication.gemspec +3 -1
- metadata +141 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86b64f6f26de2115a9fb9c35afd02072d4d668ab
|
4
|
+
data.tar.gz: b6448de97e61ce3d00910f3a6b5b165ad683e642
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
1
|
+
# Two factor authentication for Devise
|
2
|
+
|
3
|
+
[](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
|
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
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
20
|
+
flash.now[:error] = find_message(:attempt_failed)
|
20
21
|
if resource.max_login_attempts?
|
21
22
|
sign_out(resource)
|
22
|
-
render :
|
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.
|
38
|
+
@limit = resource.max_login_attempts
|
38
39
|
if resource.max_login_attempts?
|
39
40
|
sign_out(resource)
|
40
|
-
render :
|
41
|
+
render :max_login_attempts_reached and return
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
<h2
|
2
|
-
<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
|
|
data/config/locales/en.yml
CHANGED
@@ -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."
|
@@ -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}
|
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 {
|
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 >=
|
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
|
@@ -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) {
|
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) {
|
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
|
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
|