easy-admin-rails 0.2.5 → 0.2.6

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +88 -0
  3. data/app/assets/builds/easy_admin.base.js.map +3 -3
  4. data/app/assets/builds/easy_admin.css +54 -0
  5. data/app/components/easy_admin/navbar_component.rb +19 -4
  6. data/app/components/easy_admin/profile/change_password_modal_component.rb +75 -0
  7. data/app/components/easy_admin/profile/profile_tab_component.rb +92 -0
  8. data/app/components/easy_admin/profile/security_tab_component.rb +53 -0
  9. data/app/components/easy_admin/profile/settings_component.rb +103 -0
  10. data/app/components/easy_admin/two_factor/backup_codes_component.rb +118 -0
  11. data/app/components/easy_admin/two_factor/setup_component.rb +124 -0
  12. data/app/components/easy_admin/two_factor/status_component.rb +92 -0
  13. data/app/controllers/concerns/easy_admin/two_factor.rb +110 -0
  14. data/app/controllers/easy_admin/application_controller.rb +10 -0
  15. data/app/controllers/easy_admin/profile_controller.rb +25 -0
  16. data/app/controllers/easy_admin/sessions_controller.rb +107 -1
  17. data/app/javascript/easy_admin/controllers/vertical_tabs_controller.js +112 -0
  18. data/app/javascript/easy_admin/controllers.js +3 -1
  19. data/app/models/easy_admin/admin_user.rb +3 -0
  20. data/app/views/easy_admin/profile/backup_codes_regenerated.turbo_stream.erb +12 -0
  21. data/app/views/easy_admin/profile/change_password.html.erb +24 -0
  22. data/app/views/easy_admin/profile/index.html.erb +1 -0
  23. data/app/views/easy_admin/profile/password_error.turbo_stream.erb +6 -0
  24. data/app/views/easy_admin/profile/password_invalid_current.turbo_stream.erb +6 -0
  25. data/app/views/easy_admin/profile/password_updated.turbo_stream.erb +9 -0
  26. data/app/views/easy_admin/profile/two_factor_backup_codes.html.erb +24 -0
  27. data/app/views/easy_admin/profile/two_factor_enabled.turbo_stream.erb +12 -0
  28. data/app/views/easy_admin/profile/two_factor_invalid_code.turbo_stream.erb +6 -0
  29. data/app/views/easy_admin/profile/two_factor_not_enabled.turbo_stream.erb +6 -0
  30. data/app/views/easy_admin/profile/two_factor_setup.html.erb +24 -0
  31. data/app/views/easy_admin/profile/two_factor_unavailable.turbo_stream.erb +6 -0
  32. data/app/views/easy_admin/sessions/two_factor_verification.html.erb +48 -0
  33. data/app/views/easy_admin/sessions/verify_2fa_error.turbo_stream.erb +13 -0
  34. data/config/routes.rb +20 -1
  35. data/lib/easy-admin-rails.rb +1 -0
  36. data/lib/easy_admin/two_factor_authentication.rb +156 -0
  37. data/lib/easy_admin/version.rb +1 -1
  38. data/lib/generators/easy_admin/two_factor/templates/README +29 -0
  39. data/lib/generators/easy_admin/two_factor/templates/migration.rb +10 -0
  40. data/lib/generators/easy_admin/two_factor/two_factor_generator.rb +22 -0
  41. metadata +30 -2
@@ -0,0 +1,6 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "Failed to update password. Please check your input and try again.",
4
+ type: :error
5
+ ).call %>
6
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "Current password is incorrect. Please try again.",
4
+ type: :error
5
+ ).call %>
6
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "Password updated successfully!",
4
+ type: :success
5
+ ).call %>
6
+ <% end %>
7
+
8
+ <%= turbo_stream.update "modal" do %>
9
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <% if turbo_frame_request? %>
2
+ <turbo-frame id="modal">
3
+ <div id="modal-backdrop" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 opacity-100 transition-opacity duration-300" data-controller="modal" data-action="click->modal#closeOnBackdrop">
4
+ <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 xl:w-2/5 shadow-lg rounded-md bg-white">
5
+ <div class="mt-3">
6
+ <!-- Modal header -->
7
+ <div class="flex items-center justify-between mb-4">
8
+ <h3 class="text-lg font-medium text-gray-900">Backup Codes</h3>
9
+ <button class="text-gray-400 hover:text-gray-600 focus:outline-none" data-action="click->modal#close">
10
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
11
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
12
+ </svg>
13
+ </button>
14
+ </div>
15
+
16
+ <!-- Modal content -->
17
+ <%== EasyAdmin::TwoFactor::BackupCodesComponent.new(user: current_admin_user).call %>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </turbo-frame>
22
+ <% else %>
23
+ <%== EasyAdmin::TwoFactor::BackupCodesComponent.new(user: current_admin_user).call %>
24
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <%= turbo_stream.replace "two_factor_section" do %>
2
+ <div id="two_factor_section">
3
+ <%== EasyAdmin::TwoFactor::StatusComponent.new(user: current_admin_user).call %>
4
+ </div>
5
+ <% end %>
6
+
7
+ <%= turbo_stream.replace "notifications" do %>
8
+ <%== EasyAdmin::NotificationComponent.new(
9
+ message: "Two-factor authentication has been enabled successfully!",
10
+ type: :success
11
+ ).call %>
12
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "Invalid verification code. Please try again.",
4
+ type: :error
5
+ ).call %>
6
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "Two-factor authentication is not enabled",
4
+ type: :error
5
+ ).call %>
6
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <% if turbo_frame_request? %>
2
+ <turbo-frame id="modal">
3
+ <div id="modal-backdrop" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 opacity-100 transition-opacity duration-300" data-controller="modal" data-action="click->modal#closeOnBackdrop">
4
+ <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 xl:w-2/5 shadow-lg rounded-md bg-white">
5
+ <div class="mt-3">
6
+ <!-- Modal header -->
7
+ <div class="flex items-center justify-between mb-4">
8
+ <h3 class="text-lg font-medium text-gray-900">Set up Two-Factor Authentication</h3>
9
+ <button class="text-gray-400 hover:text-gray-600 focus:outline-none" data-action="click->modal#close">
10
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
11
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
12
+ </svg>
13
+ </button>
14
+ </div>
15
+
16
+ <!-- Modal content -->
17
+ <%== EasyAdmin::TwoFactor::SetupComponent.new(user: current_admin_user).call %>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </turbo-frame>
22
+ <% else %>
23
+ <%== EasyAdmin::TwoFactor::SetupComponent.new(user: current_admin_user).call %>
24
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "2FA is not available. Contact system administrator.",
4
+ type: :error
5
+ ).call %>
6
+ <% end %>
@@ -0,0 +1,48 @@
1
+ <!-- Form -->
2
+ <div class="p-10">
3
+ <%= form_with url: verify_2fa_path, method: :post, local: true, html: { class: "space-y-8" } do |f| %>
4
+
5
+ <!-- Header -->
6
+ <div class="text-center mb-6">
7
+ <h2 class="text-xl font-bold text-gray-900 mb-2">Two-Factor Authentication</h2>
8
+ <p class="text-gray-600">Enter the 6-digit code from your authenticator app</p>
9
+ <% if @user %>
10
+ <p class="text-sm text-gray-500 mt-2">Signing in as <%= @user.email %></p>
11
+ <% end %>
12
+ </div>
13
+
14
+ <!-- Input Fields Container -->
15
+ <div class="space-y-4">
16
+ <!-- OTP Code Field -->
17
+ <div class="relative">
18
+ <%= text_field_tag :otp_code, '', autofocus: true, autocomplete: "one-time-code",
19
+ maxlength: 6, pattern: "[0-9]{6}", id: "otp_code",
20
+ class: "w-full px-6 py-5 bg-gray-50/80 border-0 text-gray-900 placeholder-gray-400 focus:bg-gray-100/80 focus:ring-0 focus:outline-none text-base rounded-2xl transition-colors duration-200 font-medium text-center tracking-widest",
21
+ placeholder: "000000" %>
22
+ </div>
23
+ </div>
24
+
25
+ <!-- Submit Button -->
26
+ <div class="pt-4">
27
+ <%= f.submit "Verify Code",
28
+ class: "w-full bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-bold py-5 px-6 rounded-2xl transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-blue-500/30 text-base shadow-xl shadow-blue-500/20 active:scale-[0.98]" %>
29
+ </div>
30
+
31
+ <% end %>
32
+
33
+ <!-- Help Text -->
34
+ <div class="text-center pt-6">
35
+ <p class="text-sm text-gray-500 mb-2">
36
+ Can't access your authenticator app?
37
+ </p>
38
+ <p class="text-sm text-gray-400">
39
+ Contact your administrator for help with account recovery.
40
+ </p>
41
+ </div>
42
+
43
+ <!-- Back to Sign In -->
44
+ <div class="text-center pt-4">
45
+ <%= link_to "← Back to Sign In", cancel_2fa_path,
46
+ class: "text-sm text-blue-500 hover:text-blue-600 font-medium transition-colors duration-200" %>
47
+ </div>
48
+ </div>
@@ -0,0 +1,13 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ message: "Invalid authentication code. Please try again.",
4
+ type: :error
5
+ ).call %>
6
+ <% end %>
7
+
8
+ <%= turbo_stream.replace "otp_code" do %>
9
+ <%= text_field_tag :otp_code, '', autofocus: true, autocomplete: "one-time-code",
10
+ maxlength: 6, pattern: "[0-9]{6}",
11
+ class: "w-full px-6 py-5 bg-gray-50/80 border-0 text-gray-900 placeholder-gray-400 focus:bg-gray-100/80 focus:ring-0 focus:outline-none text-base rounded-2xl transition-colors duration-200 font-medium text-center tracking-widest",
12
+ placeholder: "000000" %>
13
+ <% end %>
data/config/routes.rb CHANGED
@@ -13,13 +13,32 @@ EasyAdmin::Engine.routes.draw do
13
13
  sign_out: 'sign_out',
14
14
  sign_up: 'sign_up'
15
15
  }
16
+
17
+ # 2FA verification routes (wrapped in devise_scope)
18
+ devise_scope :admin_user do
19
+ get 'two_factor_verification', to: 'sessions#two_factor_verification', as: 'two_factor_verification'
20
+ post 'verify_2fa', to: 'sessions#verify_2fa', as: 'verify_2fa'
21
+ get 'cancel_2fa', to: 'sessions#cancel_2fa', as: 'cancel_2fa'
22
+ end
16
23
 
17
24
  root 'dashboard#index'
18
25
 
19
- # Settings routes
26
+ # Global settings routes (Flipper)
20
27
  get 'settings', to: 'settings#index'
21
28
  patch 'settings', to: 'settings#update'
22
29
 
30
+ # User profile settings routes
31
+ get 'profile', to: 'profile#index', as: 'profile'
32
+ patch 'profile', to: 'profile#update', as: 'update_profile'
33
+ get 'profile/change_password', to: 'profile#change_password', as: 'change_password'
34
+ patch 'profile/change_password', to: 'profile#update_password', as: 'update_password'
35
+
36
+ # Two-factor authentication routes (via profile controller)
37
+ get 'profile/two_factor/setup', to: 'profile#two_factor_setup', as: 'two_factor_setup'
38
+ post 'profile/two_factor/enable', to: 'profile#two_factor_enable', as: 'two_factor_enable'
39
+ get 'profile/two_factor/backup_codes', to: 'profile#two_factor_backup_codes', as: 'two_factor_backup_codes'
40
+ post 'profile/two_factor/regenerate_backup_codes', to: 'profile#regenerate_backup_codes', as: 'two_factor_regenerate_backup_codes'
41
+
23
42
  # Confirmation modal
24
43
  get 'confirmation_modal', to: 'confirmation_modal#show'
25
44
 
@@ -14,6 +14,7 @@ require "easy_admin/action"
14
14
  require "easy_admin/delete_action"
15
15
  require "easy_admin/batch_action"
16
16
  require "easy_admin/permissions"
17
+ require "easy_admin/two_factor_authentication"
17
18
  require "easy_admin/engine"
18
19
 
19
20
  module EasyAdmin
@@ -0,0 +1,156 @@
1
+ module EasyAdmin
2
+ module TwoFactorAuthentication
3
+ extend ActiveSupport::Concern
4
+
5
+ # Check if required gems are available
6
+ def self.available?
7
+ @available ||= begin
8
+ require 'rotp'
9
+ require 'rqrcode'
10
+ true
11
+ rescue LoadError
12
+ false
13
+ end
14
+ end
15
+
16
+ included do
17
+ # Only add validations and callbacks if 2FA gems are available
18
+ if EasyAdmin::TwoFactorAuthentication.available?
19
+ validates :otp_secret, presence: true, if: :otp_required_for_login?
20
+ validates :otp_secret, uniqueness: true, allow_blank: true
21
+ end
22
+ end
23
+
24
+ def two_factor_available?
25
+ EasyAdmin::TwoFactorAuthentication.available?
26
+ end
27
+
28
+ def two_factor_enabled?
29
+ two_factor_available? && otp_required_for_login? && otp_secret.present?
30
+ end
31
+
32
+ def generate_otp_secret!
33
+ return false unless two_factor_available?
34
+
35
+ require 'rotp'
36
+ self.otp_secret = ROTP::Base32.random
37
+ save!
38
+ end
39
+
40
+ def current_otp
41
+ return nil unless two_factor_available? && otp_secret.present?
42
+
43
+ require 'rotp'
44
+ ROTP::TOTP.new(otp_secret, issuer: "EasyAdmin").now
45
+ end
46
+
47
+ def validate_and_consume_otp!(token)
48
+ return false unless two_factor_available? && otp_secret.present?
49
+ return false if token.blank?
50
+
51
+ require 'rotp'
52
+
53
+ totp = ROTP::TOTP.new(otp_secret, issuer: "EasyAdmin")
54
+ last_otp_at_timestamp = last_otp_at&.to_i
55
+
56
+ # Verify with 30-second drift tolerance and replay protection
57
+ if totp.verify(token.to_s, drift_behind: 30, drift_ahead: 30, after: last_otp_at_timestamp)
58
+ touch(:last_otp_at)
59
+ true
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ def validate_backup_code!(code)
66
+ return false unless two_factor_available?
67
+ return false if code.blank? || otp_backup_codes.blank?
68
+
69
+ normalized_code = code.to_s.upcase.strip
70
+
71
+ if otp_backup_codes.include?(normalized_code)
72
+ invalidate_backup_code!(normalized_code)
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ def generate_backup_codes!
80
+ return false unless two_factor_available?
81
+
82
+ # Generate 10 backup codes (8 characters each)
83
+ codes = 10.times.map { SecureRandom.hex(4).upcase }
84
+ self.otp_backup_codes = codes
85
+ save!
86
+ codes
87
+ end
88
+
89
+ def invalidate_backup_code!(code)
90
+ return false unless two_factor_available?
91
+
92
+ normalized_code = code.to_s.upcase.strip
93
+ self.otp_backup_codes = otp_backup_codes.reject { |c| c == normalized_code }
94
+ save!
95
+ end
96
+
97
+ def backup_codes_remaining
98
+ two_factor_available? ? (otp_backup_codes&.length || 0) : 0
99
+ end
100
+
101
+ def provisioning_uri
102
+ return nil unless two_factor_available? && otp_secret.present?
103
+
104
+ require 'rotp'
105
+ ROTP::TOTP.new(otp_secret, issuer: "EasyAdmin").provisioning_uri(email)
106
+ end
107
+
108
+ def qr_code_svg(size: 200)
109
+ return nil unless two_factor_available?
110
+
111
+ uri = provisioning_uri
112
+ return nil if uri.blank?
113
+
114
+ require 'rqrcode'
115
+
116
+ qr_code = RQRCode::QRCode.new(uri)
117
+ qr_code.as_svg(
118
+ viewbox: true,
119
+ module_size: 4,
120
+ standalone: true,
121
+ use_path: true
122
+ )
123
+ end
124
+
125
+ def enable_two_factor!
126
+ return false unless two_factor_available? && otp_secret.present?
127
+
128
+ update!(otp_required_for_login: true)
129
+ end
130
+
131
+ def disable_two_factor!
132
+ update!(
133
+ otp_required_for_login: false,
134
+ otp_secret: nil,
135
+ otp_backup_codes: nil,
136
+ last_otp_at: nil
137
+ )
138
+ end
139
+
140
+ # Check if user needs 2FA based on role requirements
141
+ def two_factor_required?
142
+ return false unless two_factor_available?
143
+
144
+ # Check if role requires 2FA (if role system exists)
145
+ if respond_to?(:role) && role.respond_to?(:require_two_factor?)
146
+ role.require_two_factor?
147
+ else
148
+ false
149
+ end
150
+ end
151
+
152
+ def should_enable_two_factor?
153
+ two_factor_required? && !two_factor_enabled?
154
+ end
155
+ end
156
+ end
@@ -1,3 +1,3 @@
1
1
  module EasyAdmin
2
- VERSION = "0.2.5"
2
+ VERSION = "0.2.6"
3
3
  end
@@ -0,0 +1,29 @@
1
+ ===============================================================================
2
+
3
+ Two-Factor Authentication has been added to EasyAdmin!
4
+
5
+ To complete the setup, you'll need to:
6
+
7
+ 1. Install the required gems for 2FA functionality:
8
+
9
+ gem 'rotp', '~> 6.3' # TOTP generation/validation
10
+ gem 'rqrcode', '~> 2.2' # QR code generation
11
+
12
+ 2. Run the migration:
13
+
14
+ rails db:migrate
15
+
16
+ 3. Restart your server
17
+
18
+ 4. Two-factor authentication is now available in user settings!
19
+
20
+ Features:
21
+ • Optional 2FA (disabled by default)
22
+ • TOTP-based (compatible with Google Authenticator, Authy, etc.)
23
+ • QR code setup for easy configuration
24
+ • Backup codes for account recovery
25
+ • Admin can view/manage 2FA status for users
26
+
27
+ Without the optional gems, 2FA features will be gracefully hidden.
28
+
29
+ ===============================================================================
@@ -0,0 +1,10 @@
1
+ class AddTwoFactorToEasyAdminAdminUsers < ActiveRecord::Migration[<%= ActiveRecord::VERSION::MAJOR %>.<%= ActiveRecord::VERSION::MINOR %>]
2
+ def change
3
+ add_column :easy_admin_admin_users, :otp_secret, :string
4
+ add_column :easy_admin_admin_users, :otp_required_for_login, :boolean, default: false, null: false
5
+ add_column :easy_admin_admin_users, :otp_backup_codes, :json
6
+ add_column :easy_admin_admin_users, :last_otp_at, :datetime
7
+
8
+ add_index :easy_admin_admin_users, :otp_secret, unique: true
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'rails/generators'
2
+
3
+ module EasyAdmin
4
+ module Generators
5
+ class TwoFactorGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ desc "Add two-factor authentication fields to EasyAdmin AdminUser model"
9
+
10
+ def create_migration
11
+ timestamp = Time.current.utc.strftime("%Y%m%d%H%M%S")
12
+ migration_file = "#{timestamp}_add_two_factor_to_easy_admin_admin_users.rb"
13
+
14
+ template 'migration.rb', "db/migrate/#{migration_file}"
15
+ end
16
+
17
+ def show_readme
18
+ readme "README" if behavior == :invoke
19
+ end
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy-admin-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Slaurmagan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-04 00:00:00.000000000 Z
11
+ date: 2025-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -271,6 +271,10 @@ files:
271
271
  - app/components/easy_admin/pagination_component.rb
272
272
  - app/components/easy_admin/permissions/user_role_assignment_component.rb
273
273
  - app/components/easy_admin/permissions/user_role_permissions_component.rb
274
+ - app/components/easy_admin/profile/change_password_modal_component.rb
275
+ - app/components/easy_admin/profile/profile_tab_component.rb
276
+ - app/components/easy_admin/profile/security_tab_component.rb
277
+ - app/components/easy_admin/profile/settings_component.rb
274
278
  - app/components/easy_admin/quick_filters_component.rb
275
279
  - app/components/easy_admin/resource_pagination_component.rb
276
280
  - app/components/easy_admin/resources/index_component.rb
@@ -286,12 +290,16 @@ files:
286
290
  - app/components/easy_admin/sidebar_component.rb
287
291
  - app/components/easy_admin/turbo/response_component.rb
288
292
  - app/components/easy_admin/turbo/stream_component.rb
293
+ - app/components/easy_admin/two_factor/backup_codes_component.rb
294
+ - app/components/easy_admin/two_factor/setup_component.rb
295
+ - app/components/easy_admin/two_factor/status_component.rb
289
296
  - app/components/easy_admin/versions/diff_component.rb
290
297
  - app/components/easy_admin/versions/diff_modal_component.rb
291
298
  - app/components/easy_admin/versions/history_component.rb
292
299
  - app/components/easy_admin/versions/pagination_component.rb
293
300
  - app/components/easy_admin/versions/timeline_component.rb
294
301
  - app/concerns/easy_admin/resource_versions.rb
302
+ - app/controllers/concerns/easy_admin/two_factor.rb
295
303
  - app/controllers/easy_admin/application_controller.rb
296
304
  - app/controllers/easy_admin/batch_actions_controller.rb
297
305
  - app/controllers/easy_admin/concerns/belongs_to_editing.rb
@@ -304,6 +312,7 @@ files:
304
312
  - app/controllers/easy_admin/dashboard_controller.rb
305
313
  - app/controllers/easy_admin/dashboards_controller.rb
306
314
  - app/controllers/easy_admin/passwords_controller.rb
315
+ - app/controllers/easy_admin/profile_controller.rb
307
316
  - app/controllers/easy_admin/registrations_controller.rb
308
317
  - app/controllers/easy_admin/resources_controller.rb
309
318
  - app/controllers/easy_admin/row_actions_controller.rb
@@ -351,6 +360,7 @@ files:
351
360
  - app/javascript/easy_admin/controllers/toggle_switch_controller.js
352
361
  - app/javascript/easy_admin/controllers/turbo_stream_redirect.js
353
362
  - app/javascript/easy_admin/controllers/version_revert_controller.js
363
+ - app/javascript/easy_admin/controllers/vertical_tabs_controller.js
354
364
  - app/models/easy_admin/admin_user.rb
355
365
  - app/models/easy_admin/application_record.rb
356
366
  - app/policies/admin_user_policy.rb
@@ -366,6 +376,18 @@ files:
366
376
  - app/views/easy_admin/dashboards/show.turbo_stream.erb
367
377
  - app/views/easy_admin/passwords/edit.html.erb
368
378
  - app/views/easy_admin/passwords/new.html.erb
379
+ - app/views/easy_admin/profile/backup_codes_regenerated.turbo_stream.erb
380
+ - app/views/easy_admin/profile/change_password.html.erb
381
+ - app/views/easy_admin/profile/index.html.erb
382
+ - app/views/easy_admin/profile/password_error.turbo_stream.erb
383
+ - app/views/easy_admin/profile/password_invalid_current.turbo_stream.erb
384
+ - app/views/easy_admin/profile/password_updated.turbo_stream.erb
385
+ - app/views/easy_admin/profile/two_factor_backup_codes.html.erb
386
+ - app/views/easy_admin/profile/two_factor_enabled.turbo_stream.erb
387
+ - app/views/easy_admin/profile/two_factor_invalid_code.turbo_stream.erb
388
+ - app/views/easy_admin/profile/two_factor_not_enabled.turbo_stream.erb
389
+ - app/views/easy_admin/profile/two_factor_setup.html.erb
390
+ - app/views/easy_admin/profile/two_factor_unavailable.turbo_stream.erb
369
391
  - app/views/easy_admin/registrations/new.html.erb
370
392
  - app/views/easy_admin/resources/_redirect.turbo_stream.erb
371
393
  - app/views/easy_admin/resources/_table_rows.html.erb
@@ -382,6 +404,8 @@ files:
382
404
  - app/views/easy_admin/resources/show.html.erb
383
405
  - app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb
384
406
  - app/views/easy_admin/sessions/new.html.erb
407
+ - app/views/easy_admin/sessions/two_factor_verification.html.erb
408
+ - app/views/easy_admin/sessions/verify_2fa_error.turbo_stream.erb
385
409
  - app/views/easy_admin/settings/_form.html.erb
386
410
  - app/views/easy_admin/settings/index.html.erb
387
411
  - app/views/layouts/easy_admin/application.html.erb
@@ -424,6 +448,7 @@ files:
424
448
  - lib/easy_admin/resource/show_builder.rb
425
449
  - lib/easy_admin/resource_modules.rb
426
450
  - lib/easy_admin/resource_registry.rb
451
+ - lib/easy_admin/two_factor_authentication.rb
427
452
  - lib/easy_admin/types/json_type.rb
428
453
  - lib/easy_admin/version.rb
429
454
  - lib/easy_admin/versioning.rb
@@ -454,6 +479,9 @@ files:
454
479
  - lib/generators/easy_admin/templates/devise.rb
455
480
  - lib/generators/easy_admin/templates/easy_admin.rb
456
481
  - lib/generators/easy_admin/templates/resource.rb
482
+ - lib/generators/easy_admin/two_factor/templates/README
483
+ - lib/generators/easy_admin/two_factor/templates/migration.rb
484
+ - lib/generators/easy_admin/two_factor/two_factor_generator.rb
457
485
  - lib/tasks/easy_admin_tasks.rake
458
486
  homepage: https://github.com/Slaurmagan/easy-admin
459
487
  licenses: