devise-otp 0.2.2 → 0.4.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.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +36 -0
  3. data/CHANGELOG.md +17 -0
  4. data/Gemfile +1 -22
  5. data/README.md +42 -75
  6. data/app/assets/javascripts/devise-otp.js +1 -0
  7. data/app/assets/javascripts/qrcode.js +609 -0
  8. data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +102 -0
  9. data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +112 -0
  10. data/app/views/devise/otp_credentials/refresh.html.erb +19 -0
  11. data/app/views/devise/otp_credentials/show.html.erb +31 -0
  12. data/app/views/devise/otp_tokens/_token_secret.html.erb +23 -0
  13. data/app/views/devise/otp_tokens/_trusted_devices.html.erb +12 -0
  14. data/app/views/devise/otp_tokens/recovery.html.erb +21 -0
  15. data/app/views/devise/otp_tokens/recovery_codes.text.erb +3 -0
  16. data/app/views/devise/otp_tokens/show.html.erb +21 -0
  17. data/config/locales/en.yml +8 -8
  18. data/devise-otp.gemspec +15 -10
  19. data/docs/QR_CODES.md +48 -0
  20. data/lib/devise-otp/version.rb +2 -2
  21. data/lib/devise-otp.rb +12 -11
  22. data/lib/devise_otp_authenticatable/controllers/helpers.rb +22 -15
  23. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +6 -7
  24. data/lib/devise_otp_authenticatable/engine.rb +22 -13
  25. data/lib/devise_otp_authenticatable/hooks/sessions.rb +8 -7
  26. data/lib/devise_otp_authenticatable/hooks.rb +1 -1
  27. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +14 -13
  28. data/lib/devise_otp_authenticatable/routes.rb +2 -5
  29. data/lib/generators/active_record/templates/migration.rb +1 -1
  30. data/lib/generators/devise_otp/install_generator.rb +8 -5
  31. data/lib/generators/devise_otp/views_generator.rb +2 -3
  32. data/test/dummy/app/assets/config/manifest.js +2 -0
  33. data/test/dummy/app/assets/javascripts/application.js +1 -0
  34. data/test/dummy/app/controllers/application_controller.rb +1 -1
  35. data/test/dummy/app/controllers/posts_controller.rb +2 -0
  36. data/test/dummy/app/models/user.rb +1 -1
  37. data/test/dummy/config/application.rb +2 -1
  38. data/test/dummy/config/database.yml +1 -1
  39. data/test/dummy/config/environments/development.rb +0 -7
  40. data/test/dummy/config/environments/production.rb +0 -4
  41. data/test/dummy/db/migrate/20130125101430_create_users.rb +1 -1
  42. data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +1 -1
  43. data/test/dummy/db/migrate/20130131142320_create_posts.rb +1 -1
  44. data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +2 -2
  45. data/test/dummy/script/rails +0 -0
  46. data/test/integration/persistence_test.rb +81 -0
  47. data/test/integration/refresh_test.rb +2 -18
  48. data/test/integration/sign_in_test.rb +13 -3
  49. data/test/integration/token_test.rb +1 -4
  50. data/test/integration_tests_helper.rb +7 -3
  51. data/test/models/otp_authenticatable_test.rb +14 -9
  52. data/test/orm/active_record.rb +3 -1
  53. data/test/test_helper.rb +71 -2
  54. metadata +132 -25
  55. data/.travis.yml +0 -12
  56. data/app/controllers/devise_otp/credentials_controller.rb +0 -106
  57. data/app/controllers/devise_otp/tokens_controller.rb +0 -105
  58. data/app/views/devise_otp/credentials/refresh.html.erb +0 -20
  59. data/app/views/devise_otp/credentials/show.html.erb +0 -23
  60. data/app/views/devise_otp/tokens/_token_secret.html.erb +0 -17
  61. data/app/views/devise_otp/tokens/_trusted_devices.html.erb +0 -10
  62. data/app/views/devise_otp/tokens/recovery.html.erb +0 -21
  63. data/app/views/devise_otp/tokens/show.html.erb +0 -19
  64. data/lib/devise_otp_authenticatable/mapping.rb +0 -19
@@ -1,33 +1,32 @@
1
1
  module DeviseOtpAuthenticatable
2
2
  module Controllers
3
-
4
3
  module UrlHelpers
5
4
 
6
5
  def recovery_otp_token_for(resource_or_scope, opts = {})
7
- scope = Devise::Mapping.find_scope!(resource_or_scope)
6
+ scope = ::Devise::Mapping.find_scope!(resource_or_scope)
8
7
  send("recovery_#{scope}_otp_token_path", opts)
9
8
  end
10
9
 
11
10
  def refresh_otp_credential_path_for(resource_or_scope, opts = {})
12
- scope = Devise::Mapping.find_scope!(resource_or_scope)
11
+ scope = ::Devise::Mapping.find_scope!(resource_or_scope)
13
12
  send("refresh_#{scope}_otp_credential_path", opts)
14
13
  end
15
14
 
16
15
  def persistence_otp_token_path_for(resource_or_scope, opts = {})
17
- scope = Devise::Mapping.find_scope!(resource_or_scope)
16
+ scope = ::Devise::Mapping.find_scope!(resource_or_scope)
18
17
  send("persistence_#{scope}_otp_token_path", opts)
19
18
  end
20
19
 
21
20
  def otp_token_path_for(resource_or_scope, opts = {})
22
- scope = Devise::Mapping.find_scope!(resource_or_scope)
21
+ scope = ::Devise::Mapping.find_scope!(resource_or_scope)
23
22
  send("#{scope}_otp_token_path", opts)
24
23
  end
25
24
 
26
25
  def otp_credential_path_for(resource_or_scope, opts = {})
27
- scope = Devise::Mapping.find_scope!(resource_or_scope)
26
+ scope = ::Devise::Mapping.find_scope!(resource_or_scope)
28
27
  send("#{scope}_otp_credential_path", opts)
29
28
  end
30
29
 
31
30
  end
32
31
  end
33
- end
32
+ end
@@ -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
- # extend mapping with after_initialize because is not reloaded
19
- config.after_initialize do
20
- Devise::Mapping.send :include, DeviseOtpAuthenticatable::Mapping
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
- alias_method_chain :create, :otp
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
- resource.otp_enabled && !is_otp_trusted_device_for?(resource)
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
@@ -5,7 +5,7 @@ module DeviseOtpAuthenticatable
5
5
 
6
6
  class << self
7
7
  def apply
8
- Devise::SessionsController.send(:include, Hooks::Sessions)
8
+ ::Devise::SessionsController.send(:include, Hooks::Sessions)
9
9
  end
10
10
  end
11
11
 
@@ -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, :otp_uri_application, :otp_recovery_tokens)
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
- "#{email}/#{self.class.otp_uri_application || Rails.application.class.parent_name}"
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
- update(:otp_enabled => false, :otp_time_drift => 0,
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,11 +62,15 @@ module Devise::Models
61
62
  end
62
63
 
63
64
  def enable_otp!
65
+ if otp_persistence_seed.nil?
66
+ reset_otp_credentials!
67
+ end
68
+
64
69
  update!(:otp_enabled => true, :otp_enabled_on => Time.now)
65
70
  end
66
71
 
67
72
  def disable_otp!
68
- update!(:otp_enabled => false, :otp_enabled_on => nil, :otp_time_drift => 0)
73
+ update!(:otp_enabled => false, :otp_enabled_on => nil)
69
74
  end
70
75
 
71
76
  def generate_otp_challenge!(expires = nil)
@@ -89,12 +94,8 @@ module Devise::Models
89
94
  alias_method :valid_otp_token?, :validate_otp_token
90
95
 
91
96
  def validate_otp_time_token(token)
92
- if drift = validate_otp_token_with_drift(token)
93
- update_column(:otp_time_drift, drift)
94
- true
95
- else
96
- false
97
- end
97
+ return false if token.blank?
98
+ validate_otp_token_with_drift(token)
98
99
  end
99
100
  alias_method :valid_otp_time_token?, :validate_otp_time_token
100
101
 
@@ -121,7 +122,7 @@ module Devise::Models
121
122
 
122
123
  # should be centered around saved drift
123
124
  (-self.class.otp_drift_window..self.class.otp_drift_window).any? {|drift|
124
- (time_based_otp.verify(token, Time.now.ago(30 * drift))) }
125
+ (time_based_otp.verify(token, at: Time.now.ago(30 * drift))) }
125
126
  end
126
127
 
127
128
  def generate_otp_persistence_seed
@@ -134,4 +135,4 @@ module Devise::Models
134
135
  end
135
136
 
136
137
  end
137
- end
138
+ end
@@ -1,12 +1,9 @@
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
9
  :path => mapping.path_names[:token], :controller => controllers[:otp_tokens] do
@@ -23,10 +20,10 @@ module ActionDispatch::Routing
23
20
  resource :credential, :only => [:show, :update],
24
21
  :path => mapping.path_names[:credentials], :controller => controllers[:otp_credentials] do
25
22
 
26
- get :refresh, :action => 'get_refresh'
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
@@ -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, :otp_time_drift, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
23
+ :otp_challenge_expires, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
24
24
 
25
25
  end
26
26
  end
@@ -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.recovery_tokens = 10
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 this application, to be added to the provisioning
40
- # url as '<user_email>/application_name' (defaults to the Rails application class)
41
- #config.otp_uri_application = 'my_application'
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/devise_otp", __FILE__)
12
+ source_root File.expand_path("../../../../app/views", __FILE__)
13
13
  def copy_views
14
- view_directory :credentials, 'app/views/devise_otp/credentials'
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
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
@@ -11,3 +11,4 @@
11
11
  // GO AFTER THE REQUIRES BELOW.
12
12
  //
13
13
  //= require_tree .
14
+ //= require devise-otp
@@ -1,4 +1,4 @@
1
1
  class ApplicationController < ActionController::Base
2
2
  protect_from_forgery
3
- before_filter :authenticate_user!
3
+ before_action :authenticate_user!
4
4
  end
@@ -1,4 +1,6 @@
1
1
  class PostsController < ApplicationController
2
+ before_action :authenticate_user!
3
+
2
4
  # GET /posts
3
5
  # GET /posts.json
4
6
  def index
@@ -12,7 +12,7 @@ class User < PARENT_MODEL_CLASS
12
12
  end
13
13
 
14
14
  devise :otp_authenticatable, :database_authenticatable, :registerable,
15
- :recoverable, :rememberable, :trackable, :validatable
15
+ :trackable, :validatable
16
16
 
17
17
  # Setup accessible (or protected) attributes for your model
18
18
  #attr_accessible :otp_enabled, :otp_mandatory, :as => :otp_privileged
@@ -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
-
@@ -14,7 +14,7 @@ development:
14
14
  # Do not set this db to the same as development or production.
15
15
  test:
16
16
  adapter: sqlite3
17
- database: ":memory:"
17
+ database: db/test.sqlite3
18
18
  pool: 5
19
19
  timeout: 5000
20
20
 
@@ -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
@@ -1,4 +1,4 @@
1
- class CreateUsers < ActiveRecord::Migration
1
+ class CreateUsers < ActiveRecord::Migration[5.0]
2
2
  def change
3
3
  create_table :users do |t|
4
4
  t.string :name
@@ -1,4 +1,4 @@
1
- class AddDeviseToUsers < ActiveRecord::Migration
1
+ class AddDeviseToUsers < ActiveRecord::Migration[5.0]
2
2
  def self.up
3
3
  change_table(:users) do |t|
4
4
  ## Database authenticatable
@@ -1,4 +1,4 @@
1
- class CreatePosts < ActiveRecord::Migration
1
+ class CreatePosts < ActiveRecord::Migration[5.0]
2
2
  def change
3
3
  create_table :posts do |t|
4
4
  t.string :title
@@ -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,
File without changes
@@ -0,0 +1,81 @@
1
+ require 'test_helper'
2
+ require 'integration_tests_helper'
3
+
4
+ class PersistenceTest < ActionDispatch::IntegrationTest
5
+
6
+ def setup
7
+ @old_persistence = User.otp_trust_persistence
8
+ User.otp_trust_persistence = 3.seconds
9
+ end
10
+
11
+ def teardown
12
+ User.otp_trust_persistence = @old_persistence
13
+ Capybara.reset_sessions!
14
+ end
15
+
16
+ test 'a user should be requested the otp challenge every log in' do
17
+ # log in 1fa
18
+ user = enable_otp_and_sign_in
19
+ otp_challenge_for user
20
+
21
+ visit user_otp_token_path
22
+ assert_equal user_otp_token_path, current_path
23
+
24
+ sign_out
25
+ sign_user_in
26
+
27
+ assert_equal user_otp_credential_path, current_path
28
+ end
29
+
30
+ test 'a user should be able to set their browser as trusted' do
31
+ # log in 1fa
32
+ user = enable_otp_and_sign_in
33
+ otp_challenge_for user
34
+
35
+ visit user_otp_token_path
36
+ assert_equal user_otp_token_path, current_path
37
+
38
+ click_link('Trust this browser')
39
+ assert_text 'Your browser is trusted.'
40
+ sign_out
41
+
42
+ sign_user_in
43
+
44
+ assert_equal root_path, current_path
45
+ end
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
+
64
+ test 'trusted status should expire' do
65
+ # log in 1fa
66
+ user = enable_otp_and_sign_in
67
+ otp_challenge_for user
68
+
69
+ visit user_otp_token_path
70
+ assert_equal user_otp_token_path, current_path
71
+
72
+ click_link('Trust this browser')
73
+ assert_text 'Your browser is trusted.'
74
+ sign_out
75
+
76
+ sleep User.otp_trust_persistence.to_i + 1
77
+ sign_user_in
78
+
79
+ assert_equal user_otp_credential_path, current_path
80
+ 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 asked their OTP challenge in order to refresh, if they have OTP' do
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,9 +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
- 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 new_user_session_path
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 root_path, current_path
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
@@ -50,6 +50,16 @@ class SignInTest < ActionDispatch::IntegrationTest
50
50
  assert_equal new_user_session_path, current_path
51
51
  end
52
52
 
53
+ test 'fail blank token authentication' do
54
+ enable_otp_and_sign_in
55
+ assert_equal user_otp_credential_path, current_path
56
+
57
+ fill_in 'user_token', :with => ''
58
+ click_button 'Submit Token'
59
+
60
+ assert_equal user_otp_credential_path, current_path
61
+ end
62
+
53
63
  test 'successful token authentication' do
54
64
  user = enable_otp_and_sign_in
55
65
 
@@ -74,4 +84,4 @@ class SignInTest < ActionDispatch::IntegrationTest
74
84
  User.otp_authentication_timeout = old_timeout
75
85
  assert_equal new_user_session_path, current_path
76
86
  end
77
- 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
@@ -1,4 +1,5 @@
1
1
  class ActionDispatch::IntegrationTest
2
+ include Warden::Test::Helpers
2
3
 
3
4
  def warden
4
5
  request.env['warden']
@@ -22,7 +23,6 @@ class ActionDispatch::IntegrationTest
22
23
  end
23
24
  end
24
25
 
25
-
26
26
  def enable_otp_and_sign_in
27
27
  user = create_full_user
28
28
  sign_user_in(user)
@@ -36,6 +36,11 @@ class ActionDispatch::IntegrationTest
36
36
  user
37
37
  end
38
38
 
39
+ def otp_challenge_for(user)
40
+ fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
41
+ click_button 'Submit Token'
42
+ end
43
+
39
44
  def disable_otp
40
45
  visit user_otp_token_path
41
46
  uncheck 'user_otp_enabled'
@@ -43,7 +48,7 @@ class ActionDispatch::IntegrationTest
43
48
  end
44
49
 
45
50
  def sign_out
46
- Capybara.reset_sessions!
51
+ logout :user
47
52
  end
48
53
 
49
54
  def sign_user_in(user = nil)
@@ -57,5 +62,4 @@ class ActionDispatch::IntegrationTest
57
62
  user
58
63
  end
59
64
 
60
-
61
65
  end