devise-otp 1.1.0 → 2.0.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +30 -0
  3. data/.rubocop.yml +18 -0
  4. data/CHANGELOG.md +42 -1
  5. data/Gemfile +7 -1
  6. data/README.md +5 -1
  7. data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +30 -7
  8. data/app/controllers/devise_otp/devise/otp_persistence_controller.rb +53 -0
  9. data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +2 -37
  10. data/app/views/devise/otp_credentials/refresh.html.erb +6 -6
  11. data/app/views/devise/otp_credentials/show.html.erb +10 -13
  12. data/app/views/devise/otp_tokens/_token_secret.html.erb +9 -9
  13. data/app/views/devise/otp_tokens/_trusted_devices.html.erb +7 -7
  14. data/app/views/devise/otp_tokens/edit.html.erb +9 -9
  15. data/app/views/devise/otp_tokens/recovery.html.erb +6 -6
  16. data/app/views/devise/otp_tokens/show.html.erb +6 -6
  17. data/bin/erb_lint +27 -0
  18. data/bin/rubocop +27 -0
  19. data/config/locales/en.yml +9 -7
  20. data/devise-otp.gemspec +3 -1
  21. data/lib/devise/strategies/database_authenticatable.rb +4 -17
  22. data/lib/devise-otp/version.rb +1 -1
  23. data/lib/devise-otp.rb +0 -1
  24. data/lib/devise_otp_authenticatable/controllers/helpers.rb +1 -2
  25. data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +1 -2
  26. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +7 -2
  27. data/lib/devise_otp_authenticatable/hooks/refreshable.rb +0 -1
  28. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +18 -12
  29. data/lib/devise_otp_authenticatable/routes.rb +7 -7
  30. data/test/dummy/app/controllers/admin_posts_controller.rb +0 -1
  31. data/test/dummy/app/controllers/base_controller.rb +0 -2
  32. data/test/dummy/app/controllers/non_otp_posts_controller.rb +0 -1
  33. data/test/dummy/app/models/admin.rb +0 -1
  34. data/test/dummy/app/models/lockable_user.rb +8 -0
  35. data/test/dummy/app/models/non_otp_user.rb +0 -1
  36. data/test/dummy/app/models/rememberable_user.rb +8 -0
  37. data/test/dummy/app/views/layouts/application.html.erb +4 -2
  38. data/test/dummy/app/views/posts/show.html.erb +0 -1
  39. data/test/dummy/app/views/shared/_navbar.html.erb +15 -0
  40. data/test/dummy/config/application.rb +3 -0
  41. data/test/dummy/config/initializers/devise.rb +2 -2
  42. data/test/dummy/config/routes.rb +3 -0
  43. data/test/dummy/db/migrate/20250731000001_create_lockable_users.rb +9 -0
  44. data/test/dummy/db/migrate/20250731000002_add_devise_to_lockable_users.rb +52 -0
  45. data/test/dummy/db/migrate/20250731000003_devise_otp_add_to_lockable_users.rb +28 -0
  46. data/test/dummy/db/migrate/20250817221304_create_rememberable_users.rb +50 -0
  47. data/test/dummy/db/migrate/20250818030305_add_devise_otp_to_rememberable_users.rb +28 -0
  48. data/test/dummy/db/schema.rb +67 -1
  49. data/test/integration/disable_token_test.rb +0 -2
  50. data/test/integration/enable_otp_form_test.rb +12 -1
  51. data/test/integration/lockable_test.rb +143 -0
  52. data/test/integration/non_otp_user_models_test.rb +0 -2
  53. data/test/integration/otp_drift_test.rb +35 -0
  54. data/test/integration/persistence_test.rb +59 -4
  55. data/test/integration/refresh_test.rb +23 -14
  56. data/test/integration/rememberable_test.rb +143 -0
  57. data/test/integration/reset_token_test.rb +0 -2
  58. data/test/integration/sign_in_test.rb +15 -8
  59. data/test/integration/trackable_test.rb +0 -2
  60. data/test/integration_tests_helper.rb +22 -1
  61. data/test/models/otp_authenticatable_test.rb +48 -11
  62. data/test/test_helper.rb +1 -0
  63. metadata +34 -4
@@ -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
46
  codes_list: 'Here is the list of your 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
@@ -20,5 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.add_dependency "rails", ">= 7.1"
21
21
  gem.add_dependency "devise", ">= 4.8.0", "< 5.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
+
25
+ gem.add_development_dependency "timecop", "~> 0.9.10"
24
26
  end
@@ -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.0.0"
4
4
  end
5
5
  end
data/lib/devise-otp.rb CHANGED
@@ -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 => [: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
@@ -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,15 @@
1
+ <nav>
2
+ <div style="float: right;">
3
+ <% if current_user.present? %>
4
+ <%= button_to "Sign Out", destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %>
5
+ <% elsif current_admin.present? %>
6
+ <%= button_to "Sign Out", destroy_admin_session_path, method: :delete, data: { turbo_method: :delete } %>
7
+ <% elsif current_lockable_user.present? %>
8
+ <%= button_to "Sign Out", destroy_lockable_user_session_path, method: :delete, data: { turbo_method: :delete } %>
9
+ <% elsif current_non_otp_user.present? %>
10
+ <%= button_to "Sign Out", destroy_non_otp_user_session_path, method: :delete, data: { turbo_method: :delete } %>
11
+ <% elsif current_rememberable_user.present? %>
12
+ <%= button_to "Sign Out", destroy_rememberable_user_session_path, method: :delete, data: { turbo_method: :delete } %>
13
+ <% end %>
14
+ </div>
15
+ </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
@@ -0,0 +1,52 @@
1
+ class AddDeviseToLockableUsers < ActiveRecord::Migration[5.0]
2
+ def self.up
3
+ change_table(:lockable_users) do |t|
4
+ ## Database authenticatable
5
+ t.string :email, null: false, default: ""
6
+ t.string :encrypted_password, null: false, default: ""
7
+
8
+ ## Recoverable
9
+ t.string :reset_password_token
10
+ t.datetime :reset_password_sent_at
11
+
12
+ ## Rememberable
13
+ t.datetime :remember_created_at
14
+
15
+ ## Trackable
16
+ t.integer :sign_in_count, default: 0
17
+ t.datetime :current_sign_in_at
18
+ t.datetime :last_sign_in_at
19
+ t.string :current_sign_in_ip
20
+ t.string :last_sign_in_ip
21
+
22
+ ## Confirmable
23
+ # t.string :confirmation_token
24
+ # t.datetime :confirmed_at
25
+ # t.datetime :confirmation_sent_at
26
+ # t.string :unconfirmed_email # Only if using reconfirmable
27
+
28
+ ## Lockable
29
+ t.integer :failed_attempts, default: 0 # Only if lock strategy is :failed_attempts
30
+ t.string :unlock_token # Only if unlock strategy is :email or :both
31
+ t.datetime :locked_at
32
+
33
+ ## Token authenticatable
34
+ t.string :authentication_token
35
+
36
+ # Uncomment below if timestamps were not included in your original model.
37
+ # t.timestamps
38
+ end
39
+
40
+ add_index :lockable_users, :email, unique: true
41
+ add_index :lockable_users, :reset_password_token, unique: true
42
+ # add_index :lockable_users, :confirmation_token, :unique => true
43
+ add_index :lockable_users, :unlock_token, unique: true
44
+ add_index :lockable_users, :authentication_token, unique: true
45
+ end
46
+
47
+ def self.down
48
+ # By default, we don't want to make any assumption about how to roll back a migration when your
49
+ # model already existed. Please edit below which fields you would like to remove in this migration.
50
+ raise ActiveRecord::IrreversibleMigration
51
+ end
52
+ end
@@ -0,0 +1,28 @@
1
+ class DeviseOtpAddToLockableUsers < ActiveRecord::Migration[5.0]
2
+ def self.up
3
+ change_table :lockable_users do |t|
4
+ t.string :otp_auth_secret
5
+ t.string :otp_recovery_secret
6
+ t.boolean :otp_enabled, default: false, null: false
7
+ t.boolean :otp_mandatory, default: false, null: false
8
+ t.datetime :otp_enabled_on
9
+ t.integer :otp_time_drift, default: 0, null: false
10
+ t.integer :otp_failed_attempts, default: 0, null: false
11
+ t.integer :otp_recovery_counter, default: 0, null: false
12
+ t.string :otp_persistence_seed
13
+
14
+ t.string :otp_session_challenge
15
+ t.datetime :otp_challenge_expires
16
+ end
17
+
18
+ add_index :lockable_users, :otp_session_challenge, unique: true
19
+ add_index :lockable_users, :otp_challenge_expires
20
+ end
21
+
22
+ def self.down
23
+ change_table :lockable_users do |t|
24
+ t.remove :otp_auth_secret, :otp_recovery_secret, :otp_enabled, :otp_mandatory, :otp_enabled_on, :otp_session_challenge,
25
+ :otp_challenge_expires, :otp_time_drift, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ class CreateRememberableUsers < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rememberable_users do |t|
4
+ t.string :name
5
+
6
+ ## Database authenticatable
7
+ t.string :email, null: false, default: ""
8
+ t.string :encrypted_password, null: false, default: ""
9
+
10
+ ## Recoverable
11
+ t.string :reset_password_token
12
+ t.datetime :reset_password_sent_at
13
+
14
+ ## Rememberable
15
+ t.datetime :remember_created_at
16
+
17
+ ## Trackable
18
+ t.integer :sign_in_count, default: 0
19
+ t.datetime :current_sign_in_at
20
+ t.datetime :last_sign_in_at
21
+ t.string :current_sign_in_ip
22
+ t.string :last_sign_in_ip
23
+
24
+ ## Confirmable
25
+ # t.string :confirmation_token
26
+ # t.datetime :confirmed_at
27
+ # t.datetime :confirmation_sent_at
28
+ # t.string :unconfirmed_email # Only if using reconfirmable
29
+
30
+ ## Lockable
31
+ # t.integer :failed_attempts, default: 0 # Only if lock strategy is :failed_attempts
32
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
33
+ # t.datetime :locked_at
34
+
35
+ ## Token authenticatable
36
+ # t.string :authentication_token
37
+
38
+ # Uncomment below if timestamps were not included in your original model.
39
+ # t.timestamps
40
+ end
41
+
42
+ add_index :rememberable_users, :email, unique: true
43
+ add_index :rememberable_users, :reset_password_token, unique: true
44
+ # add_index :rememberable_users, :confirmation_token, :unique => true
45
+ # add_index :rememberable_users, :unlock_token, unique: true
46
+ # add_index :rememberable_users, :authentication_token, unique: true
47
+ # t.timestamps
48
+ # end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ class AddDeviseOtpToRememberableUsers < ActiveRecord::Migration[7.0]
2
+ def self.up
3
+ change_table :rememberable_users do |t|
4
+ t.string :otp_auth_secret
5
+ t.string :otp_recovery_secret
6
+ t.boolean :otp_enabled, default: false, null: false
7
+ t.boolean :otp_mandatory, default: false, null: false
8
+ t.datetime :otp_enabled_on
9
+ t.integer :otp_time_drift, default: 0, null: false
10
+ t.integer :otp_failed_attempts, default: 0, null: false
11
+ t.integer :otp_recovery_counter, default: 0, null: false
12
+ t.string :otp_persistence_seed
13
+
14
+ t.string :otp_session_challenge
15
+ t.datetime :otp_challenge_expires
16
+ end
17
+
18
+ add_index :rememberable_users, :otp_session_challenge, unique: true
19
+ add_index :rememberable_users, :otp_challenge_expires
20
+ end
21
+
22
+ def self.down
23
+ change_table :rememberable_users do |t|
24
+ t.remove :otp_auth_secret, :otp_recovery_secret, :otp_enabled, :otp_mandatory, :otp_enabled_on, :otp_session_challenge,
25
+ :otp_challenge_expires, :otp_time_drift, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
26
+ end
27
+ end
28
+ end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema[8.0].define(version: 2025_07_18_092536) do
13
+ ActiveRecord::Schema[8.0].define(version: 2025_08_18_030305) do
14
14
  create_table "admins", force: :cascade do |t|
15
15
  t.string "name"
16
16
  t.datetime "created_at", precision: nil, null: false
@@ -48,6 +48,43 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_18_092536) do
48
48
  t.index ["unlock_token"], name: "index_admins_on_unlock_token", unique: true
49
49
  end
50
50
 
51
+ create_table "lockable_users", force: :cascade do |t|
52
+ t.string "name"
53
+ t.datetime "created_at", precision: nil, null: false
54
+ t.datetime "updated_at", precision: nil, null: false
55
+ t.string "email", default: "", null: false
56
+ t.string "encrypted_password", default: "", null: false
57
+ t.string "reset_password_token"
58
+ t.datetime "reset_password_sent_at", precision: nil
59
+ t.datetime "remember_created_at", precision: nil
60
+ t.integer "sign_in_count", default: 0
61
+ t.datetime "current_sign_in_at", precision: nil
62
+ t.datetime "last_sign_in_at", precision: nil
63
+ t.string "current_sign_in_ip"
64
+ t.string "last_sign_in_ip"
65
+ t.integer "failed_attempts", default: 0
66
+ t.string "unlock_token"
67
+ t.datetime "locked_at", precision: nil
68
+ t.string "authentication_token"
69
+ t.string "otp_auth_secret"
70
+ t.string "otp_recovery_secret"
71
+ t.boolean "otp_enabled", default: false, null: false
72
+ t.boolean "otp_mandatory", default: false, null: false
73
+ t.datetime "otp_enabled_on", precision: nil
74
+ t.integer "otp_time_drift", default: 0, null: false
75
+ t.integer "otp_failed_attempts", default: 0, null: false
76
+ t.integer "otp_recovery_counter", default: 0, null: false
77
+ t.string "otp_persistence_seed"
78
+ t.string "otp_session_challenge"
79
+ t.datetime "otp_challenge_expires", precision: nil
80
+ t.index ["authentication_token"], name: "index_lockable_users_on_authentication_token", unique: true
81
+ t.index ["email"], name: "index_lockable_users_on_email", unique: true
82
+ t.index ["otp_challenge_expires"], name: "index_lockable_users_on_otp_challenge_expires"
83
+ t.index ["otp_session_challenge"], name: "index_lockable_users_on_otp_session_challenge", unique: true
84
+ t.index ["reset_password_token"], name: "index_lockable_users_on_reset_password_token", unique: true
85
+ t.index ["unlock_token"], name: "index_lockable_users_on_unlock_token", unique: true
86
+ end
87
+
51
88
  create_table "non_otp_users", force: :cascade do |t|
52
89
  t.string "name"
53
90
  t.datetime "created_at", null: false
@@ -79,6 +116,35 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_18_092536) do
79
116
  t.datetime "updated_at", precision: nil, null: false
80
117
  end
81
118
 
119
+ create_table "rememberable_users", force: :cascade do |t|
120
+ t.string "name"
121
+ t.string "email", default: "", null: false
122
+ t.string "encrypted_password", default: "", null: false
123
+ t.string "reset_password_token"
124
+ t.datetime "reset_password_sent_at"
125
+ t.datetime "remember_created_at"
126
+ t.integer "sign_in_count", default: 0
127
+ t.datetime "current_sign_in_at"
128
+ t.datetime "last_sign_in_at"
129
+ t.string "current_sign_in_ip"
130
+ t.string "last_sign_in_ip"
131
+ t.string "otp_auth_secret"
132
+ t.string "otp_recovery_secret"
133
+ t.boolean "otp_enabled", default: false, null: false
134
+ t.boolean "otp_mandatory", default: false, null: false
135
+ t.datetime "otp_enabled_on"
136
+ t.integer "otp_time_drift", default: 0, null: false
137
+ t.integer "otp_failed_attempts", default: 0, null: false
138
+ t.integer "otp_recovery_counter", default: 0, null: false
139
+ t.string "otp_persistence_seed"
140
+ t.string "otp_session_challenge"
141
+ t.datetime "otp_challenge_expires"
142
+ t.index ["email"], name: "index_rememberable_users_on_email", unique: true
143
+ t.index ["otp_challenge_expires"], name: "index_rememberable_users_on_otp_challenge_expires"
144
+ t.index ["otp_session_challenge"], name: "index_rememberable_users_on_otp_session_challenge", unique: true
145
+ t.index ["reset_password_token"], name: "index_rememberable_users_on_reset_password_token", unique: true
146
+ end
147
+
82
148
  create_table "users", force: :cascade do |t|
83
149
  t.string "name"
84
150
  t.datetime "created_at", precision: nil, null: false