devise-otp 1.1.0 → 2.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +30 -0
  3. data/.github/workflows/ci.yml +4 -2
  4. data/.rubocop.yml +18 -0
  5. data/Appraisals +4 -17
  6. data/CHANGELOG.md +59 -1
  7. data/Gemfile +7 -1
  8. data/README.md +37 -14
  9. data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +30 -7
  10. data/app/controllers/devise_otp/devise/otp_persistence_controller.rb +60 -0
  11. data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +2 -37
  12. data/app/views/devise/otp_credentials/refresh.html.erb +6 -6
  13. data/app/views/devise/otp_credentials/show.html.erb +10 -13
  14. data/app/views/devise/otp_tokens/_token_secret.html.erb +9 -9
  15. data/app/views/devise/otp_tokens/_trusted_devices.html.erb +7 -7
  16. data/app/views/devise/otp_tokens/edit.html.erb +9 -9
  17. data/app/views/devise/otp_tokens/recovery.html.erb +8 -6
  18. data/app/views/devise/otp_tokens/show.html.erb +6 -6
  19. data/bin/appraisal +16 -0
  20. data/bin/erb_lint +16 -0
  21. data/bin/rake +16 -0
  22. data/bin/rubocop +16 -0
  23. data/config/locales/{en.yml → devise-otp.en.yml} +10 -8
  24. data/devise-otp.gemspec +2 -2
  25. data/gemfiles/rails_8.0.gemfile +5 -1
  26. data/gemfiles/{rails_7.1.gemfile → rails_8.1.gemfile} +7 -7
  27. data/lib/devise/strategies/database_authenticatable.rb +4 -17
  28. data/lib/devise-otp/version.rb +1 -1
  29. data/lib/devise-otp.rb +1 -2
  30. data/lib/devise_otp_authenticatable/controllers/helpers.rb +1 -2
  31. data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +1 -2
  32. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +7 -2
  33. data/lib/devise_otp_authenticatable/hooks/refreshable.rb +0 -1
  34. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +18 -12
  35. data/lib/devise_otp_authenticatable/routes.rb +7 -7
  36. data/test/dummy/app/assets/stylesheets/application.css +15 -1
  37. data/test/dummy/app/controllers/admin_posts_controller.rb +0 -1
  38. data/test/dummy/app/controllers/base_controller.rb +0 -2
  39. data/test/dummy/app/controllers/non_otp_posts_controller.rb +0 -1
  40. data/test/dummy/app/models/admin.rb +0 -1
  41. data/test/dummy/app/models/lockable_user.rb +8 -0
  42. data/test/dummy/app/models/non_otp_user.rb +0 -1
  43. data/test/dummy/app/models/rememberable_user.rb +8 -0
  44. data/test/dummy/app/views/layouts/application.html.erb +4 -2
  45. data/test/dummy/app/views/posts/show.html.erb +0 -1
  46. data/test/dummy/app/views/shared/_navbar.html.erb +23 -0
  47. data/test/dummy/config/application.rb +3 -0
  48. data/test/dummy/config/initializers/devise.rb +2 -2
  49. data/test/dummy/config/routes.rb +3 -0
  50. data/test/dummy/db/migrate/20250731000001_create_lockable_users.rb +9 -0
  51. data/test/dummy/db/migrate/20250731000002_add_devise_to_lockable_users.rb +52 -0
  52. data/test/dummy/db/migrate/20250731000003_devise_otp_add_to_lockable_users.rb +28 -0
  53. data/test/dummy/db/migrate/20250817221304_create_rememberable_users.rb +50 -0
  54. data/test/dummy/db/migrate/20250818030305_add_devise_otp_to_rememberable_users.rb +28 -0
  55. data/test/dummy/db/schema.rb +110 -44
  56. data/test/integration/disable_token_test.rb +0 -2
  57. data/test/integration/enable_otp_form_test.rb +12 -1
  58. data/test/integration/lockable_test.rb +143 -0
  59. data/test/integration/non_otp_user_models_test.rb +0 -2
  60. data/test/integration/otp_drift_test.rb +35 -0
  61. data/test/integration/persistence_test.rb +96 -5
  62. data/test/integration/refresh_test.rb +23 -14
  63. data/test/integration/rememberable_test.rb +143 -0
  64. data/test/integration/reset_token_test.rb +0 -2
  65. data/test/integration/sign_in_test.rb +15 -8
  66. data/test/integration/trackable_test.rb +0 -2
  67. data/test/integration_tests_helper.rb +22 -1
  68. data/test/models/otp_authenticatable_test.rb +48 -11
  69. data/test/test_helper.rb +1 -0
  70. metadata +26 -9
  71. data/gemfiles/rails_7.2.gemfile +0 -17
@@ -1,13 +1,13 @@
1
- <h2><%= I18n.t('title', :scope => 'devise.otp.otp_tokens.recovery') %></h2>
2
- <p><%= I18n.t('explain', :scope => 'devise.otp.otp_tokens.recovery') %></p>
1
+ <h2><%= t('title', scope: 'devise.otp.otp_tokens.recovery') %></h2>
2
+ <p><%= t('explain', scope: 'devise.otp.otp_tokens.recovery') %></p>
3
3
 
4
4
  <table>
5
5
  <caption>
6
6
  <thead>
7
- <tr>
8
- <th><%= I18n.t('sequence', :scope => 'devise.otp.otp_tokens.recovery') %></th>
9
- <th><%= I18n.t('code', :scope => 'devise.otp.otp_tokens.recovery') %></th>
10
- </tr>
7
+ <tr>
8
+ <th><%= t('sequence', scope: 'devise.otp.otp_tokens.recovery') %></th>
9
+ <th><%= t('code', scope: 'devise.otp.otp_tokens.recovery') %></th>
10
+ </tr>
11
11
  </thead>
12
12
  <tbody>
13
13
  <%- resource.next_otp_recovery_tokens.each do |seq, code| %>
@@ -19,3 +19,5 @@
19
19
  </tbody>
20
20
  </caption>
21
21
  </table>
22
+
23
+ <%= link_to "Back", otp_token_path_for(resource) %>
@@ -1,14 +1,14 @@
1
- <h2><%= I18n.t('title', :scope => 'devise.otp.otp_tokens') %></h2>
1
+ <h2><%= t('title', scope: 'devise.otp.otp_tokens') %></h2>
2
2
 
3
- <p><strong>Status:</strong> <%= resource.otp_enabled? ? "Enabled" : "Disabled" %></p>
3
+ <p><strong>Status:</strong> <%= t(resource.otp_enabled? ? :enabled : :disabled, scope: 'devise.otp.general') %></p>
4
4
 
5
5
  <%- if resource.otp_enabled? %>
6
- <%= render :partial => 'token_secret' if resource.otp_enabled? %>
7
- <%= render :partial => 'trusted_devices' if trusted_devices_enabled? %>
6
+ <%= render partial: 'token_secret' if resource.otp_enabled? %>
7
+ <%= render partial: 'trusted_devices' if trusted_devices_enabled? %>
8
8
 
9
9
  <% unless otp_mandatory_on?(resource) %>
10
- <%= button_to I18n.t('disable_link', :scope => 'devise.otp.otp_tokens'), otp_token_path_for(resource), :method => :delete, :data => { "turbo-method": "DELETE" } %>
10
+ <%= button_to t('disable_link', scope: 'devise.otp.otp_tokens'), otp_token_path_for(resource), method: :delete, data: { turbo_method: :delete } %>
11
11
  <% end %>
12
12
  <% else %>
13
- <%= link_to I18n.t('enable_link', :scope => 'devise.otp.otp_tokens'), edit_otp_token_path_for(resource) %>
13
+ <%= link_to t('enable_link', scope: 'devise.otp.otp_tokens'), edit_otp_token_path_for(resource) %>
14
14
  <% end %>
data/bin/appraisal ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'appraisal' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("appraisal", "appraisal")
data/bin/erb_lint ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'erb_lint' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("erb_lint", "erb_lint")
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rake", "rake")
data/bin/rubocop ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rubocop", "rubocop")
@@ -2,7 +2,8 @@ en:
2
2
  devise:
3
3
  otp:
4
4
  general:
5
- remember: Remember me
5
+ enabled: 'Enabled'
6
+ disabled: 'Disabled'
6
7
  submit_token:
7
8
  title: 'Check Token'
8
9
  explain: "You're getting this because you enabled Two-Factor Authentication on your account"
@@ -18,7 +19,7 @@ en:
18
19
  invalid_refresh: 'Sorry, you provided the wrong credentials.'
19
20
  credentials_refresh:
20
21
  title: 'Please enter your password again.'
21
- explain: 'In order to ensure this is safe, please enter your password again.'
22
+ explain: 'In order to ensure this is safe, please enter your password again.'
22
23
  go_on: 'Continue...'
23
24
  identity: 'Identity:'
24
25
  token: 'Your Two-Factor Authentication token'
@@ -31,22 +32,23 @@ en:
31
32
  enable_link: 'Enable Two-Factor Authentication'
32
33
  disable_link: 'Disable Two-Factor Authentication'
33
34
  reset_link: 'Reset Token Secret'
34
- reset_explain: 'Resetting your token secret will temporarilly disable Two-Factor Authentication.'
35
+ reset_explain: 'Resetting your token secret will temporarily disable Two-Factor Authentication.'
35
36
  reset_explain_warn: 'To re-enable Two-Factor Authentication, you will need to re-enroll your mobile device with the new token secret.'
36
37
  successfully_updated: 'Your Two-Factor Authentication settings have been updated.'
37
38
  could_not_confirm: 'The Confirmation Code you entered did not match the QR code shown below.'
38
39
  successfully_disabled_otp: 'Two-Factor Authentication has been disabled.'
39
40
  successfully_reset_otp: 'Your token secret has been reset. Please confirm your new token secret below.'
40
- successfully_set_persistence: 'Your device is now trusted.'
41
- successfully_cleared_persistence: 'Your device has been removed from the list of trusted devices.'
42
- successfully_reset_persistence: 'Your list of trusted devices has been cleared.'
43
41
  recovery:
44
42
  title: 'Your Emergency Recovery Codes'
45
- explain: 'Take note or print these recovery codes. The will allow you to log back in in case your token device is lost, stolen, or unavailable.'
43
+ explain: 'Take note or print these recovery codes. They will allow you to log in if your token device is lost, stolen, or unavailable.'
46
44
  sequence: 'Sequence'
47
45
  code: 'Recovery Code'
48
- codes_list: 'Here is the list of your recovery codes'
46
+ codes_list: 'View recovery codes'
49
47
  download_codes: 'Download recovery codes'
48
+ otp_persistence:
49
+ successfully_set_persistence: 'Your device is now trusted.'
50
+ successfully_cleared_persistence: 'Your device has been removed from the list of trusted devices.'
51
+ successfully_reset_persistence: 'Your list of trusted devices has been cleared.'
50
52
  edit_otp_token:
51
53
  title: 'Enable Two-factor Authentication'
52
54
  explain: 'Two-Factor Authentication adds an additional layer of security to your account. When logging in you will be asked for a code that you can generate on a physical device, like your phone.'
data/devise-otp.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.required_ruby_version = ">= 3.2.0"
19
19
 
20
20
  gem.add_dependency "rails", ">= 7.1"
21
- gem.add_dependency "devise", ">= 4.8.0", "< 5.0"
21
+ gem.add_dependency "devise", ">= 4.8.0", "< 6.0"
22
22
  gem.add_dependency "rotp", ">= 2.0.0"
23
- gem.add_dependency "rqrcode", "~> 2.0"
23
+ gem.add_dependency "rqrcode", "~> 3.0"
24
24
  end
@@ -11,7 +11,11 @@ gem "rdoc"
11
11
  gem "shoulda"
12
12
  gem "sprockets-rails"
13
13
  gem "sqlite3", "~> 2.1"
14
- gem "standardrb"
14
+ gem "timecop", "~> 0.9.10"
15
+ gem "standardrb", require: false
16
+ gem "rubocop-rails-omakase", require: false
17
+ gem "erb_lint", require: false
18
+ gem "debug"
15
19
  gem "rails", "~> 8.0.0"
16
20
 
17
21
  gemspec path: "../"
@@ -10,12 +10,12 @@ gem "rake"
10
10
  gem "rdoc"
11
11
  gem "shoulda"
12
12
  gem "sprockets-rails"
13
- gem "sqlite3", "~> 1.5.0"
14
- gem "standardrb"
15
- gem "rails", "~> 7.1.0"
16
-
17
- install_if -> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0") } do
18
- gem "logger"
19
- end
13
+ gem "sqlite3", "~> 2.1"
14
+ gem "timecop", "~> 0.9.10"
15
+ gem "standardrb", require: false
16
+ gem "rubocop-rails-omakase", require: false
17
+ gem "erb_lint", require: false
18
+ gem "debug"
19
+ gem "rails", "~> 8.1.0"
20
20
 
21
21
  gemspec path: "../"
@@ -10,11 +10,11 @@ module Devise
10
10
  resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
11
11
  hashed = false
12
12
 
13
- if validate(resource){ hashed = true; resource.valid_password?(password) }
13
+ if validate(resource) { hashed = true; resource.valid_password?(password) }
14
14
  if otp_challenge_required_on?(resource)
15
15
  # Redirect to challenge
16
16
  challenge = resource.generate_otp_challenge!
17
- redirect!(otp_challenge_url, {:challenge => challenge})
17
+ redirect!(otp_challenge_path, {challenge: challenge, remember_me: remember_me?})
18
18
  else
19
19
  # Sign in user as usual
20
20
  remember_me(resource)
@@ -41,21 +41,8 @@ module Devise
41
41
  resource.respond_to?(:otp_enabled?) && resource.otp_enabled?
42
42
  end
43
43
 
44
- def otp_challenge_url
45
- if Rails.env.development? || Rails.env.test?
46
- host = "#{request.host}:#{request.port}"
47
- else
48
- host = "#{request.host}"
49
- end
50
-
51
- path_fragments = ["otp", mapping.path_names[:credentials]]
52
- if mapping.fullpath == "/"
53
- path = mapping.fullpath + path_fragments.join("/")
54
- else
55
- path = path_fragments.prepend(mapping.fullpath).join("/")
56
- end
57
-
58
- request.protocol + host + path
44
+ def otp_challenge_path
45
+ Rails.application.routes.url_helpers.public_send("#{mapping.singular}_otp_credential_path")
59
46
  end
60
47
  end
61
48
  end
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module OTP
3
- VERSION = "1.1.0"
3
+ VERSION = "2.1.0"
4
4
  end
5
5
  end
data/lib/devise-otp.rb CHANGED
@@ -45,7 +45,7 @@ module Devise
45
45
  @@otp_trust_persistence = 30.days
46
46
 
47
47
  mattr_accessor :otp_drift_window
48
- @@otp_drift_window = 3 # in minutes
48
+ @@otp_drift_window = 3
49
49
 
50
50
  #
51
51
  # if the user wants to change Otp settings,
@@ -75,7 +75,6 @@ module Devise
75
75
 
76
76
  module Otp
77
77
  end
78
-
79
78
  end
80
79
 
81
80
  Devise.add_module :otp_authenticatable,
@@ -122,10 +122,9 @@ module DeviseOtpAuthenticatable
122
122
  #
123
123
  def otp_authenticator_token_image(resource)
124
124
  content_tag(:div, class: "qrcode-container") do
125
- raw RQRCode::QRCode.new(resource.otp_provisioning_uri).as_svg(:module_size => 5, :viewbox => true, :use_path => true)
125
+ raw RQRCode::QRCode.new(resource.otp_provisioning_uri).as_svg(module_size: 5, viewbox: true, use_path: true)
126
126
  end
127
127
  end
128
-
129
128
  end
130
129
  end
131
130
  end
@@ -9,7 +9,7 @@ module DeviseOtpAuthenticatable
9
9
  end
10
10
  end
11
11
 
12
- def self.define_helpers(mapping) #:nodoc:
12
+ def self.define_helpers(mapping) # :nodoc:
13
13
  mapping = mapping.name
14
14
 
15
15
  class_eval <<-METHODS, __FILE__, __LINE__ + 1
@@ -33,7 +33,6 @@ module DeviseOtpAuthenticatable
33
33
  def mandatory_otp_missing_on?(resource)
34
34
  otp_mandatory_on?(resource) && !resource.otp_enabled
35
35
  end
36
-
37
36
  end
38
37
  end
39
38
  end
@@ -11,9 +11,14 @@ module DeviseOtpAuthenticatable
11
11
  send("refresh_#{scope}_otp_credential_path", opts)
12
12
  end
13
13
 
14
- def persistence_otp_token_path_for(resource_or_scope, opts = {})
14
+ def otp_persistence_path_for(resource_or_scope, opts = {})
15
15
  scope = ::Devise::Mapping.find_scope!(resource_or_scope)
16
- send("persistence_#{scope}_otp_token_path", opts)
16
+ send("#{scope}_otp_persistence_path", opts)
17
+ end
18
+
19
+ def reset_otp_persistence_path_for(resource_or_scope, opts = {})
20
+ scope = ::Devise::Mapping.find_scope!(resource_or_scope)
21
+ send("reset_#{scope}_otp_persistence_path", opts)
17
22
  end
18
23
 
19
24
  def otp_token_path_for(resource_or_scope, opts = {})
@@ -4,4 +4,3 @@ Warden::Manager.after_set_user except: :fetch do |record, warden, options|
4
4
  warden.session(options[:scope])["credentials_refreshed_at"] = (Time.now + record.class.otp_credentials_refresh)
5
5
  end
6
6
  end
7
-
@@ -19,7 +19,11 @@ module Devise::Models
19
19
  end
20
20
 
21
21
  def time_based_otp
22
- @time_based_otp ||= ROTP::TOTP.new(otp_auth_secret, issuer: (self.class.otp_issuer || Rails.application.class.module_parent_name).to_s)
22
+ @time_based_otp ||= ROTP::TOTP.new(otp_auth_secret, issuer: otp_issuer)
23
+ end
24
+
25
+ def otp_issuer
26
+ (self.class.otp_issuer || Rails.application.class.module_parent_name).to_s
23
27
  end
24
28
 
25
29
  def recovery_otp
@@ -56,13 +60,13 @@ module Devise::Models
56
60
  @recovery_otp = nil
57
61
 
58
62
  self.update!(
59
- :otp_auth_secret => nil,
60
- :otp_recovery_secret => nil,
61
- :otp_persistence_seed => nil,
62
- :otp_session_challenge => nil,
63
- :otp_challenge_expires => nil,
64
- :otp_failed_attempts => 0,
65
- :otp_recovery_counter => 0
63
+ otp_auth_secret: nil,
64
+ otp_recovery_secret: nil,
65
+ otp_persistence_seed: nil,
66
+ otp_session_challenge: nil,
67
+ otp_challenge_expires: nil,
68
+ otp_failed_attempts: 0,
69
+ otp_recovery_counter: 0
66
70
  )
67
71
  end
68
72
 
@@ -116,10 +120,12 @@ module Devise::Models
116
120
  private
117
121
 
118
122
  def validate_otp_token_with_drift(token)
119
- # should be centered around saved drift
120
- (-self.class.otp_drift_window..self.class.otp_drift_window).any? { |drift|
121
- time_based_otp.verify(token, at: Time.now.ago(30 * drift))
122
- }
123
+ verified_token_time = time_based_otp.verify(token,
124
+ drift_behind: self.class.otp_drift_window * 30,
125
+ drift_ahead: self.class.otp_drift_window * 30,
126
+ )
127
+
128
+ verified_token_time.present?
123
129
  end
124
130
 
125
131
  def generate_otp_persistence_seed
@@ -6,22 +6,22 @@ module ActionDispatch::Routing
6
6
  namespace :otp, module: :devise_otp do
7
7
  resource :token, only: [:show, :edit, :update, :destroy],
8
8
  path: mapping.path_names[:token], controller: controllers[:otp_tokens] do
9
- if Devise.otp_trust_persistence
10
- get :persistence, action: "get_persistence"
11
- post :persistence, action: "clear_persistence"
12
- delete :persistence, action: "delete_persistence"
13
- end
14
-
15
9
  get :recovery
16
10
  post :reset
17
11
  end
18
12
 
13
+ if Devise.otp_trust_persistence
14
+ resource :persistence, only: [:show, :create, :destroy],
15
+ path: mapping.path_names[:persistence], controller: controllers[:otp_persistence] do
16
+ delete :reset
17
+ end
18
+ end
19
+
19
20
  resource :credential, only: [:show, :update],
20
21
  path: mapping.path_names[:credentials], controller: controllers[:otp_credentials] do
21
22
  get :refresh, action: "get_refresh"
22
23
  put :refresh, action: "set_refresh"
23
24
  end
24
-
25
25
  end
26
26
  end
27
27
  end
@@ -8,7 +8,21 @@
8
8
  * You're free to add application-wide styles to this file and they'll appear at the top of the
9
9
  * compiled file, but it's generally better to create a new file per style scope.
10
10
  *
11
- *= require devise-otp
12
11
  *= require_self
13
12
  *= require_tree .
14
13
  */
14
+
15
+ @import url("devise-otp.css");
16
+
17
+ nav {
18
+ ul {
19
+ float: right;
20
+ margin: 0;
21
+ display: flex;
22
+ }
23
+ li {
24
+ list-style: none;
25
+ padding: 0.25rem 0.5rem;;
26
+ margin: 0;
27
+ }
28
+ }
@@ -9,5 +9,4 @@ class AdminPostsController < ApplicationController
9
9
  format.json { render json: @posts }
10
10
  end
11
11
  end
12
-
13
12
  end
@@ -1,6 +1,4 @@
1
1
  class BaseController < ApplicationController
2
-
3
2
  def home
4
3
  end
5
-
6
4
  end
@@ -9,5 +9,4 @@ class NonOtpPostsController < ApplicationController
9
9
  format.json { render json: @posts }
10
10
  end
11
11
  end
12
-
13
12
  end
@@ -9,5 +9,4 @@ class Admin < ActiveRecord::Base
9
9
  def self.otp_mandatory
10
10
  true
11
11
  end
12
-
13
12
  end
@@ -0,0 +1,8 @@
1
+ class LockableUser < ActiveRecord::Base
2
+ devise :otp_authenticatable, :database_authenticatable, :registerable,
3
+ :trackable, :validatable, :lockable
4
+
5
+ # Setup accessible (or protected) attributes for your model
6
+ # attr_accessible :otp_enabled, :otp_mandatory, :as => :otp_privileged
7
+ # attr_accessible :email, :password, :password_confirmation, :remember_me
8
+ end
@@ -1,4 +1,3 @@
1
1
  class NonOtpUser < ActiveRecord::Base
2
2
  devise :database_authenticatable, :registerable, :trackable, :validatable
3
-
4
3
  end
@@ -0,0 +1,8 @@
1
+ class RememberableUser < ActiveRecord::Base
2
+ devise :otp_authenticatable, :database_authenticatable, :registerable,
3
+ :trackable, :validatable, :rememberable
4
+
5
+ # Setup accessible (or protected) attributes for your model
6
+ # attr_accessible :otp_enabled, :otp_mandatory, :as => :otp_privileged
7
+ # attr_accessible :email, :password, :password_confirmation, :remember_me
8
+ end
@@ -2,15 +2,17 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Dummy</title>
5
- <%= stylesheet_link_tag "application", :media => "all" %>
5
+ <%= stylesheet_link_tag "application", media: "all" %>
6
6
  <%= javascript_include_tag "application" %>
7
7
  <%= csrf_meta_tags %>
8
8
  </head>
9
9
  <body>
10
10
 
11
+ <%= render "shared/navbar" %>
12
+
11
13
  <div id="alerts">
12
14
  <% flash.keys.each do |key| %>
13
- <%= content_tag :p, flash[key], :id => key %>
15
+ <%= content_tag :p, flash[key], id: key %>
14
16
  <% end %>
15
17
  </div>
16
18
 
@@ -10,6 +10,5 @@
10
10
  <%= @post.body %>
11
11
  </p>
12
12
 
13
-
14
13
  <%= link_to 'Edit', edit_post_path(@post) %> |
15
14
  <%= link_to 'Back', posts_path %>
@@ -0,0 +1,23 @@
1
+ <nav>
2
+ <ul style="float: right;">
3
+ <li><%= link_to "Home", root_path %></li>
4
+ <% if current_user.present? %>
5
+ <li><%= link_to "Posts", posts_path %></li>
6
+ <li><%= link_to "OTP Token", user_otp_token_path %></li>
7
+ <li><%= button_to "Sign Out", destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
8
+ <% elsif current_admin.present? %>
9
+ <li><%= link_to "Posts", admin_posts_path %></li>
10
+ <li><%= link_to "OTP Token", admin_otp_token_path %></li>
11
+ <li><%= button_to "Sign Out", destroy_admin_session_path, method: :delete, data: { turbo_method: :delete } %></li>
12
+ <% elsif current_lockable_user.present? %>
13
+ <li><%= link_to "OTP Token", lockable_user_otp_token_path %></li>
14
+ <li><%= button_to "Sign Out", destroy_lockable_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
15
+ <% elsif current_non_otp_user.present? %>
16
+ <li><%= link_to "Posts", admin_posts_path %></li>
17
+ <li><%= button_to "Sign Out", destroy_non_otp_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
18
+ <% elsif current_rememberable_user.present? %>
19
+ <li><%= link_to "OTP Token", rememberable_user_otp_token_path %></li>
20
+ <li><%= button_to "Sign Out", destroy_rememberable_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
21
+ <% end %>
22
+ </ul >
23
+ </nav>
@@ -56,5 +56,8 @@ module Dummy
56
56
 
57
57
  # Version of your assets, change this if you want to expire all your assets
58
58
  config.assets.version = "1.0"
59
+
60
+ # Default URL for Devise
61
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
59
62
  end
60
63
  end
@@ -143,7 +143,7 @@ Devise.setup do |config|
143
143
  # Defines which strategy will be used to lock an account.
144
144
  # :failed_attempts = Locks an account after a number of failed attempts to sign in.
145
145
  # :none = No lock strategy. You should handle locking by yourself.
146
- # config.lock_strategy = :failed_attempts
146
+ config.lock_strategy = :failed_attempts
147
147
 
148
148
  # Defines which key will be used when locking and unlocking an account
149
149
  # config.unlock_keys = [ :email ]
@@ -157,7 +157,7 @@ Devise.setup do |config|
157
157
 
158
158
  # Number of authentication tries before locking an account if lock_strategy
159
159
  # is failed attempts.
160
- # config.maximum_attempts = 20
160
+ config.maximum_attempts = 5
161
161
 
162
162
  # Time interval to unlock the account if :time is enabled as unlock_strategy.
163
163
  # config.unlock_in = 1.hour
@@ -1,7 +1,10 @@
1
1
  Dummy::Application.routes.draw do
2
2
  devise_for :admins
3
3
  devise_for :users
4
+
5
+ devise_for :lockable_users
4
6
  devise_for :non_otp_users
7
+ devise_for :rememberable_users
5
8
 
6
9
  resources :posts
7
10
  resources :admin_posts
@@ -0,0 +1,9 @@
1
+ class CreateLockableUsers < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :lockable_users do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end