devise-otp 0.8.0 → 1.1.0
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/.github/workflows/ci.yml +14 -3
- data/.gitignore +3 -3
- data/Appraisals +22 -0
- data/CHANGELOG.md +34 -1
- data/Gemfile +3 -1
- data/README.md +22 -7
- data/Rakefile +0 -11
- data/app/assets/stylesheets/devise-otp.css +4 -0
- data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +4 -6
- data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +1 -2
- data/config/locales/en.yml +0 -2
- data/devise-otp.gemspec +9 -5
- data/gemfiles/rails_7.1.gemfile +21 -0
- data/gemfiles/rails_7.2.gemfile +17 -0
- data/gemfiles/rails_8.0.gemfile +17 -0
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +5 -28
- data/lib/devise_otp_authenticatable/hooks/refreshable.rb +3 -1
- data/test/dummy/app/assets/javascripts/application.js +0 -1
- data/test/dummy/app/assets/stylesheets/application.css +1 -0
- data/test/dummy/app/controllers/admin_posts_controller.rb +0 -72
- data/test/dummy/app/controllers/non_otp_posts_controller.rb +13 -0
- data/test/dummy/app/controllers/posts_controller.rb +8 -2
- data/test/dummy/app/models/admin.rb +1 -13
- data/test/dummy/app/models/non_otp_user.rb +4 -0
- data/test/dummy/app/models/post.rb +1 -1
- data/test/dummy/app/models/user.rb +1 -13
- data/test/dummy/app/views/admin_posts/index.html.erb +0 -7
- data/test/dummy/app/views/layouts/application.html.erb +7 -1
- data/test/dummy/app/views/non_otp_posts/index.html.erb +18 -0
- data/test/dummy/config/application.rb +0 -7
- data/test/dummy/config/database.yml +20 -13
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/migrate/20240604000001_create_admins.rb +1 -1
- data/test/dummy/db/migrate/20250718092451_create_non_otp_users.rb +9 -0
- data/test/dummy/db/migrate/20250718092536_add_devise_to_non_otp_users.rb +52 -0
- data/test/dummy/db/schema.rb +118 -0
- data/test/dummy/db/seeds.rb +24 -0
- data/test/integration/disable_token_test.rb +3 -0
- data/test/integration/enable_otp_form_test.rb +17 -0
- data/test/integration/non_otp_user_models_test.rb +21 -0
- data/test/integration/persistence_test.rb +3 -0
- data/test/integration/refresh_test.rb +9 -0
- data/test/integration/reset_token_test.rb +3 -0
- data/test/integration/sign_in_test.rb +30 -0
- data/test/integration_tests_helper.rb +11 -0
- data/test/test_helper.rb +0 -5
- metadata +39 -23
- data/app/assets/javascripts/devise-otp.js +0 -1
- data/app/assets/javascripts/qrcode.js +0 -609
- data/docs/QR_CODES.md +0 -9
- data/test/dummy/app/views/admin_posts/_form.html.erb +0 -25
- data/test/dummy/app/views/admin_posts/edit.html.erb +0 -6
- data/test/dummy/app/views/admin_posts/new.html.erb +0 -5
- data/test/dummy/app/views/admin_posts/show.html.erb +0 -15
- data/test/orm/active_record.rb +0 -11
@@ -0,0 +1,18 @@
|
|
1
|
+
<h1>Listing posts</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Title</th>
|
6
|
+
<th>Body</th>
|
7
|
+
<th></th>
|
8
|
+
<th></th>
|
9
|
+
<th></th>
|
10
|
+
</tr>
|
11
|
+
|
12
|
+
<% @posts.each do |post| %>
|
13
|
+
<tr>
|
14
|
+
<td><%= post.title %></td>
|
15
|
+
<td><%= post.body %></td>
|
16
|
+
</tr>
|
17
|
+
<% end %>
|
18
|
+
</table>
|
@@ -9,13 +9,6 @@ require "sprockets/railtie"
|
|
9
9
|
# require "rails/test_unit/railtie"
|
10
10
|
|
11
11
|
Bundler.require
|
12
|
-
Bundler.require(:default, DEVISE_ORM) if defined?(Bundler)
|
13
|
-
|
14
|
-
begin
|
15
|
-
require "#{DEVISE_ORM}/railtie"
|
16
|
-
rescue LoadError
|
17
|
-
end
|
18
|
-
PARENT_MODEL_CLASS = (DEVISE_ORM == :active_record) ? ActiveRecord::Base : Object
|
19
12
|
|
20
13
|
require "devise"
|
21
14
|
require "devise-otp"
|
@@ -1,25 +1,32 @@
|
|
1
|
-
# SQLite
|
1
|
+
# SQLite. Versions 3.8.0 and up are supported.
|
2
2
|
# gem install sqlite3
|
3
3
|
#
|
4
4
|
# Ensure the SQLite 3 gem is defined in your Gemfile
|
5
|
-
# gem
|
6
|
-
|
5
|
+
# gem "sqlite3"
|
6
|
+
#
|
7
|
+
default: &default
|
7
8
|
adapter: sqlite3
|
8
|
-
|
9
|
-
pool: 5
|
9
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
10
10
|
timeout: 5000
|
11
11
|
|
12
|
+
development:
|
13
|
+
<<: *default
|
14
|
+
database: storage/development.sqlite3
|
15
|
+
|
12
16
|
# Warning: The database defined as "test" will be erased and
|
13
17
|
# re-generated from your development database when you run "rake".
|
14
18
|
# Do not set this db to the same as development or production.
|
15
19
|
test:
|
16
|
-
|
17
|
-
database:
|
18
|
-
|
19
|
-
timeout: 5000
|
20
|
+
<<: *default
|
21
|
+
database: storage/test.sqlite3
|
22
|
+
|
20
23
|
|
24
|
+
# SQLite3 write its data on the local filesystem, as such it requires
|
25
|
+
# persistent disks. If you are deploying to a managed service, you should
|
26
|
+
# make sure it provides disk persistence, as many don't.
|
27
|
+
#
|
28
|
+
# Similarly, if you deploy your application as a Docker container, you must
|
29
|
+
# ensure the database is located in a persisted volume.
|
21
30
|
production:
|
22
|
-
|
23
|
-
database:
|
24
|
-
pool: 5
|
25
|
-
timeout: 5000
|
31
|
+
<<: *default
|
32
|
+
# database: path/to/persistent/storage/production.sqlite3
|
data/test/dummy/config/routes.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
class AddDeviseToNonOtpUsers < ActiveRecord::Migration[5.0]
|
2
|
+
def self.up
|
3
|
+
change_table(:non_otp_users) do |t|
|
4
|
+
## Database authenticatable
|
5
|
+
t.string :email, null: false, default: ""
|
6
|
+
t.string :encrypted_password, null: false, default: ""
|
7
|
+
|
8
|
+
## Recoverable
|
9
|
+
t.string :reset_password_token
|
10
|
+
t.datetime :reset_password_sent_at
|
11
|
+
|
12
|
+
## Rememberable
|
13
|
+
t.datetime :remember_created_at
|
14
|
+
|
15
|
+
## Trackable
|
16
|
+
t.integer :sign_in_count, default: 0
|
17
|
+
t.datetime :current_sign_in_at
|
18
|
+
t.datetime :last_sign_in_at
|
19
|
+
t.string :current_sign_in_ip
|
20
|
+
t.string :last_sign_in_ip
|
21
|
+
|
22
|
+
## Confirmable
|
23
|
+
# t.string :confirmation_token
|
24
|
+
# t.datetime :confirmed_at
|
25
|
+
# t.datetime :confirmation_sent_at
|
26
|
+
# t.string :unconfirmed_email # Only if using reconfirmable
|
27
|
+
|
28
|
+
## Lockable
|
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
|
+
t.datetime :locked_at
|
32
|
+
|
33
|
+
## Token authenticatable
|
34
|
+
t.string :authentication_token
|
35
|
+
|
36
|
+
# Uncomment below if timestamps were not included in your original model.
|
37
|
+
# t.timestamps
|
38
|
+
end
|
39
|
+
|
40
|
+
add_index :non_otp_users, :email, unique: true
|
41
|
+
add_index :non_otp_users, :reset_password_token, unique: true
|
42
|
+
# add_index :non_otp_users, :confirmation_token, :unique => true
|
43
|
+
add_index :non_otp_users, :unlock_token, unique: true
|
44
|
+
add_index :non_otp_users, :authentication_token, unique: true
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.down
|
48
|
+
# By default, we don't want to make any assumption about how to roll back a migration when your
|
49
|
+
# model already existed. Please edit below which fields you would like to remove in this migration.
|
50
|
+
raise ActiveRecord::IrreversibleMigration
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
4
|
+
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
10
|
+
#
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
12
|
+
|
13
|
+
ActiveRecord::Schema[8.0].define(version: 2025_07_18_092536) do
|
14
|
+
create_table "admins", force: :cascade do |t|
|
15
|
+
t.string "name"
|
16
|
+
t.datetime "created_at", precision: nil, null: false
|
17
|
+
t.datetime "updated_at", precision: nil, null: false
|
18
|
+
t.string "email", default: "", null: false
|
19
|
+
t.string "encrypted_password", default: "", null: false
|
20
|
+
t.string "reset_password_token"
|
21
|
+
t.datetime "reset_password_sent_at", precision: nil
|
22
|
+
t.datetime "remember_created_at", precision: nil
|
23
|
+
t.integer "sign_in_count", default: 0
|
24
|
+
t.datetime "current_sign_in_at", precision: nil
|
25
|
+
t.datetime "last_sign_in_at", precision: nil
|
26
|
+
t.string "current_sign_in_ip"
|
27
|
+
t.string "last_sign_in_ip"
|
28
|
+
t.integer "failed_attempts", default: 0
|
29
|
+
t.string "unlock_token"
|
30
|
+
t.datetime "locked_at", precision: nil
|
31
|
+
t.string "authentication_token"
|
32
|
+
t.string "otp_auth_secret"
|
33
|
+
t.string "otp_recovery_secret"
|
34
|
+
t.boolean "otp_enabled", default: false, null: false
|
35
|
+
t.boolean "otp_mandatory", default: false, null: false
|
36
|
+
t.datetime "otp_enabled_on", precision: nil
|
37
|
+
t.integer "otp_time_drift", default: 0, null: false
|
38
|
+
t.integer "otp_failed_attempts", default: 0, null: false
|
39
|
+
t.integer "otp_recovery_counter", default: 0, null: false
|
40
|
+
t.string "otp_persistence_seed"
|
41
|
+
t.string "otp_session_challenge"
|
42
|
+
t.datetime "otp_challenge_expires", precision: nil
|
43
|
+
t.index ["authentication_token"], name: "index_admins_on_authentication_token", unique: true
|
44
|
+
t.index ["email"], name: "index_admins_on_email", unique: true
|
45
|
+
t.index ["otp_challenge_expires"], name: "index_admins_on_otp_challenge_expires"
|
46
|
+
t.index ["otp_session_challenge"], name: "index_admins_on_otp_session_challenge", unique: true
|
47
|
+
t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
|
48
|
+
t.index ["unlock_token"], name: "index_admins_on_unlock_token", unique: true
|
49
|
+
end
|
50
|
+
|
51
|
+
create_table "non_otp_users", force: :cascade do |t|
|
52
|
+
t.string "name"
|
53
|
+
t.datetime "created_at", null: false
|
54
|
+
t.datetime "updated_at", null: false
|
55
|
+
t.string "email", default: "", null: false
|
56
|
+
t.string "encrypted_password", default: "", null: false
|
57
|
+
t.string "reset_password_token"
|
58
|
+
t.datetime "reset_password_sent_at"
|
59
|
+
t.datetime "remember_created_at"
|
60
|
+
t.integer "sign_in_count", default: 0
|
61
|
+
t.datetime "current_sign_in_at"
|
62
|
+
t.datetime "last_sign_in_at"
|
63
|
+
t.string "current_sign_in_ip"
|
64
|
+
t.string "last_sign_in_ip"
|
65
|
+
t.integer "failed_attempts", default: 0
|
66
|
+
t.string "unlock_token"
|
67
|
+
t.datetime "locked_at"
|
68
|
+
t.string "authentication_token"
|
69
|
+
t.index ["authentication_token"], name: "index_non_otp_users_on_authentication_token", unique: true
|
70
|
+
t.index ["email"], name: "index_non_otp_users_on_email", unique: true
|
71
|
+
t.index ["reset_password_token"], name: "index_non_otp_users_on_reset_password_token", unique: true
|
72
|
+
t.index ["unlock_token"], name: "index_non_otp_users_on_unlock_token", unique: true
|
73
|
+
end
|
74
|
+
|
75
|
+
create_table "posts", force: :cascade do |t|
|
76
|
+
t.string "title"
|
77
|
+
t.text "body"
|
78
|
+
t.datetime "created_at", precision: nil, null: false
|
79
|
+
t.datetime "updated_at", precision: nil, null: false
|
80
|
+
end
|
81
|
+
|
82
|
+
create_table "users", force: :cascade do |t|
|
83
|
+
t.string "name"
|
84
|
+
t.datetime "created_at", precision: nil, null: false
|
85
|
+
t.datetime "updated_at", precision: nil, null: false
|
86
|
+
t.string "email", default: "", null: false
|
87
|
+
t.string "encrypted_password", default: "", null: false
|
88
|
+
t.string "reset_password_token"
|
89
|
+
t.datetime "reset_password_sent_at", precision: nil
|
90
|
+
t.datetime "remember_created_at", precision: nil
|
91
|
+
t.integer "sign_in_count", default: 0
|
92
|
+
t.datetime "current_sign_in_at", precision: nil
|
93
|
+
t.datetime "last_sign_in_at", precision: nil
|
94
|
+
t.string "current_sign_in_ip"
|
95
|
+
t.string "last_sign_in_ip"
|
96
|
+
t.integer "failed_attempts", default: 0
|
97
|
+
t.string "unlock_token"
|
98
|
+
t.datetime "locked_at", precision: nil
|
99
|
+
t.string "authentication_token"
|
100
|
+
t.string "otp_auth_secret"
|
101
|
+
t.string "otp_recovery_secret"
|
102
|
+
t.boolean "otp_enabled", default: false, null: false
|
103
|
+
t.boolean "otp_mandatory", default: false, null: false
|
104
|
+
t.datetime "otp_enabled_on", precision: nil
|
105
|
+
t.integer "otp_time_drift", default: 0, null: false
|
106
|
+
t.integer "otp_failed_attempts", default: 0, null: false
|
107
|
+
t.integer "otp_recovery_counter", default: 0, null: false
|
108
|
+
t.string "otp_persistence_seed"
|
109
|
+
t.string "otp_session_challenge"
|
110
|
+
t.datetime "otp_challenge_expires", precision: nil
|
111
|
+
t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true
|
112
|
+
t.index ["email"], name: "index_users_on_email", unique: true
|
113
|
+
t.index ["otp_challenge_expires"], name: "index_users_on_otp_challenge_expires"
|
114
|
+
t.index ["otp_session_challenge"], name: "index_users_on_otp_session_challenge", unique: true
|
115
|
+
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
116
|
+
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Admin.create(
|
2
|
+
name: "Test Admin",
|
3
|
+
email: "admin@devise-otp.local",
|
4
|
+
password: "Pass1234"
|
5
|
+
)
|
6
|
+
|
7
|
+
User.create(
|
8
|
+
name: "Test User",
|
9
|
+
email: "user@devise-otp.local",
|
10
|
+
password: "Pass1234"
|
11
|
+
)
|
12
|
+
|
13
|
+
NonOtpUser.create(
|
14
|
+
name: "Non OTP User",
|
15
|
+
email: "non-otp-user@devise-otp.local",
|
16
|
+
password: "Pass1234"
|
17
|
+
)
|
18
|
+
|
19
|
+
5.times do |n|
|
20
|
+
Post.create(
|
21
|
+
title: "Post #{n + 1}",
|
22
|
+
body: "This is post #{n + 1}."
|
23
|
+
)
|
24
|
+
end
|
@@ -20,6 +20,10 @@ class EnableOtpFormTest < ActionDispatch::IntegrationTest
|
|
20
20
|
assert_equal user_otp_token_path, current_path
|
21
21
|
assert page.has_content?("Enabled")
|
22
22
|
|
23
|
+
within "#alerts" do
|
24
|
+
assert page.has_content? 'Your Two-Factor Authentication settings have been updated.'
|
25
|
+
end
|
26
|
+
|
23
27
|
user.reload
|
24
28
|
assert user.otp_enabled?
|
25
29
|
end
|
@@ -37,6 +41,15 @@ class EnableOtpFormTest < ActionDispatch::IntegrationTest
|
|
37
41
|
|
38
42
|
user.reload
|
39
43
|
assert_not user.otp_enabled?
|
44
|
+
|
45
|
+
within "#alerts" do
|
46
|
+
assert page.has_content? 'The Confirmation Code you entered did not match the QR code shown below.'
|
47
|
+
end
|
48
|
+
|
49
|
+
visit "/"
|
50
|
+
within "#alerts" do
|
51
|
+
assert !page.has_content?('The Confirmation Code you entered did not match the QR code shown below.')
|
52
|
+
end
|
40
53
|
end
|
41
54
|
|
42
55
|
test "a user should not be able enable their OTP authentication with a blank confirmation code" do
|
@@ -50,6 +63,10 @@ class EnableOtpFormTest < ActionDispatch::IntegrationTest
|
|
50
63
|
|
51
64
|
assert page.has_content?("To Enable Two-Factor Authentication")
|
52
65
|
|
66
|
+
within "#alerts" do
|
67
|
+
assert page.has_content? 'The Confirmation Code you entered did not match the QR code shown below.'
|
68
|
+
end
|
69
|
+
|
53
70
|
user.reload
|
54
71
|
assert_not user.otp_enabled?
|
55
72
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
|
+
|
4
|
+
class NonOtpUserModelsTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Capybara.reset_sessions!
|
8
|
+
end
|
9
|
+
|
10
|
+
test "a non-OTP user should be able to sign in without error" do
|
11
|
+
create_non_otp_user
|
12
|
+
|
13
|
+
visit non_otp_posts_path
|
14
|
+
fill_in "non_otp_user_email", with: "non-otp-user@email.invalid"
|
15
|
+
fill_in "non_otp_user_password", with: "12345678"
|
16
|
+
page.has_content?("Log in") ? click_button("Log in") : click_button("Sign in")
|
17
|
+
|
18
|
+
assert_equal non_otp_posts_path, current_path
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -60,6 +60,15 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
60
60
|
fill_in "user_refresh_password", with: "12345670"
|
61
61
|
click_button "Continue..."
|
62
62
|
assert_equal refresh_user_otp_credential_path, current_path
|
63
|
+
|
64
|
+
within "#alerts" do
|
65
|
+
assert page.has_content? 'Sorry, you provided the wrong credentials.'
|
66
|
+
end
|
67
|
+
|
68
|
+
visit "/"
|
69
|
+
within "#alerts" do
|
70
|
+
assert !page.has_content?('Sorry, you provided the wrong credentials.')
|
71
|
+
end
|
63
72
|
end
|
64
73
|
|
65
74
|
test "user should be finally be able to access their settings, and just password is enough" do
|
@@ -23,6 +23,9 @@ class ResetTokenTest < ActionDispatch::IntegrationTest
|
|
23
23
|
reset_otp
|
24
24
|
|
25
25
|
assert_equal "/users/otp/token/edit", current_path
|
26
|
+
within "#alerts" do
|
27
|
+
assert page.has_content? 'Your token secret has been reset. Please confirm your new token secret below.'
|
28
|
+
end
|
26
29
|
end
|
27
30
|
|
28
31
|
test "generates new token secrets" do
|
@@ -43,6 +43,7 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
43
43
|
click_button "Submit Token"
|
44
44
|
|
45
45
|
assert_equal user_otp_credential_path, current_path
|
46
|
+
assert page.has_content? "The token you provided was invalid."
|
46
47
|
end
|
47
48
|
|
48
49
|
test "fail blank token authentication" do
|
@@ -53,6 +54,7 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
53
54
|
click_button "Submit Token"
|
54
55
|
|
55
56
|
assert_equal user_otp_credential_path, current_path
|
57
|
+
assert page.has_content? "You need to type in the token you generated with your device."
|
56
58
|
end
|
57
59
|
|
58
60
|
test "successful token authentication" do
|
@@ -78,4 +80,32 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
78
80
|
User.otp_authentication_timeout = old_timeout
|
79
81
|
assert_equal new_user_session_path, current_path
|
80
82
|
end
|
83
|
+
|
84
|
+
test "blank token flash message does not persist to successful authentication redirect." do
|
85
|
+
user = enable_otp_and_sign_in
|
86
|
+
|
87
|
+
fill_in "token", with: "123456"
|
88
|
+
click_button "Submit Token"
|
89
|
+
|
90
|
+
assert page.has_content?("The token you provided was invalid.")
|
91
|
+
|
92
|
+
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
93
|
+
click_button "Submit Token"
|
94
|
+
|
95
|
+
assert !page.has_content?("The token you provided was invalid.")
|
96
|
+
end
|
97
|
+
|
98
|
+
test "invalid token flash message does not persist to successful authentication redirect." do
|
99
|
+
user = enable_otp_and_sign_in
|
100
|
+
|
101
|
+
fill_in "token", with: ""
|
102
|
+
click_button "Submit Token"
|
103
|
+
|
104
|
+
assert page.has_content?("You need to type in the token you generated with your device.")
|
105
|
+
|
106
|
+
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
107
|
+
click_button "Submit Token"
|
108
|
+
|
109
|
+
assert !page.has_content?("You need to type in the token you generated with your device.")
|
110
|
+
end
|
81
111
|
end
|
@@ -27,6 +27,17 @@ class ActionDispatch::IntegrationTest
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
def create_non_otp_user
|
31
|
+
@non_otp_user ||= begin
|
32
|
+
non_otp_user = NonOtpUser.create!(
|
33
|
+
email: "non-otp-user@email.invalid",
|
34
|
+
password: "12345678",
|
35
|
+
password_confirmation: "12345678"
|
36
|
+
)
|
37
|
+
non_otp_user
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
30
41
|
def enable_otp_and_sign_in_with_otp
|
31
42
|
enable_otp_and_sign_in.tap do |user|
|
32
43
|
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
data/test/test_helper.rb
CHANGED
@@ -1,17 +1,12 @@
|
|
1
1
|
ENV["RAILS_ENV"] = "test"
|
2
|
-
DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym
|
3
2
|
|
4
|
-
puts "\n==> Devise.orm = #{DEVISE_ORM.inspect}"
|
5
3
|
require "dummy/config/environment"
|
6
|
-
require "orm/#{DEVISE_ORM}"
|
7
4
|
require "rails/test_help"
|
8
5
|
require "capybara/rails"
|
9
6
|
require "minitest/reporters"
|
10
7
|
|
11
8
|
Minitest::Reporters.use!
|
12
9
|
|
13
|
-
# I18n.load_path << File.expand_path("../support/locale/en.yml", __FILE__) if DEVISE_ORM == :mongoid
|
14
|
-
|
15
10
|
# ActiveSupport::Deprecation.silenced = true
|
16
11
|
|
17
12
|
class ActionDispatch::IntegrationTest
|