devise-otp 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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