pages_core 3.13.0 → 3.14.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +1 -1
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +27 -4
  6. data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -6
  7. data/app/assets/stylesheets/pages_core/admin/components/totp.css +26 -0
  8. data/app/controllers/admin/account_recoveries_controller.rb +87 -0
  9. data/app/controllers/admin/invites_controller.rb +3 -2
  10. data/app/controllers/admin/otp_secrets_controller.rb +45 -0
  11. data/app/controllers/admin/recovery_codes_controller.rb +32 -0
  12. data/app/controllers/admin/sessions_controller.rb +65 -0
  13. data/app/controllers/admin/users_controller.rb +2 -8
  14. data/app/controllers/concerns/pages_core/authentication.rb +12 -10
  15. data/app/controllers/pages_core/admin_controller.rb +1 -1
  16. data/app/helpers/pages_core/admin/admin_helper.rb +11 -0
  17. data/app/javascript/index.ts +0 -2
  18. data/app/mailers/admin_mailer.rb +2 -2
  19. data/app/models/concerns/pages_core/has_otp.rb +27 -0
  20. data/app/models/otp_secret.rb +101 -0
  21. data/app/models/user.rb +15 -37
  22. data/app/policies/user_policy.rb +4 -0
  23. data/app/views/admin/account_recoveries/new.html.erb +22 -0
  24. data/app/views/admin/account_recoveries/show.html.erb +37 -0
  25. data/app/views/admin/invites/show.html.erb +1 -1
  26. data/app/views/admin/otp_secrets/create.html.erb +7 -0
  27. data/app/views/admin/otp_secrets/new.html.erb +60 -0
  28. data/app/views/admin/recovery_codes/_codes.html.erb +14 -0
  29. data/app/views/admin/recovery_codes/create.html.erb +7 -0
  30. data/app/views/admin/recovery_codes/new.html.erb +11 -0
  31. data/app/views/admin/sessions/_otp_form.html.erb +13 -0
  32. data/app/views/admin/sessions/new.html.erb +33 -0
  33. data/app/views/admin/sessions/verify_otp.html.erb +19 -0
  34. data/app/views/admin/users/edit.html.erb +31 -1
  35. data/app/views/admin/users/new.html.erb +1 -1
  36. data/app/views/admin_mailer/account_recovery.text.erb +10 -0
  37. data/app/views/layouts/admin/_header.html.erb +1 -1
  38. data/app/views/layouts/admin/_toast.html.erb +12 -0
  39. data/app/views/layouts/admin.html.erb +1 -1
  40. data/config/locales/en.yml +11 -3
  41. data/config/routes.rb +11 -6
  42. data/db/migrate/20240126160700_add_2fa_fields.rb +22 -0
  43. data/db/migrate/20240129201300_remove_password_reset_tokens.rb +13 -0
  44. data/lib/pages_core.rb +6 -0
  45. metadata +51 -9
  46. data/app/controllers/admin/password_resets_controller.rb +0 -85
  47. data/app/controllers/sessions_controller.rb +0 -27
  48. data/app/javascript/controllers/LoginController.ts +0 -32
  49. data/app/models/password_reset_token.rb +0 -34
  50. data/app/views/admin/password_resets/show.html.erb +0 -21
  51. data/app/views/admin/users/login.html.erb +0 -65
  52. data/app/views/admin_mailer/password_reset.text.erb +0 -11
@@ -0,0 +1,22 @@
1
+ <% content_for :page_title, "Account recovery" %>
2
+ <% content_for :page_description, "Account recovery" %>
3
+
4
+ <%= form_tag admin_account_recovery_path do %>
5
+ <h2>
6
+ Forgot your password or lost your authenticator?
7
+ </h2>
8
+ <p>
9
+ Don't worry, it happens.
10
+ Enter your email address below, and we'll send you a link where you
11
+ can recover your account.
12
+ </p>
13
+ <div class="field">
14
+ <label for="email">Email address</label>
15
+ <%= text_field_tag(:email, "", autocomplete: "email", autofocus: true) %>
16
+ </div>
17
+ <p>
18
+ <button type="submit">
19
+ Send
20
+ </button>
21
+ </p>
22
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <% content_for :page_title, "Account recovery" %>
2
+ <% content_for :page_description, "Please choose a new password to proceed" %>
3
+ <% content_for :body_class, "login" %>
4
+
5
+ <div class="login-form">
6
+ <%= form_for(@user,
7
+ url: admin_account_recovery_path,
8
+ builder: PagesCore::Admin::FormBuilder,
9
+ class: 'form') do |f| %>
10
+ <%= hidden_field_tag :token, @token %>
11
+ <%= f.labelled_password_field(:password,
12
+ autofocus: true,
13
+ autocomplete: "new-password") %>
14
+ <%= f.labelled_password_field(:password_confirmation,
15
+ autocomplete: "new-password") %>
16
+
17
+ <% if @user.otp_enabled? %>
18
+ <div class="field">
19
+ <label for="otp">6 digit code or recovery code</label>
20
+ <%= text_field_tag(:otp, "",
21
+ autocomplete: "one-time-code",
22
+ size: 6) %>
23
+ </div>
24
+ <p>
25
+ Lost your authenticator device? You can use one of your
26
+ emergency recovery codes instead.
27
+ </p>
28
+ <% end %>
29
+
30
+ <p>
31
+ <button type="submit">
32
+ Change password
33
+ </button>
34
+ or <%= link_to "Return to login screen", admin_login_path %>
35
+ </p>
36
+ <% end %>
37
+ </div>
@@ -14,7 +14,7 @@
14
14
  <%= f.labelled_text_field :email, autocomplete: "email" %>
15
15
  <%= f.labelled_password_field(:password,
16
16
  autocomplete: "new-password") %>
17
- <%= f.labelled_password_field(:confirm_password,
17
+ <%= f.labelled_password_field(:password_confirmation,
18
18
  autocomplete: "new-password") %>
19
19
  <p>
20
20
  <button type="submit">
@@ -0,0 +1,7 @@
1
+ <% content_for :page_title, "2FA enabled" %>
2
+ <% content_for :page_description, "Two-factor authentication enabled" %>
3
+
4
+ <div class="content">
5
+ <%= render(partial: "admin/recovery_codes/codes",
6
+ locals: { recovery_codes: @recovery_codes }) %>
7
+ </div>
@@ -0,0 +1,60 @@
1
+ <% content_for :page_title, "Enable 2FA" %>
2
+ <% content_for :page_description, "Enable two-factor authentication" %>
3
+
4
+ <%= form_tag(admin_otp_secret_path, method: :post, class: "totp-enrollment") do |f| %>
5
+ <h2>
6
+ Scan the QR-code
7
+ </h2>
8
+ <p>
9
+ Use an authenticator app or browser extension to scan the QR code below.<br>
10
+ Don't have one? Some options are
11
+ <%= link_to("1Password", "https://1password.com/") %>,
12
+ <%= link_to("LastPass Authenticator", "https://www.lastpass.com/") %>,
13
+ <%= link_to("Microsoft Authenticator",
14
+ "https://www.microsoft.com/en-us/security/mobile-authenticator-app") %>
15
+ or
16
+ <%= link_to("Google Authenticator",
17
+ "https://support.google.com/accounts/answer/1066447") %>.
18
+ </p>
19
+
20
+
21
+ <div class="qr-code">
22
+ <%= qr_code(@otp_secret.provisioning_uri) %>
23
+ </div>
24
+
25
+ <p>
26
+ If you are unable to scan the code, you can enter the following
27
+ info instead:
28
+ </p>
29
+
30
+ <p>
31
+ <b>Account name:</b><br>
32
+ <%= @otp_secret.account_name %>
33
+ </p>
34
+ <p>
35
+ <b>Secret:</b><br>
36
+ <span class="otp-secret">
37
+ <%= @otp_secret.secret %>
38
+ </span>
39
+ </p>
40
+
41
+ <h2>
42
+ Enter the code from the app
43
+ </h2>
44
+
45
+ <div class="field">
46
+ <label for="otp">6 digit code</label>
47
+ <%= text_field_tag(:otp, "",
48
+ autofocus: true,
49
+ autocomplete: "one-time-code",
50
+ size: 6) %>
51
+ </div>
52
+
53
+ <%= hidden_field_tag :signed_message, @otp_secret.signed_message %>
54
+
55
+ <p>
56
+ <button type="submit">
57
+ Verify
58
+ </button>
59
+ </p>
60
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <h2>
2
+ Recovery codes
3
+ </h2>
4
+ <p>
5
+ Please save the recovery codes below in a safe place, ideally
6
+ using a secure password manager.<br>
7
+ Without them, you will lose access to your account if you lose your device.
8
+ </p>
9
+
10
+ <ul class="recovery-codes">
11
+ <% recovery_codes.each do |c| %>
12
+ <li><%= c %></li>
13
+ <% end %>
14
+ </ul>
@@ -0,0 +1,7 @@
1
+ <% content_for :page_title, "New recovery codes" %>
2
+ <% content_for :page_description, "Recovery codes updated" %>
3
+
4
+ <div class="content">
5
+ <%= render(partial: "admin/recovery_codes/codes",
6
+ locals: { recovery_codes: @recovery_codes }) %>
7
+ </div>
@@ -0,0 +1,11 @@
1
+ <% content_for :page_title, "New recovery codes" %>
2
+ <% content_for :page_description, "Generate new recovery codes" %>
3
+
4
+ <%= form_tag(admin_recovery_codes_path, method: :post) do |f| %>
5
+ <%= render(partial: "admin/sessions/otp_form") %>
6
+ <p>
7
+ <button type="submit">
8
+ Verify
9
+ </button>
10
+ </p>
11
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <h2>
2
+ Two-factor authentication
3
+ </h2>
4
+ <p>
5
+ Enter a one-time code from your authenticator app to proceed.
6
+ </p>
7
+ <div class="field">
8
+ <label for="otp">6 digit code</label>
9
+ <%= text_field_tag(:otp, "",
10
+ autofocus: true,
11
+ autocomplete: "one-time-code",
12
+ size: 6) %>
13
+ </div>
@@ -0,0 +1,33 @@
1
+ <% content_for :page_title, "Sign in" %>
2
+ <% content_for(:page_description,
3
+ "Please enter your email address and password to sign in") %>
4
+ <% content_for :body_class, "login" %>
5
+
6
+ <% content_for :sidebar do %>
7
+ <h2>Please note</h2>
8
+ <p>
9
+ Please contact support if you experience problems logging in or using Pages.
10
+ </p>
11
+ <% end %>
12
+
13
+ <div class="login-form">
14
+ <%= form_tag admin_session_path do %>
15
+ <p>
16
+ <label>Email address</label>
17
+ <%= text_field_tag(:email, "", autocomplete: "email") %>
18
+ </p>
19
+ <p>
20
+ <label>Password</label>
21
+ <%= password_field_tag(:password, "", autocomplete: "current-password") %>
22
+ </p>
23
+ <p>
24
+ <button type="submit">Sign in</button>
25
+ </p>
26
+ <ul>
27
+ <li>
28
+ <%= link_to("<b>Help!</b> I forgot my password!".html_safe,
29
+ new_admin_account_recovery_path) %>
30
+ </li>
31
+ </ul>
32
+ <% end %>
33
+ </div>
@@ -0,0 +1,19 @@
1
+ <% content_for :page_title, "Two-factor authentication" %>
2
+ <% content_for :page_description, "Two-factor authentication" %>
3
+
4
+ <%= form_tag(verify_otp_admin_session_path, method: :post) do |f| %>
5
+ <%= hidden_field_tag :signed_user_id, @signed_user_id %>
6
+ <%= render(partial: "admin/sessions/otp_form") %>
7
+
8
+ <p>
9
+ <button type="submit">
10
+ Verify
11
+ </button>
12
+ </p>
13
+
14
+ <p>
15
+ Lost your authenticator device?
16
+ <%= link_to("Recover your account here",
17
+ new_admin_account_recovery_path) %>.
18
+ </p>
19
+ <% end %>
@@ -31,12 +31,42 @@
31
31
  <% if policy(@user).change_password? %>
32
32
  <h2>Password</h2>
33
33
  <%= f.labelled_password_field :password, 'Change password' %>
34
- <%= f.labelled_password_field :confirm_password, 'Confirm password' %>
34
+ <%= f.labelled_password_field :password_confirmation, 'Confirm password' %>
35
35
  <p>
36
36
  Leave the password blank if you do not wish to change the password.
37
37
  </p>
38
38
  <% end %>
39
39
 
40
+ <% if policy(@user).otp? %>
41
+ <h2>Two-factor authentication</h2>
42
+ <% if @user.otp_enabled? %>
43
+ <p>
44
+ Two-factor authentication has been enabled.
45
+ <%= link_to("Disable",
46
+ admin_otp_secret_path,
47
+ class: :delete,
48
+ method: :delete,
49
+ data: { confirm: "Are you sure you want to disable 2FA?" }) %>
50
+ </p>
51
+ <p>
52
+
53
+ You have
54
+ <%= t("pages_core.recovery_codes",
55
+ count: @user.hashed_recovery_codes.length) %>
56
+ remaining.
57
+ <%= link_to("Generate new codes", new_admin_recovery_codes_path) %>
58
+ </p>
59
+ <% else %>
60
+ <p>
61
+ Protect your account with an additional layer of security by
62
+ requiring an authentication app to sign in.
63
+ </p>
64
+ <p>
65
+ <%= link_to("Enable 2FA", new_admin_otp_secret_path) %>
66
+ </p>
67
+ <% end %>
68
+ <% end %>
69
+
40
70
  <%= render partial: "access_control", locals: { user: @user, f: f } %>
41
71
 
42
72
  <p>
@@ -11,7 +11,7 @@
11
11
  <%= f.labelled_text_field(:email, autocomplete: "email") %>
12
12
  <%= f.labelled_password_field(:password,
13
13
  autocomplete: "new-password") %>
14
- <%= f.labelled_password_field(:confirm_password,
14
+ <%= f.labelled_password_field(:password_confirmation,
15
15
  autocomplete: "new-password") %>
16
16
 
17
17
  <p>
@@ -0,0 +1,10 @@
1
+ Hi, <%= @user.name %>!
2
+
3
+ We've received a request to recover your account on <%= PagesCore.config(:site_name) %>.
4
+
5
+ Please click the following link to continue:
6
+ <%= @url %>
7
+
8
+ The link will expire in 24 hours.
9
+
10
+ If you do not want to recover your password, please ignore this email.
@@ -10,7 +10,7 @@
10
10
  <% if logged_in? %>
11
11
  <div class="user">
12
12
  Hello, <%= link_to(current_user.name, admin_user_url(current_user)) %>
13
- <%= link_to("Log out", session_path, method: "delete") %>
13
+ <%= link_to("Log out", admin_session_path, method: "delete") %>
14
14
  </div>
15
15
  <% end %>
16
16
  <nav class="tabs">
@@ -0,0 +1,12 @@
1
+ <%= react_component "Toast", { notice: flash[:notice], error: flash[:error] } %>
2
+ <% if Rails.env.test? && flash.any? %>
3
+ <div class="flash-test-helper">
4
+ <% %i[notice error].each do |type| %>
5
+ <% if flash[type] || true %>
6
+ <div class="<%= type %>">
7
+ <%= flash[type] %>
8
+ </div>
9
+ <% end %>
10
+ <% end %>
11
+ </div>
12
+ <% end %>
@@ -58,6 +58,6 @@
58
58
  <% end %>
59
59
  </div>
60
60
  <%= react_component "Modal", {} %>
61
- <%= react_component "Toast", { notice: flash[:notice], error: flash[:error] } %>
61
+ <%= render(partial: "layouts/admin/toast") %>
62
62
  </body>
63
63
  </html>
@@ -28,13 +28,21 @@ en:
28
28
  The provided email address and password combination was not valid
29
29
  invite_expired: This invite is no longer valid.
30
30
  logged_out: You have been logged out
31
- password_reset:
31
+ otp:
32
+ already_enabled: 2FA has already been enabled
33
+ disabled: 2FA has been disabled
34
+ invalid_code: Invalid 2FA code
35
+ required: 2FA is required for this
36
+ account_recovery:
32
37
  changed: Your password has been changed
33
- expired: Your password reset link has expired
34
- invalid_request: Invalid password reset request
38
+ invalid_request: This link is no longer valid
35
39
  not_found: Couldn't find a user with that email address
36
40
  sent: An email with further instructions has been sent
37
41
  problems_saving: There were problems saving your changes
42
+ recovery_codes:
43
+ zero: "no recovery codes"
44
+ one: "one recovery code"
45
+ other: "%{count} recovery codes"
38
46
  templates:
39
47
  default:
40
48
  blocks:
data/config/routes.rb CHANGED
@@ -33,9 +33,6 @@ Rails.application.routes.draw do
33
33
  get "pages/:locale/*glob" => redirect("/%{locale}/pages/%{glob}"),
34
34
  locale: /\w\w\w/
35
35
 
36
- # Authentication
37
- resource :session, only: %i[create destroy]
38
-
39
36
  # Sitemap
40
37
  resource :sitemap, only: [:show]
41
38
 
@@ -51,9 +48,9 @@ Rails.application.routes.draw do
51
48
  end
52
49
 
53
50
  # Password resets
54
- resources :password_resets, only: %i[create show update]
55
- controller :password_resets do
56
- get "/password_resets/:id/:token" => :show, as: :password_reset_with_token
51
+ resource :account_recovery
52
+ controller :account_recoveries do
53
+ get "/account_recovery/:token" => :show, as: :account_recovery_with_token
57
54
  end
58
55
 
59
56
  # Attachments
@@ -76,6 +73,14 @@ Rails.application.routes.draw do
76
73
  # Categories
77
74
  resources :categories
78
75
 
76
+ # Authentication
77
+ resource :session, only: %i[create destroy] do
78
+ member { post :verify_otp }
79
+ end
80
+ resource :otp_secret, only: %i[new create destroy]
81
+ resource :recovery_codes, only: %i[new create]
82
+ get "login" => "sessions#new", as: "login"
83
+
79
84
  # Pages
80
85
  scope ":locale" do
81
86
  resources :news,
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Add2faFields < ActiveRecord::Migration[7.0]
4
+ def change
5
+ change_table :users do |t|
6
+ t.boolean :otp_enabled, null: false, default: false
7
+ t.string :otp_secret
8
+ t.datetime :last_otp_at
9
+ t.jsonb :hashed_recovery_codes, null: false, default: []
10
+ t.string :session_token
11
+ end
12
+
13
+ rename_column :users, :hashed_password, :password_digest
14
+
15
+ reversible do |dir|
16
+ dir.up do
17
+ User.find_each { |u| u.update(session_token: SecureRandom.hex(32)) }
18
+ change_column_null :users, :session_token, false
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RemovePasswordResetTokens < ActiveRecord::Migration[7.0]
4
+ def change
5
+ drop_table :password_reset_tokens do |t|
6
+ t.integer :user_id
7
+ t.string :token
8
+ t.datetime :expires_at
9
+ t.datetime :created_at
10
+ t.datetime :updated_at
11
+ end
12
+ end
13
+ end
data/lib/pages_core.rb CHANGED
@@ -32,6 +32,8 @@ require "pg_search"
32
32
  require "progress_bar"
33
33
  require "rails_i18n"
34
34
  require "RedCloth"
35
+ require "rotp"
36
+ require "rqrcode"
35
37
  require "sass-rails"
36
38
  require "typhoeus"
37
39
  require "will_paginate"
@@ -83,5 +85,9 @@ module PagesCore
83
85
  end
84
86
  end
85
87
  alias config configuration
88
+
89
+ def reset_configuration!
90
+ @configuration = PagesCore::Configuration::Pages.new
91
+ end
86
92
  end
87
93
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pages_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.0
4
+ version: 3.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-17 00:00:00.000000000 Z
11
+ date: 2024-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -206,6 +206,34 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: 4.3.2
209
+ - !ruby/object:Gem::Dependency
210
+ name: rotp
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: 6.3.0
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: 6.3.0
223
+ - !ruby/object:Gem::Dependency
224
+ name: rqrcode
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
209
237
  - !ruby/object:Gem::Dependency
210
238
  name: tty-table
211
239
  requirement: !ruby/object:Gem::Requirement
@@ -377,6 +405,7 @@ files:
377
405
  - app/assets/stylesheets/pages_core/admin/components/textarea.css
378
406
  - app/assets/stylesheets/pages_core/admin/components/toast.css
379
407
  - app/assets/stylesheets/pages_core/admin/components/toolbar.css
408
+ - app/assets/stylesheets/pages_core/admin/components/totp.css
380
409
  - app/assets/stylesheets/pages_core/admin/controllers/pages.css
381
410
  - app/assets/stylesheets/pages_core/admin/controllers/users.css
382
411
  - app/assets/stylesheets/pages_core/admin/vars.css
@@ -388,14 +417,17 @@ files:
388
417
  - app/controller_dummies/page_files_controller.rb
389
418
  - app/controller_dummies/pages_controller.rb
390
419
  - app/controller_dummies/sitemaps_controller.rb
420
+ - app/controllers/admin/account_recoveries_controller.rb
391
421
  - app/controllers/admin/attachments_controller.rb
392
422
  - app/controllers/admin/calendars_controller.rb
393
423
  - app/controllers/admin/categories_controller.rb
394
424
  - app/controllers/admin/images_controller.rb
395
425
  - app/controllers/admin/invites_controller.rb
396
426
  - app/controllers/admin/news_controller.rb
427
+ - app/controllers/admin/otp_secrets_controller.rb
397
428
  - app/controllers/admin/pages_controller.rb
398
- - app/controllers/admin/password_resets_controller.rb
429
+ - app/controllers/admin/recovery_codes_controller.rb
430
+ - app/controllers/admin/sessions_controller.rb
399
431
  - app/controllers/admin/users_controller.rb
400
432
  - app/controllers/concerns/pages_core/admin/persistent_params.rb
401
433
  - app/controllers/concerns/pages_core/authentication.rb
@@ -416,7 +448,6 @@ files:
416
448
  - app/controllers/pages_core/frontend_controller.rb
417
449
  - app/controllers/pages_core/images_controller.rb
418
450
  - app/controllers/pages_core/sitemaps_controller.rb
419
- - app/controllers/sessions_controller.rb
420
451
  - app/formatters/pages_core/html_formatter.rb
421
452
  - app/formatters/pages_core/image_embedder.rb
422
453
  - app/formatters/pages_core/link_renderer.rb
@@ -492,7 +523,6 @@ files:
492
523
  - app/javascript/components/drag/useDragUploader.ts
493
524
  - app/javascript/components/drag/useDraggable.ts
494
525
  - app/javascript/controllers/EditPageController.ts
495
- - app/javascript/controllers/LoginController.ts
496
526
  - app/javascript/controllers/MainController.ts
497
527
  - app/javascript/controllers/PageOptionsController.js
498
528
  - app/javascript/features/RichText.tsx
@@ -512,6 +542,7 @@ files:
512
542
  - app/models/attachment.rb
513
543
  - app/models/autopublisher.rb
514
544
  - app/models/category.rb
545
+ - app/models/concerns/pages_core/has_otp.rb
515
546
  - app/models/concerns/pages_core/has_roles.rb
516
547
  - app/models/concerns/pages_core/humanizable_param.rb
517
548
  - app/models/concerns/pages_core/page_model/attachments.rb
@@ -532,6 +563,7 @@ files:
532
563
  - app/models/image.rb
533
564
  - app/models/invite.rb
534
565
  - app/models/invite_role.rb
566
+ - app/models/otp_secret.rb
535
567
  - app/models/page.rb
536
568
  - app/models/page_builder.rb
537
569
  - app/models/page_category.rb
@@ -539,7 +571,6 @@ files:
539
571
  - app/models/page_file.rb
540
572
  - app/models/page_image.rb
541
573
  - app/models/page_path.rb
542
- - app/models/password_reset_token.rb
543
574
  - app/models/role.rb
544
575
  - app/models/search_document.rb
545
576
  - app/models/tag.rb
@@ -563,6 +594,8 @@ files:
563
594
  - app/services/pages_core/create_user_service.rb
564
595
  - app/services/pages_core/destroy_invite_service.rb
565
596
  - app/services/pages_core/invite_service.rb
597
+ - app/views/admin/account_recoveries/new.html.erb
598
+ - app/views/admin/account_recoveries/show.html.erb
566
599
  - app/views/admin/calendars/_sidebar.html.erb
567
600
  - app/views/admin/calendars/show.html.erb
568
601
  - app/views/admin/images/show.json.jbuilder
@@ -570,6 +603,8 @@ files:
570
603
  - app/views/admin/invites/show.html.erb
571
604
  - app/views/admin/news/_sidebar.html.erb
572
605
  - app/views/admin/news/index.html.erb
606
+ - app/views/admin/otp_secrets/create.html.erb
607
+ - app/views/admin/otp_secrets/new.html.erb
573
608
  - app/views/admin/pages/_edit_content.html.erb
574
609
  - app/views/admin/pages/_edit_files.html.erb
575
610
  - app/views/admin/pages/_edit_images.html.erb
@@ -583,18 +618,22 @@ files:
583
618
  - app/views/admin/pages/index.html.erb
584
619
  - app/views/admin/pages/new.html.erb
585
620
  - app/views/admin/pages/search.html.erb
586
- - app/views/admin/password_resets/show.html.erb
621
+ - app/views/admin/recovery_codes/_codes.html.erb
622
+ - app/views/admin/recovery_codes/create.html.erb
623
+ - app/views/admin/recovery_codes/new.html.erb
624
+ - app/views/admin/sessions/_otp_form.html.erb
625
+ - app/views/admin/sessions/new.html.erb
626
+ - app/views/admin/sessions/verify_otp.html.erb
587
627
  - app/views/admin/users/_access_control.html.erb
588
628
  - app/views/admin/users/_list.html.erb
589
629
  - app/views/admin/users/deactivated.html.erb
590
630
  - app/views/admin/users/edit.html.erb
591
631
  - app/views/admin/users/index.html.erb
592
- - app/views/admin/users/login.html.erb
593
632
  - app/views/admin/users/new.html.erb
594
633
  - app/views/admin/users/new_password.html.erb
595
634
  - app/views/admin/users/show.html.erb
635
+ - app/views/admin_mailer/account_recovery.text.erb
596
636
  - app/views/admin_mailer/invite.text.erb
597
- - app/views/admin_mailer/password_reset.text.erb
598
637
  - app/views/errors/401.html.erb
599
638
  - app/views/errors/403.html.erb
600
639
  - app/views/errors/404.html.erb
@@ -606,6 +645,7 @@ files:
606
645
  - app/views/layouts/admin.html.erb
607
646
  - app/views/layouts/admin/_header.html.erb
608
647
  - app/views/layouts/admin/_page_header.html.erb
648
+ - app/views/layouts/admin/_toast.html.erb
609
649
  - app/views/layouts/errors.html.erb
610
650
  - app/views/sitemaps/show.xml.builder
611
651
  - config/locales/en.yml
@@ -615,6 +655,8 @@ files:
615
655
  - db/migrate/20210209151400_create_search_configurations.rb
616
656
  - db/migrate/20210210235200_create_search_documents.rb
617
657
  - db/migrate/20220615160300_remove_username.rb
658
+ - db/migrate/20240126160700_add_2fa_fields.rb
659
+ - db/migrate/20240129201300_remove_password_reset_tokens.rb
618
660
  - lib/pages_core.rb
619
661
  - lib/pages_core/admin_menu_item.rb
620
662
  - lib/pages_core/archive_finder.rb