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.
- checksums.yaml +4 -4
- data/.erb_lint.yml +30 -0
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +42 -1
- data/Gemfile +7 -1
- data/README.md +5 -1
- data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +30 -7
- data/app/controllers/devise_otp/devise/otp_persistence_controller.rb +53 -0
- data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +2 -37
- data/app/views/devise/otp_credentials/refresh.html.erb +6 -6
- data/app/views/devise/otp_credentials/show.html.erb +10 -13
- data/app/views/devise/otp_tokens/_token_secret.html.erb +9 -9
- data/app/views/devise/otp_tokens/_trusted_devices.html.erb +7 -7
- data/app/views/devise/otp_tokens/edit.html.erb +9 -9
- data/app/views/devise/otp_tokens/recovery.html.erb +6 -6
- data/app/views/devise/otp_tokens/show.html.erb +6 -6
- data/bin/erb_lint +27 -0
- data/bin/rubocop +27 -0
- data/config/locales/en.yml +9 -7
- data/devise-otp.gemspec +3 -1
- data/lib/devise/strategies/database_authenticatable.rb +4 -17
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise-otp.rb +0 -1
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +1 -2
- data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +1 -2
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +7 -2
- data/lib/devise_otp_authenticatable/hooks/refreshable.rb +0 -1
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +18 -12
- data/lib/devise_otp_authenticatable/routes.rb +7 -7
- data/test/dummy/app/controllers/admin_posts_controller.rb +0 -1
- data/test/dummy/app/controllers/base_controller.rb +0 -2
- data/test/dummy/app/controllers/non_otp_posts_controller.rb +0 -1
- data/test/dummy/app/models/admin.rb +0 -1
- data/test/dummy/app/models/lockable_user.rb +8 -0
- data/test/dummy/app/models/non_otp_user.rb +0 -1
- data/test/dummy/app/models/rememberable_user.rb +8 -0
- data/test/dummy/app/views/layouts/application.html.erb +4 -2
- data/test/dummy/app/views/posts/show.html.erb +0 -1
- data/test/dummy/app/views/shared/_navbar.html.erb +15 -0
- data/test/dummy/config/application.rb +3 -0
- data/test/dummy/config/initializers/devise.rb +2 -2
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/db/migrate/20250731000001_create_lockable_users.rb +9 -0
- data/test/dummy/db/migrate/20250731000002_add_devise_to_lockable_users.rb +52 -0
- data/test/dummy/db/migrate/20250731000003_devise_otp_add_to_lockable_users.rb +28 -0
- data/test/dummy/db/migrate/20250817221304_create_rememberable_users.rb +50 -0
- data/test/dummy/db/migrate/20250818030305_add_devise_otp_to_rememberable_users.rb +28 -0
- data/test/dummy/db/schema.rb +67 -1
- data/test/integration/disable_token_test.rb +0 -2
- data/test/integration/enable_otp_form_test.rb +12 -1
- data/test/integration/lockable_test.rb +143 -0
- data/test/integration/non_otp_user_models_test.rb +0 -2
- data/test/integration/otp_drift_test.rb +35 -0
- data/test/integration/persistence_test.rb +59 -4
- data/test/integration/refresh_test.rb +23 -14
- data/test/integration/rememberable_test.rb +143 -0
- data/test/integration/reset_token_test.rb +0 -2
- data/test/integration/sign_in_test.rb +15 -8
- data/test/integration/trackable_test.rb +0 -2
- data/test/integration_tests_helper.rb +22 -1
- data/test/models/otp_authenticatable_test.rb +48 -11
- data/test/test_helper.rb +1 -0
- metadata +34 -4
data/config/locales/en.yml
CHANGED
|
@@ -2,7 +2,8 @@ en:
|
|
|
2
2
|
devise:
|
|
3
3
|
otp:
|
|
4
4
|
general:
|
|
5
|
-
|
|
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
|
|
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
|
|
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.
|
|
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", "~>
|
|
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!(
|
|
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
|
|
45
|
-
|
|
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
|
data/lib/devise-otp/version.rb
CHANGED
data/lib/devise-otp.rb
CHANGED
|
@@ -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(:
|
|
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)
|
|
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
|
|
14
|
+
def otp_persistence_path_for(resource_or_scope, opts = {})
|
|
15
15
|
scope = ::Devise::Mapping.find_scope!(resource_or_scope)
|
|
16
|
-
send("
|
|
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 = {})
|
|
@@ -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:
|
|
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
|
-
:
|
|
60
|
-
:
|
|
61
|
-
:
|
|
62
|
-
:
|
|
63
|
-
:
|
|
64
|
-
:
|
|
65
|
-
:
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
@@ -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
|
|
@@ -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
|
|
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], :
|
|
15
|
+
<%= content_tag :p, flash[key], id: key %>
|
|
14
16
|
<% end %>
|
|
15
17
|
</div>
|
|
16
18
|
|
|
@@ -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>
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
data/test/dummy/config/routes.rb
CHANGED
|
@@ -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
|
data/test/dummy/db/schema.rb
CHANGED
|
@@ -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:
|
|
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
|