devise-otp 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +1 -1
- data/Rakefile +17 -18
- data/app/assets/javascripts/devise-otp.js +0 -0
- data/app/assets/javascripts/qrcode.js +0 -0
- data/app/controllers/devise_otp/devise/{credentials_controller.rb → otp_credentials_controller.rb} +17 -21
- data/app/controllers/devise_otp/devise/{tokens_controller.rb → otp_tokens_controller.rb} +9 -13
- data/app/views/devise/{credentials → otp_credentials}/refresh.html.erb +0 -0
- data/app/views/devise/{credentials → otp_credentials}/show.html.erb +1 -1
- data/app/views/devise/{tokens → otp_tokens}/_token_secret.html.erb +4 -4
- data/app/views/devise/{tokens → otp_tokens}/_trusted_devices.html.erb +0 -0
- data/app/views/devise/{tokens → otp_tokens}/recovery.html.erb +4 -4
- data/app/views/devise/{tokens → otp_tokens}/recovery_codes.text.erb +0 -0
- data/app/views/devise/{tokens → otp_tokens}/show.html.erb +5 -5
- data/config/locales/en.yml +4 -12
- data/devise-otp.gemspec +13 -13
- data/lib/devise-otp/version.rb +2 -2
- data/lib/devise-otp.rb +15 -29
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +21 -23
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +0 -2
- data/lib/devise_otp_authenticatable/engine.rb +6 -4
- data/lib/devise_otp_authenticatable/hooks/sessions.rb +4 -5
- data/lib/devise_otp_authenticatable/hooks.rb +1 -3
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +19 -25
- data/lib/devise_otp_authenticatable/routes.rb +11 -14
- data/lib/generators/active_record/devise_otp_generator.rb +1 -1
- data/lib/generators/active_record/templates/migration.rb +0 -0
- data/lib/generators/devise_otp/devise_otp_generator.rb +12 -13
- data/lib/generators/devise_otp/install_generator.rb +3 -4
- data/lib/generators/devise_otp/views_generator.rb +5 -5
- data/test/dummy/README.rdoc +0 -0
- data/test/dummy/Rakefile +1 -1
- data/test/dummy/app/assets/config/manifest.js +0 -0
- data/test/dummy/app/assets/javascripts/application.js +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +0 -0
- data/test/dummy/app/controllers/application_controller.rb +0 -0
- data/test/dummy/app/controllers/posts_controller.rb +2 -2
- data/test/dummy/app/helpers/application_helper.rb +0 -0
- data/test/dummy/app/helpers/posts_helper.rb +0 -0
- data/test/dummy/app/mailers/.gitkeep +0 -0
- data/test/dummy/app/models/post.rb +0 -0
- data/test/dummy/app/models/user.rb +8 -8
- data/test/dummy/app/views/layouts/application.html.erb +0 -0
- data/test/dummy/app/views/posts/_form.html.erb +0 -0
- data/test/dummy/app/views/posts/edit.html.erb +0 -0
- data/test/dummy/app/views/posts/index.html.erb +0 -0
- data/test/dummy/app/views/posts/new.html.erb +0 -0
- data/test/dummy/app/views/posts/show.html.erb +0 -0
- data/test/dummy/config/application.rb +4 -4
- data/test/dummy/config/boot.rb +5 -5
- data/test/dummy/config/database.yml +0 -0
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/development.rb +1 -1
- data/test/dummy/config/environments/production.rb +1 -1
- data/test/dummy/config/environments/test.rb +2 -2
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -0
- data/test/dummy/config/initializers/devise.rb +6 -8
- data/test/dummy/config/initializers/inflections.rb +0 -0
- data/test/dummy/config/initializers/mime_types.rb +0 -0
- data/test/dummy/config/initializers/secret_token.rb +2 -2
- data/test/dummy/config/initializers/session_store.rb +1 -1
- data/test/dummy/config/initializers/wrap_parameters.rb +1 -1
- data/test/dummy/config/locales/en.yml +0 -0
- data/test/dummy/config/routes.rb +1 -1
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20130125101430_create_users.rb +0 -0
- data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +12 -13
- data/test/dummy/db/migrate/20130131142320_create_posts.rb +0 -0
- data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +13 -13
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/public/404.html +0 -0
- data/test/dummy/public/422.html +0 -0
- data/test/dummy/public/500.html +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +3 -3
- data/test/integration/persistence_test.rb +11 -12
- data/test/integration/refresh_test.rb +13 -14
- data/test/integration/sign_in_test.rb +24 -26
- data/test/integration/token_test.rb +5 -6
- data/test/integration_tests_helper.rb +16 -17
- data/test/model_tests_helper.rb +5 -7
- data/test/models/otp_authenticatable_test.rb +18 -19
- data/test/orm/active_record.rb +0 -0
- data/test/test_helper.rb +10 -10
- metadata +32 -73
- data/test/dummy/db/test.sqlite3-journal +0 -0
@@ -2,22 +2,22 @@ class AddDeviseToUsers < ActiveRecord::Migration[5.0]
|
|
2
2
|
def self.up
|
3
3
|
change_table(:users) do |t|
|
4
4
|
## Database authenticatable
|
5
|
-
t.string :email,
|
6
|
-
t.string :encrypted_password, :
|
5
|
+
t.string :email, null: false, default: ""
|
6
|
+
t.string :encrypted_password, null: false, default: ""
|
7
7
|
|
8
8
|
## Recoverable
|
9
|
-
t.string
|
9
|
+
t.string :reset_password_token
|
10
10
|
t.datetime :reset_password_sent_at
|
11
11
|
|
12
12
|
## Rememberable
|
13
13
|
t.datetime :remember_created_at
|
14
14
|
|
15
15
|
## Trackable
|
16
|
-
t.integer
|
16
|
+
t.integer :sign_in_count, default: 0
|
17
17
|
t.datetime :current_sign_in_at
|
18
18
|
t.datetime :last_sign_in_at
|
19
|
-
t.string
|
20
|
-
t.string
|
19
|
+
t.string :current_sign_in_ip
|
20
|
+
t.string :last_sign_in_ip
|
21
21
|
|
22
22
|
## Confirmable
|
23
23
|
# t.string :confirmation_token
|
@@ -26,23 +26,22 @@ class AddDeviseToUsers < ActiveRecord::Migration[5.0]
|
|
26
26
|
# t.string :unconfirmed_email # Only if using reconfirmable
|
27
27
|
|
28
28
|
## Lockable
|
29
|
-
t.integer
|
30
|
-
t.string
|
29
|
+
t.integer :failed_attempts, default: 0 # Only if lock strategy is :failed_attempts
|
30
|
+
t.string :unlock_token # Only if unlock strategy is :email or :both
|
31
31
|
t.datetime :locked_at
|
32
32
|
|
33
33
|
## Token authenticatable
|
34
34
|
t.string :authentication_token
|
35
35
|
|
36
|
-
|
37
36
|
# Uncomment below if timestamps were not included in your original model.
|
38
37
|
# t.timestamps
|
39
38
|
end
|
40
39
|
|
41
|
-
add_index :users, :email,
|
42
|
-
add_index :users, :reset_password_token, :
|
40
|
+
add_index :users, :email, unique: true
|
41
|
+
add_index :users, :reset_password_token, unique: true
|
43
42
|
# add_index :users, :confirmation_token, :unique => true
|
44
|
-
add_index :users, :unlock_token,
|
45
|
-
add_index :users, :authentication_token, :
|
43
|
+
add_index :users, :unlock_token, unique: true
|
44
|
+
add_index :users, :authentication_token, unique: true
|
46
45
|
end
|
47
46
|
|
48
47
|
def self.down
|
File without changes
|
@@ -1,28 +1,28 @@
|
|
1
1
|
class DeviseOtpAddToUsers < ActiveRecord::Migration[5.0]
|
2
2
|
def self.up
|
3
3
|
change_table :users do |t|
|
4
|
-
t.string
|
5
|
-
t.string
|
6
|
-
t.boolean
|
7
|
-
t.boolean
|
8
|
-
t.datetime
|
9
|
-
t.integer
|
10
|
-
t.integer
|
11
|
-
t.integer
|
12
|
-
t.string
|
4
|
+
t.string :otp_auth_secret
|
5
|
+
t.string :otp_recovery_secret
|
6
|
+
t.boolean :otp_enabled, default: false, null: false
|
7
|
+
t.boolean :otp_mandatory, default: false, null: false
|
8
|
+
t.datetime :otp_enabled_on
|
9
|
+
t.integer :otp_time_drift, default: 0, null: false
|
10
|
+
t.integer :otp_failed_attempts, default: 0, null: false
|
11
|
+
t.integer :otp_recovery_counter, default: 0, null: false
|
12
|
+
t.string :otp_persistence_seed
|
13
13
|
|
14
|
-
t.string
|
15
|
-
t.datetime
|
14
|
+
t.string :otp_session_challenge
|
15
|
+
t.datetime :otp_challenge_expires
|
16
16
|
end
|
17
17
|
|
18
|
-
add_index :users, :otp_session_challenge,
|
18
|
+
add_index :users, :otp_session_challenge, unique: true
|
19
19
|
add_index :users, :otp_challenge_expires
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.down
|
23
23
|
change_table :users do |t|
|
24
24
|
t.remove :otp_auth_secret, :otp_recovery_secret, :otp_enabled, :otp_mandatory, :otp_enabled_on, :otp_session_challenge,
|
25
|
-
|
25
|
+
:otp_challenge_expires, :otp_time_drift, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
File without changes
|
data/test/dummy/public/404.html
CHANGED
File without changes
|
data/test/dummy/public/422.html
CHANGED
File without changes
|
data/test/dummy/public/500.html
CHANGED
File without changes
|
File without changes
|
data/test/dummy/script/rails
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
3
|
|
4
|
-
APP_PATH = File.expand_path(
|
5
|
-
require File.expand_path(
|
6
|
-
require
|
4
|
+
APP_PATH = File.expand_path("../../config/application", __FILE__)
|
5
|
+
require File.expand_path("../../config/boot", __FILE__)
|
6
|
+
require "rails/commands"
|
@@ -1,8 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
3
|
|
4
4
|
class PersistenceTest < ActionDispatch::IntegrationTest
|
5
|
-
|
6
5
|
def setup
|
7
6
|
@old_persistence = User.otp_trust_persistence
|
8
7
|
User.otp_trust_persistence = 3.seconds
|
@@ -13,7 +12,7 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
13
12
|
Capybara.reset_sessions!
|
14
13
|
end
|
15
14
|
|
16
|
-
test
|
15
|
+
test "a user should be requested the otp challenge every log in" do
|
17
16
|
# log in 1fa
|
18
17
|
user = enable_otp_and_sign_in
|
19
18
|
otp_challenge_for user
|
@@ -27,7 +26,7 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
27
26
|
assert_equal user_otp_credential_path, current_path
|
28
27
|
end
|
29
28
|
|
30
|
-
test
|
29
|
+
test "a user should be able to set their browser as trusted" do
|
31
30
|
# log in 1fa
|
32
31
|
user = enable_otp_and_sign_in
|
33
32
|
otp_challenge_for user
|
@@ -35,8 +34,8 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
35
34
|
visit user_otp_token_path
|
36
35
|
assert_equal user_otp_token_path, current_path
|
37
36
|
|
38
|
-
click_link(
|
39
|
-
assert_text
|
37
|
+
click_link("Trust this browser")
|
38
|
+
assert_text "Your browser is trusted."
|
40
39
|
sign_out
|
41
40
|
|
42
41
|
sign_user_in
|
@@ -44,7 +43,7 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
44
43
|
assert_equal root_path, current_path
|
45
44
|
end
|
46
45
|
|
47
|
-
test
|
46
|
+
test "a user should be able to download its recovery codes" do
|
48
47
|
# log in 1fa
|
49
48
|
user = enable_otp_and_sign_in
|
50
49
|
otp_challenge_for user
|
@@ -55,13 +54,13 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
55
54
|
enable_chrome_headless_downloads(page, "/tmp/devise-otp")
|
56
55
|
|
57
56
|
DownloadHelper.wait_for_download(count: 1) do
|
58
|
-
click_link(
|
57
|
+
click_link("Download recovery codes")
|
59
58
|
end
|
60
59
|
|
61
60
|
assert_equal 1, DownloadHelper.downloads.size
|
62
61
|
end
|
63
62
|
|
64
|
-
test
|
63
|
+
test "trusted status should expire" do
|
65
64
|
# log in 1fa
|
66
65
|
user = enable_otp_and_sign_in
|
67
66
|
otp_challenge_for user
|
@@ -69,8 +68,8 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
69
68
|
visit user_otp_token_path
|
70
69
|
assert_equal user_otp_token_path, current_path
|
71
70
|
|
72
|
-
click_link(
|
73
|
-
assert_text
|
71
|
+
click_link("Trust this browser")
|
72
|
+
assert_text "Your browser is trusted."
|
74
73
|
sign_out
|
75
74
|
|
76
75
|
sleep User.otp_trust_persistence.to_i + 1
|
@@ -1,8 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
3
|
|
4
4
|
class RefreshTest < ActionDispatch::IntegrationTest
|
5
|
-
|
6
5
|
def setup
|
7
6
|
@old_refresh = User.otp_credentials_refresh
|
8
7
|
User.otp_credentials_refresh = 1.second
|
@@ -13,14 +12,14 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
13
12
|
Capybara.reset_sessions!
|
14
13
|
end
|
15
14
|
|
16
|
-
test
|
15
|
+
test "a user that just signed in should be able to access their OTP settings without refreshing" do
|
17
16
|
sign_user_in
|
18
17
|
|
19
18
|
visit user_otp_token_path
|
20
19
|
assert_equal user_otp_token_path, current_path
|
21
20
|
end
|
22
21
|
|
23
|
-
test
|
22
|
+
test "a user should be prompted for credentials when the credentials_refresh time is expired" do
|
24
23
|
sign_user_in
|
25
24
|
visit user_otp_token_path
|
26
25
|
assert_equal user_otp_token_path, current_path
|
@@ -31,7 +30,7 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
31
30
|
assert_equal refresh_user_otp_credential_path, current_path
|
32
31
|
end
|
33
32
|
|
34
|
-
test
|
33
|
+
test "a user should be able to access their OTP settings after refreshing" do
|
35
34
|
sign_user_in
|
36
35
|
visit user_otp_token_path
|
37
36
|
assert_equal user_otp_token_path, current_path
|
@@ -41,12 +40,12 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
41
40
|
visit user_otp_token_path
|
42
41
|
assert_equal refresh_user_otp_credential_path, current_path
|
43
42
|
|
44
|
-
fill_in
|
45
|
-
click_button
|
43
|
+
fill_in "user_refresh_password", with: "12345678"
|
44
|
+
click_button "Continue..."
|
46
45
|
assert_equal user_otp_token_path, current_path
|
47
46
|
end
|
48
47
|
|
49
|
-
test
|
48
|
+
test "a user should NOT be able to access their OTP settings unless refreshing" do
|
50
49
|
sign_user_in
|
51
50
|
visit user_otp_token_path
|
52
51
|
assert_equal user_otp_token_path, current_path
|
@@ -56,20 +55,20 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
56
55
|
visit user_otp_token_path
|
57
56
|
assert_equal refresh_user_otp_credential_path, current_path
|
58
57
|
|
59
|
-
fill_in
|
60
|
-
click_button
|
58
|
+
fill_in "user_refresh_password", with: "12345670"
|
59
|
+
click_button "Continue..."
|
61
60
|
assert_equal refresh_user_otp_credential_path, current_path
|
62
61
|
end
|
63
62
|
|
64
|
-
test
|
63
|
+
test "user should be finally be able to access their settings, and just password is enough" do
|
65
64
|
user = enable_otp_and_sign_in_with_otp
|
66
65
|
|
67
66
|
sleep(2)
|
68
67
|
visit user_otp_token_path
|
69
68
|
assert_equal refresh_user_otp_credential_path, current_path
|
70
69
|
|
71
|
-
fill_in
|
72
|
-
click_button
|
70
|
+
fill_in "user_refresh_password", with: "12345678"
|
71
|
+
click_button "Continue..."
|
73
72
|
|
74
73
|
assert_equal user_otp_token_path, current_path
|
75
74
|
end
|
@@ -1,76 +1,74 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
3
|
|
4
4
|
class SignInTest < ActionDispatch::IntegrationTest
|
5
|
-
|
6
5
|
def teardown
|
7
6
|
Capybara.reset_sessions!
|
8
7
|
end
|
9
8
|
|
10
|
-
test
|
9
|
+
test "a new user should be able to sign in without using their token" do
|
11
10
|
create_full_user
|
12
11
|
|
13
12
|
visit posts_path
|
14
|
-
fill_in
|
15
|
-
fill_in
|
16
|
-
page.has_content?(
|
13
|
+
fill_in "user_email", with: "user@email.invalid"
|
14
|
+
fill_in "user_password", with: "12345678"
|
15
|
+
page.has_content?("Log in") ? click_button("Log in") : click_button("Sign in")
|
17
16
|
|
18
17
|
assert_equal posts_path, current_path
|
19
18
|
end
|
20
19
|
|
21
|
-
test
|
20
|
+
test "a new user, just signed in, should be able to sign in and enable their OTP authentication" do
|
22
21
|
user = sign_user_in
|
23
22
|
|
24
23
|
visit user_otp_token_path
|
25
|
-
assert !page.has_content?(
|
24
|
+
assert !page.has_content?("Your token secret")
|
26
25
|
|
27
|
-
check
|
28
|
-
click_button
|
26
|
+
check "user_otp_enabled"
|
27
|
+
click_button "Continue..."
|
29
28
|
|
30
29
|
assert_equal user_otp_token_path, current_path
|
31
30
|
|
32
|
-
assert page.has_content?(
|
31
|
+
assert page.has_content?("Your token secret")
|
33
32
|
assert !user.otp_auth_secret.nil?
|
34
33
|
assert !user.otp_persistence_seed.nil?
|
35
34
|
end
|
36
35
|
|
37
|
-
test
|
36
|
+
test "a new user should be able to sign in enable OTP and be prompted for their token" do
|
38
37
|
enable_otp_and_sign_in
|
39
38
|
|
40
39
|
assert_equal user_otp_credential_path, current_path
|
41
40
|
end
|
42
41
|
|
43
|
-
test
|
42
|
+
test "fail token authentication" do
|
44
43
|
enable_otp_and_sign_in
|
45
44
|
assert_equal user_otp_credential_path, current_path
|
46
45
|
|
47
|
-
fill_in
|
48
|
-
click_button
|
46
|
+
fill_in "user_token", with: "123456"
|
47
|
+
click_button "Submit Token"
|
49
48
|
|
50
49
|
assert_equal new_user_session_path, current_path
|
51
50
|
end
|
52
51
|
|
53
|
-
test
|
52
|
+
test "fail blank token authentication" do
|
54
53
|
enable_otp_and_sign_in
|
55
54
|
assert_equal user_otp_credential_path, current_path
|
56
55
|
|
57
|
-
fill_in
|
58
|
-
click_button
|
56
|
+
fill_in "user_token", with: ""
|
57
|
+
click_button "Submit Token"
|
59
58
|
|
60
59
|
assert_equal user_otp_credential_path, current_path
|
61
60
|
end
|
62
61
|
|
63
|
-
test
|
62
|
+
test "successful token authentication" do
|
64
63
|
user = enable_otp_and_sign_in
|
65
64
|
|
66
|
-
fill_in
|
67
|
-
click_button
|
65
|
+
fill_in "user_token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
66
|
+
click_button "Submit Token"
|
68
67
|
|
69
68
|
assert_equal root_path, current_path
|
70
69
|
end
|
71
70
|
|
72
|
-
|
73
|
-
test 'should fail if the the challenge times out' do
|
71
|
+
test "should fail if the the challenge times out" do
|
74
72
|
old_timeout = User.otp_authentication_timeout
|
75
73
|
User.otp_authentication_timeout = 1.second
|
76
74
|
|
@@ -78,8 +76,8 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
78
76
|
|
79
77
|
sleep(2)
|
80
78
|
|
81
|
-
fill_in
|
82
|
-
click_button
|
79
|
+
fill_in "user_token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
80
|
+
click_button "Submit Token"
|
83
81
|
|
84
82
|
User.otp_authentication_timeout = old_timeout
|
85
83
|
assert_equal new_user_session_path, current_path
|
@@ -1,20 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
3
|
|
4
4
|
class TokenTest < ActionDispatch::IntegrationTest
|
5
|
-
|
6
5
|
def teardown
|
7
6
|
Capybara.reset_sessions!
|
8
7
|
end
|
9
8
|
|
10
|
-
test
|
9
|
+
test "disabling OTP after successfully enabling" do
|
11
10
|
# log in 1fa
|
12
11
|
user = enable_otp_and_sign_in
|
13
12
|
assert_equal user_otp_credential_path, current_path
|
14
13
|
|
15
14
|
# otp 2fa
|
16
|
-
fill_in
|
17
|
-
click_button
|
15
|
+
fill_in "user_token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
16
|
+
click_button "Submit Token"
|
18
17
|
assert_equal root_path, current_path
|
19
18
|
|
20
19
|
# disable OTP
|
@@ -2,15 +2,15 @@ class ActionDispatch::IntegrationTest
|
|
2
2
|
include Warden::Test::Helpers
|
3
3
|
|
4
4
|
def warden
|
5
|
-
request.env[
|
5
|
+
request.env["warden"]
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
def create_full_user
|
9
9
|
@user ||= begin
|
10
10
|
user = User.create!(
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
11
|
+
email: "user@email.invalid",
|
12
|
+
password: "12345678",
|
13
|
+
password_confirmation: "12345678"
|
14
14
|
)
|
15
15
|
user
|
16
16
|
end
|
@@ -18,8 +18,8 @@ class ActionDispatch::IntegrationTest
|
|
18
18
|
|
19
19
|
def enable_otp_and_sign_in_with_otp
|
20
20
|
enable_otp_and_sign_in.tap do |user|
|
21
|
-
fill_in
|
22
|
-
click_button
|
21
|
+
fill_in "user_token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
22
|
+
click_button "Submit Token"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -27,8 +27,8 @@ class ActionDispatch::IntegrationTest
|
|
27
27
|
user = create_full_user
|
28
28
|
sign_user_in(user)
|
29
29
|
visit user_otp_token_path
|
30
|
-
check
|
31
|
-
click_button
|
30
|
+
check "user_otp_enabled"
|
31
|
+
click_button "Continue..."
|
32
32
|
|
33
33
|
Capybara.reset_sessions!
|
34
34
|
|
@@ -37,14 +37,14 @@ class ActionDispatch::IntegrationTest
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def otp_challenge_for(user)
|
40
|
-
fill_in
|
41
|
-
click_button
|
40
|
+
fill_in "user_token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
41
|
+
click_button "Submit Token"
|
42
42
|
end
|
43
43
|
|
44
44
|
def disable_otp
|
45
45
|
visit user_otp_token_path
|
46
|
-
uncheck
|
47
|
-
click_button
|
46
|
+
uncheck "user_otp_enabled"
|
47
|
+
click_button "Continue..."
|
48
48
|
end
|
49
49
|
|
50
50
|
def sign_out
|
@@ -55,11 +55,10 @@ class ActionDispatch::IntegrationTest
|
|
55
55
|
user ||= create_full_user
|
56
56
|
resource_name = user.class.name.underscore
|
57
57
|
visit send("new_#{resource_name}_session_path")
|
58
|
-
fill_in "#{resource_name}_email", :
|
59
|
-
fill_in "#{resource_name}_password", :
|
58
|
+
fill_in "#{resource_name}_email", with: user.email
|
59
|
+
fill_in "#{resource_name}_password", with: user.password
|
60
60
|
|
61
|
-
page.has_content?(
|
61
|
+
page.has_content?("Log in") ? click_button("Log in") : click_button("Sign in")
|
62
62
|
user
|
63
63
|
end
|
64
|
-
|
65
64
|
end
|
data/test/model_tests_helper.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
class ActiveSupport::TestCase
|
2
|
-
|
3
2
|
#
|
4
3
|
# Helpers for creating new users
|
5
4
|
#
|
@@ -9,14 +8,13 @@ class ActiveSupport::TestCase
|
|
9
8
|
"user-#{@@unique_identity_count}@mail.invalid"
|
10
9
|
end
|
11
10
|
|
12
|
-
def valid_attributes(attributes={})
|
13
|
-
{
|
14
|
-
|
15
|
-
|
11
|
+
def valid_attributes(attributes = {})
|
12
|
+
{email: unique_identity,
|
13
|
+
password: "12345678",
|
14
|
+
password_confirmation: "12345678"}.update(attributes)
|
16
15
|
end
|
17
16
|
|
18
|
-
def new_user(attributes={})
|
17
|
+
def new_user(attributes = {})
|
19
18
|
User.new(valid_attributes(attributes)).save
|
20
19
|
end
|
21
|
-
|
22
20
|
end
|
@@ -1,32 +1,31 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "test_helper"
|
2
|
+
require "model_tests_helper"
|
3
3
|
|
4
4
|
class OtpAuthenticatableTest < ActiveSupport::TestCase
|
5
|
-
|
6
5
|
def setup
|
7
6
|
new_user
|
8
7
|
end
|
9
8
|
|
10
|
-
test
|
9
|
+
test "new users have a non-nil secret set" do
|
11
10
|
assert_not_nil User.first.otp_auth_secret
|
12
11
|
end
|
13
12
|
|
14
|
-
test
|
13
|
+
test "new users have OTP disabled by default" do
|
15
14
|
assert !User.first.otp_enabled
|
16
15
|
end
|
17
16
|
|
18
|
-
test
|
17
|
+
test "users should have an instance of TOTP/ROTP objects" do
|
19
18
|
u = User.first
|
20
19
|
assert u.time_based_otp.is_a? ROTP::TOTP
|
21
20
|
assert u.recovery_otp.is_a? ROTP::HOTP
|
22
21
|
end
|
23
22
|
|
24
|
-
test
|
23
|
+
test "users should have their otp_auth_secret/persistence_seed set on creation" do
|
25
24
|
assert User.first.otp_auth_secret
|
26
25
|
assert User.first.otp_persistence_seed
|
27
26
|
end
|
28
27
|
|
29
|
-
test
|
28
|
+
test "reset_otp_credentials should generate new secrets and disable OTP" do
|
30
29
|
u = User.first
|
31
30
|
u.update_attribute(:otp_enabled, true)
|
32
31
|
assert u.otp_enabled
|
@@ -39,7 +38,7 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
39
38
|
assert !u.otp_enabled
|
40
39
|
end
|
41
40
|
|
42
|
-
test
|
41
|
+
test "reset_otp_persistence should generate new persistence_seed but NOT change the otp_auth_secret" do
|
43
42
|
u = User.first
|
44
43
|
u.update_attribute(:otp_enabled, true)
|
45
44
|
assert u.otp_enabled
|
@@ -47,22 +46,22 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
47
46
|
otp_persistence_seed = u.otp_persistence_seed
|
48
47
|
|
49
48
|
u.reset_otp_persistence!
|
50
|
-
assert
|
49
|
+
assert(otp_auth_secret == u.otp_auth_secret)
|
51
50
|
assert !(otp_persistence_seed == u.otp_persistence_seed)
|
52
51
|
assert u.otp_enabled
|
53
52
|
end
|
54
53
|
|
55
|
-
test
|
54
|
+
test "generating a challenge, should retrieve the user later" do
|
56
55
|
u = User.first
|
57
56
|
u.update_attribute(:otp_enabled, true)
|
58
57
|
challenge = u.generate_otp_challenge!
|
59
58
|
|
60
59
|
w = User.find_valid_otp_challenge(challenge)
|
61
60
|
assert w.is_a? User
|
62
|
-
assert_equal w,u
|
61
|
+
assert_equal w, u
|
63
62
|
end
|
64
63
|
|
65
|
-
test
|
64
|
+
test "expiring the challenge, should retrieve nothing" do
|
66
65
|
u = User.first
|
67
66
|
u.update_attribute(:otp_enabled, true)
|
68
67
|
challenge = u.generate_otp_challenge!(1.second)
|
@@ -72,7 +71,7 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
72
71
|
assert_nil w
|
73
72
|
end
|
74
73
|
|
75
|
-
test
|
74
|
+
test "expired challenges should not be valid" do
|
76
75
|
u = User.first
|
77
76
|
u.update_attribute(:otp_enabled, true)
|
78
77
|
challenge = u.generate_otp_challenge!(1.second)
|
@@ -80,14 +79,14 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
80
79
|
assert_equal false, u.otp_challenge_valid?
|
81
80
|
end
|
82
81
|
|
83
|
-
test
|
82
|
+
test "null otp challenge" do
|
84
83
|
u = User.first
|
85
84
|
u.update_attribute(:otp_enabled, true)
|
86
|
-
assert_equal false, u.validate_otp_token(
|
85
|
+
assert_equal false, u.validate_otp_token("")
|
87
86
|
assert_equal false, u.validate_otp_token(nil)
|
88
87
|
end
|
89
88
|
|
90
|
-
test
|
89
|
+
test "generated otp token should be valid for the user" do
|
91
90
|
u = User.first
|
92
91
|
u.update_attribute(:otp_enabled, true)
|
93
92
|
|
@@ -97,7 +96,7 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
97
96
|
assert_equal true, u.validate_otp_token(token)
|
98
97
|
end
|
99
98
|
|
100
|
-
test
|
99
|
+
test "generated otp token, out of drift window, should be NOT valid for the user" do
|
101
100
|
u = User.first
|
102
101
|
u.update_attribute(:otp_enabled, true)
|
103
102
|
|
@@ -109,7 +108,7 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
109
108
|
end
|
110
109
|
end
|
111
110
|
|
112
|
-
test
|
111
|
+
test "recovery secrets should be valid, and valid only once" do
|
113
112
|
u = User.first
|
114
113
|
u.update_attribute(:otp_enabled, true)
|
115
114
|
recovery = u.next_otp_recovery_tokens
|
data/test/orm/active_record.rb
CHANGED
File without changes
|