devise-otp 0.2.3 → 0.3.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 +5 -5
- data/.github/workflows/ci.yml +36 -0
- data/.gitignore +0 -0
- data/Gemfile +1 -22
- data/LICENSE.txt +0 -0
- data/README.md +41 -74
- data/Rakefile +0 -0
- data/app/assets/javascripts/devise-otp.js +1 -0
- data/app/assets/javascripts/qrcode.js +609 -0
- data/app/controllers/devise_otp/devise/credentials_controller.rb +102 -0
- data/app/controllers/devise_otp/devise/tokens_controller.rb +112 -0
- data/app/views/devise/credentials/refresh.html.erb +19 -0
- data/app/views/devise/credentials/show.html.erb +31 -0
- data/app/views/devise/tokens/_token_secret.html.erb +23 -0
- data/app/views/devise/tokens/_trusted_devices.html.erb +12 -0
- data/app/views/devise/tokens/recovery.html.erb +21 -0
- data/app/views/devise/tokens/recovery_codes.text.erb +3 -0
- data/app/views/devise/tokens/show.html.erb +21 -0
- data/config/locales/en.yml +8 -8
- data/devise-otp.gemspec +14 -9
- data/docs/QR_CODES.md +48 -0
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise-otp.rb +12 -11
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +20 -12
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +6 -7
- data/lib/devise_otp_authenticatable/engine.rb +22 -13
- data/lib/devise_otp_authenticatable/hooks/sessions.rb +8 -7
- data/lib/devise_otp_authenticatable/hooks.rb +1 -1
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +14 -9
- data/lib/devise_otp_authenticatable/routes.rb +4 -7
- data/lib/generators/active_record/devise_otp_generator.rb +0 -0
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/devise_otp/devise_otp_generator.rb +0 -0
- data/lib/generators/devise_otp/install_generator.rb +8 -5
- data/lib/generators/devise_otp/views_generator.rb +2 -3
- data/test/dummy/README.rdoc +0 -0
- data/test/dummy/Rakefile +0 -0
- data/test/dummy/app/assets/config/manifest.js +2 -0
- data/test/dummy/app/assets/javascripts/application.js +1 -0
- data/test/dummy/app/assets/stylesheets/application.css +0 -0
- data/test/dummy/app/controllers/application_controller.rb +1 -1
- data/test/dummy/app/controllers/posts_controller.rb +2 -0
- 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 +0 -0
- 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 +2 -1
- data/test/dummy/config/boot.rb +0 -0
- data/test/dummy/config/database.yml +1 -1
- data/test/dummy/config/environment.rb +0 -0
- data/test/dummy/config/environments/development.rb +0 -7
- data/test/dummy/config/environments/production.rb +0 -4
- data/test/dummy/config/environments/test.rb +0 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -0
- data/test/dummy/config/initializers/devise.rb +0 -0
- 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 +0 -0
- data/test/dummy/config/initializers/session_store.rb +0 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -0
- data/test/dummy/config/locales/en.yml +0 -0
- data/test/dummy/config/routes.rb +0 -0
- data/test/dummy/config.ru +0 -0
- data/test/dummy/db/migrate/20130125101430_create_users.rb +1 -1
- data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +1 -1
- data/test/dummy/db/migrate/20130131142320_create_posts.rb +1 -1
- data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +2 -2
- data/test/dummy/db/test.sqlite3-journal +0 -0
- 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/integration/persistence_test.rb +18 -2
- data/test/integration/refresh_test.rb +2 -32
- data/test/integration/sign_in_test.rb +3 -3
- data/test/integration/token_test.rb +1 -4
- data/test/integration_tests_helper.rb +0 -1
- data/test/model_tests_helper.rb +0 -0
- data/test/models/otp_authenticatable_test.rb +8 -9
- data/test/orm/active_record.rb +3 -1
- data/test/test_helper.rb +71 -2
- metadata +146 -40
- data/.travis.yml +0 -12
- data/app/controllers/devise_otp/credentials_controller.rb +0 -106
- data/app/controllers/devise_otp/tokens_controller.rb +0 -105
- data/app/views/devise_otp/credentials/refresh.html.erb +0 -20
- data/app/views/devise_otp/credentials/show.html.erb +0 -23
- data/app/views/devise_otp/tokens/_token_secret.html.erb +0 -17
- data/app/views/devise_otp/tokens/_trusted_devices.html.erb +0 -10
- data/app/views/devise_otp/tokens/recovery.html.erb +0 -21
- data/app/views/devise_otp/tokens/show.html.erb +0 -19
- data/lib/devise_otp_authenticatable/mapping.rb +0 -19
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
module DeviseOtpAuthenticatable
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
|
|
4
|
-
ActiveSupport.on_load(:action_controller) do
|
|
5
|
-
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
|
6
|
-
include DeviseOtpAuthenticatable::Controllers::Helpers
|
|
7
|
-
end
|
|
8
|
-
ActiveSupport.on_load(:action_view) do
|
|
9
|
-
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
|
10
|
-
include DeviseOtpAuthenticatable::Controllers::Helpers
|
|
11
|
-
end
|
|
12
|
-
|
|
13
4
|
# We use to_prepare instead of after_initialize here because Devise is a Rails engine;
|
|
14
5
|
config.to_prepare do
|
|
15
6
|
DeviseOtpAuthenticatable::Hooks.apply
|
|
16
7
|
end
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
initializer "devise-otp", group: :all do |app|
|
|
10
|
+
ActiveSupport.on_load(:devise_controller) do
|
|
11
|
+
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
|
12
|
+
include DeviseOtpAuthenticatable::Controllers::Helpers
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ActiveSupport.on_load(:action_view) do
|
|
16
|
+
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
|
17
|
+
include DeviseOtpAuthenticatable::Controllers::Helpers
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# See: https://guides.rubyonrails.org/engines.html#separate-assets-and-precompiling
|
|
21
|
+
# check if Rails api mode
|
|
22
|
+
if app.config.respond_to?(:assets)
|
|
23
|
+
if defined?(Sprockets) && Sprockets::VERSION >= "4"
|
|
24
|
+
app.config.assets.precompile << "devise-otp.js"
|
|
25
|
+
else
|
|
26
|
+
# use a proc instead of a string
|
|
27
|
+
app.config.assets.precompile << proc { |path| path == "devise-otp.js" }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
21
30
|
end
|
|
22
31
|
end
|
|
23
|
-
end
|
|
32
|
+
end
|
|
@@ -4,36 +4,36 @@ module DeviseOtpAuthenticatable::Hooks
|
|
|
4
4
|
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
|
5
5
|
|
|
6
6
|
included do
|
|
7
|
-
|
|
7
|
+
alias_method :create, :create_with_otp
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
#
|
|
11
11
|
# replaces Devise::SessionsController#create
|
|
12
12
|
#
|
|
13
13
|
def create_with_otp
|
|
14
|
-
|
|
15
14
|
resource = warden.authenticate!(auth_options)
|
|
16
15
|
|
|
16
|
+
devise_stored_location = stored_location_for(resource) # Grab the current stored location before it gets lost by warden.logout
|
|
17
|
+
store_location_for(resource, devise_stored_location) # Restore it since #stored_location_for removes it
|
|
18
|
+
|
|
17
19
|
otp_refresh_credentials_for(resource)
|
|
18
20
|
|
|
19
21
|
if otp_challenge_required_on?(resource)
|
|
20
22
|
challenge = resource.generate_otp_challenge!
|
|
21
23
|
warden.logout
|
|
24
|
+
store_location_for(resource, devise_stored_location) # restore the stored location
|
|
22
25
|
respond_with resource, :location => otp_credential_path_for(resource, {:challenge => challenge})
|
|
23
|
-
|
|
24
26
|
elsif otp_mandatory_on?(resource) # if mandatory, log in user but send him to the must activate otp
|
|
25
27
|
set_flash_message(:notice, :signed_in_but_otp) if is_navigational_format?
|
|
26
28
|
sign_in(resource_name, resource)
|
|
27
29
|
respond_with resource, :location => otp_token_path_for(resource)
|
|
28
30
|
else
|
|
29
|
-
|
|
30
31
|
set_flash_message(:notice, :signed_in) if is_navigational_format?
|
|
31
32
|
sign_in(resource_name, resource)
|
|
32
33
|
respond_with resource, :location => after_sign_in_path_for(resource)
|
|
33
34
|
end
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
|
|
37
37
|
private
|
|
38
38
|
|
|
39
39
|
#
|
|
@@ -41,7 +41,8 @@ module DeviseOtpAuthenticatable::Hooks
|
|
|
41
41
|
#
|
|
42
42
|
def otp_challenge_required_on?(resource)
|
|
43
43
|
return false unless resource.respond_to?(:otp_enabled) && resource.respond_to?(:otp_auth_secret)
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
resource.otp_enabled && !is_otp_trusted_browser_for?(resource)
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
#
|
|
@@ -54,4 +55,4 @@ module DeviseOtpAuthenticatable::Hooks
|
|
|
54
55
|
resource.otp_mandatory && !resource.otp_enabled
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
|
-
end
|
|
58
|
+
end
|
|
@@ -12,7 +12,8 @@ module Devise::Models
|
|
|
12
12
|
|
|
13
13
|
module ClassMethods
|
|
14
14
|
::Devise::Models.config(self, :otp_authentication_timeout, :otp_drift_window, :otp_trust_persistence,
|
|
15
|
-
:otp_mandatory, :otp_credentials_refresh, :
|
|
15
|
+
:otp_mandatory, :otp_credentials_refresh, :otp_issuer, :otp_recovery_tokens,
|
|
16
|
+
:otp_controller_path)
|
|
16
17
|
|
|
17
18
|
def find_valid_otp_challenge(challenge)
|
|
18
19
|
with_valid_otp_challenge(Time.now).where(:otp_session_challenge => challenge).first
|
|
@@ -20,7 +21,7 @@ module Devise::Models
|
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def time_based_otp
|
|
23
|
-
@time_based_otp ||= ROTP::TOTP.new(otp_auth_secret)
|
|
24
|
+
@time_based_otp ||= ROTP::TOTP.new(otp_auth_secret, issuer: "#{self.class.otp_issuer || Rails.application.class.module_parent_name}")
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def recovery_otp
|
|
@@ -32,7 +33,7 @@ module Devise::Models
|
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def otp_provisioning_identifier
|
|
35
|
-
|
|
36
|
+
email
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
|
|
@@ -41,7 +42,7 @@ module Devise::Models
|
|
|
41
42
|
@recovery_otp = nil
|
|
42
43
|
generate_otp_auth_secret
|
|
43
44
|
reset_otp_persistence
|
|
44
|
-
|
|
45
|
+
update!(:otp_enabled => false,
|
|
45
46
|
:otp_session_challenge => nil, :otp_challenge_expires => nil,
|
|
46
47
|
:otp_recovery_counter => 0)
|
|
47
48
|
end
|
|
@@ -61,15 +62,19 @@ module Devise::Models
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def enable_otp!
|
|
64
|
-
|
|
65
|
+
if otp_persistence_seed.nil?
|
|
66
|
+
reset_otp_credentials!
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
update!(:otp_enabled => true, :otp_enabled_on => Time.now)
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
def disable_otp!
|
|
68
|
-
|
|
73
|
+
update!(:otp_enabled => false, :otp_enabled_on => nil)
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
def generate_otp_challenge!(expires = nil)
|
|
72
|
-
|
|
77
|
+
update!(:otp_session_challenge => SecureRandom.hex,
|
|
73
78
|
:otp_challenge_expires => DateTime.now + (expires || self.class.otp_authentication_timeout))
|
|
74
79
|
otp_session_challenge
|
|
75
80
|
end
|
|
@@ -117,7 +122,7 @@ module Devise::Models
|
|
|
117
122
|
|
|
118
123
|
# should be centered around saved drift
|
|
119
124
|
(-self.class.otp_drift_window..self.class.otp_drift_window).any? {|drift|
|
|
120
|
-
(time_based_otp.verify(token, Time.now.ago(30 * drift))) }
|
|
125
|
+
(time_based_otp.verify(token, at: Time.now.ago(30 * drift))) }
|
|
121
126
|
end
|
|
122
127
|
|
|
123
128
|
def generate_otp_persistence_seed
|
|
@@ -130,4 +135,4 @@ module Devise::Models
|
|
|
130
135
|
end
|
|
131
136
|
|
|
132
137
|
end
|
|
133
|
-
end
|
|
138
|
+
end
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
module ActionDispatch::Routing
|
|
2
2
|
class Mapper
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
protected
|
|
6
|
-
#########
|
|
7
5
|
|
|
8
6
|
def devise_otp(mapping, controllers)
|
|
9
|
-
|
|
10
7
|
namespace :otp, :module => :devise_otp do
|
|
11
8
|
resource :token, :only => [:show, :update, :destroy],
|
|
12
|
-
:path => mapping.path_names[:token], :controller => controllers[:
|
|
9
|
+
:path => mapping.path_names[:token], :controller => controllers[:tokens] do
|
|
13
10
|
|
|
14
11
|
if Devise.otp_trust_persistence
|
|
15
12
|
get :persistence, :action => 'get_persistence'
|
|
@@ -21,12 +18,12 @@ module ActionDispatch::Routing
|
|
|
21
18
|
end
|
|
22
19
|
|
|
23
20
|
resource :credential, :only => [:show, :update],
|
|
24
|
-
:path => mapping.path_names[:credentials], :controller => controllers[:
|
|
21
|
+
:path => mapping.path_names[:credentials], :controller => controllers[:credentials] do
|
|
25
22
|
|
|
26
|
-
get
|
|
23
|
+
get :refresh, :action => 'get_refresh'
|
|
27
24
|
put :refresh, :action => 'set_refresh'
|
|
28
25
|
end
|
|
29
26
|
end
|
|
30
27
|
end
|
|
31
28
|
end
|
|
32
|
-
end
|
|
29
|
+
end
|
|
File without changes
|
|
@@ -20,7 +20,7 @@ class DeviseOtpAddTo<%= table_name.camelize %> < ActiveRecord::Migration
|
|
|
20
20
|
def self.down
|
|
21
21
|
change_table :<%= table_name %> do |t|
|
|
22
22
|
t.remove :otp_auth_secret, :otp_recovery_secret, :otp_enabled, :otp_mandatory, :otp_enabled_on, :otp_session_challenge,
|
|
23
|
-
:otp_challenge_expires, :
|
|
23
|
+
:otp_challenge_expires, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
|
|
24
24
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
File without changes
|
|
@@ -29,16 +29,19 @@ content = <<-CONTENT
|
|
|
29
29
|
|
|
30
30
|
# Users are given a list of one-time recovery tokens, for emergency access
|
|
31
31
|
# set to false to disable giving recovery tokens.
|
|
32
|
-
#config.
|
|
32
|
+
#config.otp_recovery_tokens = 10
|
|
33
33
|
|
|
34
34
|
# The user is allowed to set his browser as "trusted", no more OTP challenges will be
|
|
35
35
|
# asked for that browser, for a limited time.
|
|
36
36
|
# set to false to disable setting the browser as trusted
|
|
37
37
|
#config.otp_trust_persistence = 1.month
|
|
38
38
|
|
|
39
|
-
# The name of
|
|
40
|
-
# url
|
|
41
|
-
#config.
|
|
39
|
+
# The name of the token issuer, to be added to the provisioning
|
|
40
|
+
# url. Display will vary based on token application. (defaults to the Rails application class)
|
|
41
|
+
#config.otp_issuer = 'my_application'
|
|
42
|
+
|
|
43
|
+
# Custom view path for Devise OTP controllers
|
|
44
|
+
#config.otp_controller_path = 'devise'
|
|
42
45
|
|
|
43
46
|
CONTENT
|
|
44
47
|
|
|
@@ -50,4 +53,4 @@ CONTENT
|
|
|
50
53
|
end
|
|
51
54
|
end
|
|
52
55
|
end
|
|
53
|
-
end
|
|
56
|
+
end
|
|
@@ -9,10 +9,9 @@ module DeviseOtp
|
|
|
9
9
|
:desc => "The scope to copy views to"
|
|
10
10
|
|
|
11
11
|
include ::Devise::Generators::ViewPathTemplates
|
|
12
|
-
source_root File.expand_path("../../../../app/views
|
|
12
|
+
source_root File.expand_path("../../../../app/views", __FILE__)
|
|
13
13
|
def copy_views
|
|
14
|
-
view_directory :
|
|
15
|
-
view_directory :tokens, 'app/views/devise_otp/tokens'
|
|
14
|
+
view_directory :devise, 'app/views/devise'
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
17
|
end
|
data/test/dummy/README.rdoc
CHANGED
|
File without changes
|
data/test/dummy/Rakefile
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -53,6 +53,8 @@ module Dummy
|
|
|
53
53
|
# Enable escaping HTML in JSON.
|
|
54
54
|
config.active_support.escape_html_entities_in_json = true
|
|
55
55
|
|
|
56
|
+
config.active_record.legacy_connection_handling = false
|
|
57
|
+
|
|
56
58
|
# Use SQL instead of Active Record's schema dumper when creating the database.
|
|
57
59
|
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
|
58
60
|
# like if you have constraints or database-specific column types
|
|
@@ -65,4 +67,3 @@ module Dummy
|
|
|
65
67
|
config.assets.version = '1.0'
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
|
-
|
data/test/dummy/config/boot.rb
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -22,13 +22,6 @@ Dummy::Application.configure do
|
|
|
22
22
|
# Only use best-standards-support built into browsers
|
|
23
23
|
config.action_dispatch.best_standards_support = :builtin
|
|
24
24
|
|
|
25
|
-
# Raise exception on mass assignment protection for Active Record models
|
|
26
|
-
config.active_record.mass_assignment_sanitizer = :strict
|
|
27
|
-
|
|
28
|
-
# Log the query plan for queries taking more than this (works
|
|
29
|
-
# with SQLite, MySQL, and PostgreSQL)
|
|
30
|
-
config.active_record.auto_explain_threshold_in_seconds = 0.5
|
|
31
|
-
|
|
32
25
|
# Do not compress assets
|
|
33
26
|
config.assets.compress = false
|
|
34
27
|
|
|
@@ -66,8 +66,4 @@ Dummy::Application.configure do
|
|
|
66
66
|
|
|
67
67
|
# Send deprecation notices to registered listeners
|
|
68
68
|
config.active_support.deprecation = :notify
|
|
69
|
-
|
|
70
|
-
# Log the query plan for queries taking more than this (works
|
|
71
|
-
# with SQLite, MySQL, and PostgreSQL)
|
|
72
|
-
# config.active_record.auto_explain_threshold_in_seconds = 0.5
|
|
73
69
|
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/test/dummy/config/routes.rb
CHANGED
|
File without changes
|
data/test/dummy/config.ru
CHANGED
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class DeviseOtpAddToUsers < ActiveRecord::Migration
|
|
1
|
+
class DeviseOtpAddToUsers < ActiveRecord::Migration[5.0]
|
|
2
2
|
def self.up
|
|
3
3
|
change_table :users do |t|
|
|
4
4
|
t.string :otp_auth_secret
|
|
@@ -18,7 +18,7 @@ class DeviseOtpAddToUsers < ActiveRecord::Migration
|
|
|
18
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,
|
|
Binary file
|
|
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
|
|
@@ -44,6 +44,23 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
|
44
44
|
assert_equal root_path, current_path
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
test 'a user should be able to download its recovery codes' do
|
|
48
|
+
# log in 1fa
|
|
49
|
+
user = enable_otp_and_sign_in
|
|
50
|
+
otp_challenge_for user
|
|
51
|
+
|
|
52
|
+
visit user_otp_token_path
|
|
53
|
+
assert_equal user_otp_token_path, current_path
|
|
54
|
+
|
|
55
|
+
enable_chrome_headless_downloads(page, "/tmp/devise-otp")
|
|
56
|
+
|
|
57
|
+
DownloadHelper.wait_for_download(count: 1) do
|
|
58
|
+
click_link('Download recovery codes')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
assert_equal 1, DownloadHelper.downloads.size
|
|
62
|
+
end
|
|
63
|
+
|
|
47
64
|
test 'trusted status should expire' do
|
|
48
65
|
# log in 1fa
|
|
49
66
|
user = enable_otp_and_sign_in
|
|
@@ -61,5 +78,4 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
|
61
78
|
|
|
62
79
|
assert_equal user_otp_credential_path, current_path
|
|
63
80
|
end
|
|
64
|
-
|
|
65
|
-
end
|
|
81
|
+
end
|
|
@@ -21,7 +21,6 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
test 'a user should be prompted for credentials when the credentials_refresh time is expired' do
|
|
24
|
-
|
|
25
24
|
sign_user_in
|
|
26
25
|
visit user_otp_token_path
|
|
27
26
|
assert_equal user_otp_token_path, current_path
|
|
@@ -45,7 +44,6 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
|
45
44
|
fill_in 'user_refresh_password', :with => '12345678'
|
|
46
45
|
click_button 'Continue...'
|
|
47
46
|
assert_equal user_otp_token_path, current_path
|
|
48
|
-
|
|
49
47
|
end
|
|
50
48
|
|
|
51
49
|
test 'a user should NOT be able to access their OTP settings unless refreshing' do
|
|
@@ -63,20 +61,7 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
|
63
61
|
assert_equal refresh_user_otp_credential_path, current_path
|
|
64
62
|
end
|
|
65
63
|
|
|
66
|
-
test 'user should be
|
|
67
|
-
enable_otp_and_sign_in_with_otp
|
|
68
|
-
|
|
69
|
-
sleep(2)
|
|
70
|
-
visit user_otp_token_path
|
|
71
|
-
assert_equal refresh_user_otp_credential_path, current_path
|
|
72
|
-
|
|
73
|
-
fill_in 'user_refresh_password', :with => '12345678'
|
|
74
|
-
click_button 'Continue...'
|
|
75
|
-
|
|
76
|
-
assert_equal refresh_user_otp_credential_path, current_path
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
test 'user should be finally be able to access their settings, if they provide both a password and a valid OTP token' do
|
|
64
|
+
test 'user should be finally be able to access their settings, and just password is enough' do
|
|
80
65
|
user = enable_otp_and_sign_in_with_otp
|
|
81
66
|
|
|
82
67
|
sleep(2)
|
|
@@ -84,23 +69,8 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
|
84
69
|
assert_equal refresh_user_otp_credential_path, current_path
|
|
85
70
|
|
|
86
71
|
fill_in 'user_refresh_password', :with => '12345678'
|
|
87
|
-
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
|
88
72
|
click_button 'Continue...'
|
|
89
73
|
|
|
90
74
|
assert_equal user_otp_token_path, current_path
|
|
91
75
|
end
|
|
92
|
-
|
|
93
|
-
test 'and rejected when the token is blank or null' do
|
|
94
|
-
user = enable_otp_and_sign_in_with_otp
|
|
95
|
-
|
|
96
|
-
sleep(2)
|
|
97
|
-
visit user_otp_token_path
|
|
98
|
-
assert_equal refresh_user_otp_credential_path, current_path
|
|
99
|
-
|
|
100
|
-
fill_in 'user_refresh_password', :with => '12345678'
|
|
101
|
-
fill_in 'user_token', :with => ''
|
|
102
|
-
click_button 'Continue...'
|
|
103
|
-
|
|
104
|
-
assert_equal refresh_user_otp_credential_path, current_path
|
|
105
|
-
end
|
|
106
|
-
end
|
|
76
|
+
end
|
|
@@ -10,12 +10,12 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
|
10
10
|
test 'a new user should be able to sign in without using their token' do
|
|
11
11
|
create_full_user
|
|
12
12
|
|
|
13
|
-
visit
|
|
13
|
+
visit posts_path
|
|
14
14
|
fill_in 'user_email', :with => 'user@email.invalid'
|
|
15
15
|
fill_in 'user_password', :with => '12345678'
|
|
16
16
|
page.has_content?('Log in') ? click_button('Log in') : click_button('Sign in')
|
|
17
17
|
|
|
18
|
-
assert_equal
|
|
18
|
+
assert_equal posts_path, current_path
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
test 'a new user, just signed in, should be able to sign in and enable their OTP authentication' do
|
|
@@ -84,4 +84,4 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
|
84
84
|
User.otp_authentication_timeout = old_timeout
|
|
85
85
|
assert_equal new_user_session_path, current_path
|
|
86
86
|
end
|
|
87
|
-
end
|
|
87
|
+
end
|
|
@@ -3,13 +3,11 @@ require 'integration_tests_helper'
|
|
|
3
3
|
|
|
4
4
|
class TokenTest < ActionDispatch::IntegrationTest
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
def teardown
|
|
8
7
|
Capybara.reset_sessions!
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
test 'disabling OTP after successfully enabling' do
|
|
12
|
-
|
|
13
11
|
# log in 1fa
|
|
14
12
|
user = enable_otp_and_sign_in
|
|
15
13
|
assert_equal user_otp_credential_path, current_path
|
|
@@ -29,6 +27,5 @@ class TokenTest < ActionDispatch::IntegrationTest
|
|
|
29
27
|
sign_user_in(user)
|
|
30
28
|
|
|
31
29
|
assert_equal root_path, current_path
|
|
32
|
-
|
|
33
30
|
end
|
|
34
|
-
end
|
|
31
|
+
end
|
data/test/model_tests_helper.rb
CHANGED
|
File without changes
|