trusty-cms 7.0.27 → 7.0.29

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +15 -1
  4. data/INSTALL.md +21 -3
  5. data/README.md +17 -5
  6. data/app/assets/javascripts/admin/preview.js +40 -0
  7. data/app/assets/javascripts/admin.js +1 -0
  8. data/app/assets/stylesheets/admin/modules/_buttons.scss +4 -1
  9. data/app/assets/stylesheets/admin/partials/_forms.scss +4 -0
  10. data/app/controllers/admin/pages_controller.rb +29 -0
  11. data/app/controllers/admin/resource_controller.rb +0 -2
  12. data/app/controllers/admin/security_controller.rb +74 -0
  13. data/app/controllers/admin/sessions_controller.rb +43 -0
  14. data/app/controllers/admin/two_factor_controller.rb +42 -0
  15. data/app/controllers/application_controller.rb +7 -0
  16. data/app/helpers/application_helper.rb +0 -10
  17. data/app/models/trusty_cms/config.rb +0 -4
  18. data/app/models/user.rb +1 -1
  19. data/app/services/admin/preview_page_builder.rb +54 -0
  20. data/app/views/admin/configuration/edit.html.haml +0 -7
  21. data/app/views/admin/configuration/show.html.haml +21 -12
  22. data/app/views/admin/pages/_fields.html.haml +17 -21
  23. data/app/views/admin/preferences/edit.html.haml +1 -4
  24. data/app/views/admin/security/edit.html.haml +57 -0
  25. data/app/views/{devise → admin}/sessions/new.html.haml +5 -5
  26. data/app/views/admin/two_factor/show.html.haml +14 -0
  27. data/app/views/admin/users/_form.html.haml +0 -3
  28. data/app/views/devise/passwords/edit.html.haml +2 -4
  29. data/config/initializers/active_record_encryption.rb +5 -0
  30. data/config/initializers/devise.rb +4 -0
  31. data/config/initializers/trusty_cms_config.rb +0 -1
  32. data/config/locales/en.yml +29 -7
  33. data/config/routes.rb +9 -4
  34. data/db/migrate/20250502162215_add_devise_two_factor_to_admins.rb +7 -0
  35. data/lib/trusty_cms/admin_ui.rb +10 -5
  36. data/lib/trusty_cms/version.rb +1 -1
  37. data/package.json +1 -1
  38. data/spec/dummy/config/initializers/trusty_cms_config.rb +0 -1
  39. data/trusty_cms.gemspec +2 -0
  40. data/yarn.lock +4 -4
  41. metadata +40 -4
  42. data/app/views/admin/users/_password_fields.html.haml +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94e21fe3c5c8a96301637f07c7a3520590884187a45d9e1f00aa39e81e129f5c
4
- data.tar.gz: 769b855942bd1bb7ecdf193c062e47f45d77fe0561acc9b0b368f5a934e56408
3
+ metadata.gz: 520f342b36983dbca58c14ba3b104f212ed80c271797d63081212d8945ca7ade
4
+ data.tar.gz: 8f0204809ddbd49f9d2db4dde486f92a05bd1da1f410b73764a6d79edab26b73
5
5
  SHA512:
6
- metadata.gz: ec4b76f1b3009d3eb40fa9cd66f033967f9a3ebdeb022ccfbf04d60e88c11dfe7ee2a4dac88a879ecd57170903dd0de2705c8fb5ab50bb2426a34daf23065e37
7
- data.tar.gz: 403fc527a1bf912a6498efc113082b360556df0fec15148f4cfeb7efdd213b62b04fa935d60f65f57134297f4685a2da0a1895c0957bb0b6a7719f1384635070
6
+ metadata.gz: 31fd3cb0dcbf88a788b127ac94404e9b813f6af5e09a2533eceab1b7974c821f9eae10884704df8ba59ad3c4a3ee7b5885a8b65024dcda1cfe0f1d6374b8db8e
7
+ data.tar.gz: 5ba2f9d8b2bcbd0875d058a35e7035058beed413c835ae5ac3e66a579ecc9b14aad4a86714f8c12eaf16e877e4c5c88291260b4f243523193e8d7bfa0a9089e4
data/Gemfile CHANGED
@@ -15,6 +15,7 @@ group :development, :test do
15
15
  gem 'activestorage-validator'
16
16
  gem 'acts_as_list'
17
17
  gem 'database_cleaner'
18
+ gem 'devise-two-factor'
18
19
  gem 'factory_bot_rails', '6.4.4'
19
20
  gem 'file_validators'
20
21
  gem 'launchy', '~> 3.0.1'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trusty-cms (7.0.27)
4
+ trusty-cms (7.0.29)
5
5
  RedCloth (= 4.3.3)
6
6
  activestorage-validator
7
7
  acts_as_list (>= 0.9.5, < 1.3.0)
@@ -11,6 +11,7 @@ PATH
11
11
  ckeditor (>= 4.2.2, < 4.4.0)
12
12
  delocalize (>= 0.2, < 2.0)
13
13
  devise
14
+ devise-two-factor
14
15
  drb
15
16
  execjs (~> 2.7)
16
17
  haml (>= 5.0, < 6.0)
@@ -32,6 +33,7 @@ PATH
32
33
  ransack (~> 4.2.1)
33
34
  rdoc (>= 5.1, < 7.0)
34
35
  roadie-rails
36
+ rqrcode
35
37
  sass-rails
36
38
  stringex (>= 2.7.1, < 2.9.0)
37
39
  tzinfo (>= 1.2.3, < 2.1.0)
@@ -133,6 +135,7 @@ GEM
133
135
  xpath (~> 3.2)
134
136
  childprocess (5.1.0)
135
137
  logger (~> 1.5)
138
+ chunky_png (1.4.0)
136
139
  ckeditor (4.3.0)
137
140
  orm_adapter (~> 0.5.0)
138
141
  terrapin
@@ -159,6 +162,11 @@ GEM
159
162
  railties (>= 4.1.0)
160
163
  responders
161
164
  warden (~> 1.2.3)
165
+ devise-two-factor (6.1.0)
166
+ activesupport (>= 7.0, < 8.1)
167
+ devise (~> 4.0)
168
+ railties (>= 7.0, < 8.1)
169
+ rotp (~> 6.0)
162
170
  diff-lcs (1.5.1)
163
171
  docile (1.4.1)
164
172
  drb (2.2.1)
@@ -346,6 +354,11 @@ GEM
346
354
  roadie-rails (3.3.0)
347
355
  railties (>= 5.1, < 8.1)
348
356
  roadie (~> 5.0)
357
+ rotp (6.3.0)
358
+ rqrcode (3.1.0)
359
+ chunky_png (~> 1.0)
360
+ rqrcode_core (~> 2.0)
361
+ rqrcode_core (2.0.0)
349
362
  rspec-core (3.13.2)
350
363
  rspec-support (~> 3.13.0)
351
364
  rspec-expectations (3.13.3)
@@ -430,6 +443,7 @@ DEPENDENCIES
430
443
  activestorage-validator
431
444
  acts_as_list
432
445
  database_cleaner
446
+ devise-two-factor
433
447
  factory_bot_rails (= 6.4.4)
434
448
  file_validators
435
449
  launchy (~> 3.0.1)
data/INSTALL.md CHANGED
@@ -6,8 +6,9 @@ From within the directory containing your TrustyCMS instance:
6
6
 
7
7
  2. Add the following gems to your Gemfile:
8
8
 
9
- - gem 'trusty-cms'
10
- - gem 'rails-observers'
9
+ - gem 'trusty-cms'
10
+ - gem 'rails-observers'
11
+ - gem 'devise-two-factor'
11
12
 
12
13
  3. Run `bundle install`
13
14
 
@@ -20,5 +21,22 @@ From within the directory containing your TrustyCMS instance:
20
21
 
21
22
  7. Add utf8 encoding to your db.yml
22
23
 
23
- 8. Run `bundle exec rake db:setup`, `bundle exec rake trusty_cms:install:migrations`, then
24
+ 8. Set up encryption keys required for Rails’ native encryption (used by features like two-factor authentication):
25
+
26
+ - Run the encryption initializer command:
27
+
28
+ ```bash
29
+ ./bin/rails db:encryption:init
30
+ ```
31
+
32
+ - This will output three secrets. Copy the values and set them as environment variables in your preferred environment file (e.g., `.env`, `.env.development`, or via system environment settings):
33
+
34
+ ```env
35
+ ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
36
+ ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
37
+ ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
38
+ ```
39
+ - **Important**: Use different keys for each environment (development, test, production) unless two environments (e.g., development and staging) share a database — in that case, they **must** use the same keys.
40
+
41
+ 9. Run `bundle exec rake db:setup`, `bundle exec rake trusty_cms:install:migrations`, then
24
42
  `bundle exec rake db:bootstrap`.
data/README.md CHANGED
@@ -77,6 +77,23 @@ Steps:
77
77
 
78
78
  rspec
79
79
 
80
+ ### Preview Custom Page Types
81
+
82
+ TrustyCMS supports a preview feature for standard page types. However, this functionality may not work out of the box for custom page types. To enable the preview feature for your custom page types, follow these steps:
83
+
84
+ 1. In your application, create the following initializer file: `config/initializers/preview_page_types.rb`
85
+ 2. Inside this file, define a `PREVIEW_PAGE_TYPES` array constant with the names of the page types you’d like to enable the Preview button for, for example:
86
+
87
+ ```ruby
88
+ PREVIEW_PAGE_TYPES = %w[
89
+ BlogPage
90
+ FacilityPage
91
+ FileNotFoundPage
92
+ ].freeze
93
+ ```
94
+
95
+ 3. Test the Preview button with each custom page type. If a page type does not preview correctly, remove it from the list.
96
+
80
97
  ### Custom Page Type Routes Setup
81
98
  Additional configuration is required to ensure correct URL generation in the admin interface — specifically for the "Edit Page" dropdown and the "Save and View Draft" functionality.
82
99
 
@@ -106,11 +123,6 @@ DEFAULT_PAGE_TYPE_ROUTES = %w[
106
123
  ].freeze
107
124
  ```
108
125
 
109
- ### Save and View Draft Caching
110
- To ensure that pages and drafts under development are not cached by the browser or content delivery networks (such as CloudFront), the CMS appends a `trusty-no-cache` URL parameter containing the current date and time when a user selects **Save and View Draft** or **Save and View Page**.
111
-
112
- Because the `trusty-no-cache` parameter is always unique, it effectively bypasses caching mechanisms at both the CDN and browser levels, ensuring the user receives the most up-to-date version of the content with every request. Note that additional CDN configuration may be required to ensure query parameters are respected.
113
-
114
126
  ### Page Status Refresh Setup
115
127
 
116
128
  To ensure **Scheduled Pages** automatically update their status to **Published** after their designated **Publish Date & Time**, follow these steps to set up an automated refresh using **AWS Lambda** and **EventBridge**.
@@ -0,0 +1,40 @@
1
+ (function(Preview, $) {
2
+ Preview.showPreview = function(form){
3
+ var oldTarget = form.target;
4
+ var oldAction = form.action;
5
+
6
+ var $previewer = $('#preview_panel');
7
+ var $preview_tools = $previewer.find('.preview_tools');
8
+ var $frame = $('#page-preview');
9
+
10
+ $(window).scrollTop(0);
11
+ $previewer.show();
12
+ $preview_tools.css('opacity', 1);
13
+ $('body').addClass('clipped');
14
+ form.target = $frame.attr('id');
15
+ form.action = relative_url_root + '/admin/preview';
16
+ form.submit();
17
+
18
+ form.target = oldTarget;
19
+ form.action = oldAction;
20
+ }
21
+
22
+ }(window.Preview = window.Preview || {}, jQuery));
23
+
24
+ $(function () {
25
+ $('#show-preview').on('click', function(event){
26
+ event.preventDefault();
27
+ Preview.showPreview(this.form);
28
+ });
29
+
30
+ $('.preview_tools a.cancel').on('click', function(event){
31
+ event.preventDefault();
32
+ $('#preview_panel').hide();
33
+ $('body').removeClass('clipped');
34
+ $('#page-preview').attr('src', '');
35
+ });
36
+
37
+ $('iframe').on('load', function(event){
38
+ $('#preview_panel .preview_tools').css('opacity', null);
39
+ });
40
+ });
@@ -28,6 +28,7 @@
28
28
  //= require 'admin/page-edit'
29
29
  //= require 'admin/pagefield'
30
30
  //= require 'admin/popup'
31
+ //= require 'admin/preview'
31
32
  //= require 'admin/tabcontrol'
32
33
  //= require 'admin/utilities'
33
34
  //= require 'admin/tags'
@@ -18,7 +18,6 @@
18
18
  text-decoration: none;
19
19
  }
20
20
 
21
-
22
21
  .cancel-button {
23
22
  @include button;
24
23
  background-color: $light-red;
@@ -30,3 +29,7 @@
30
29
  color: $white;
31
30
  }
32
31
  }
32
+
33
+ .button-margin-top {
34
+ margin-top: 0.5em;
35
+ }
@@ -111,3 +111,7 @@ textarea {
111
111
  #search-input {
112
112
  max-width: 25em;
113
113
  }
114
+
115
+ .form-margin-top {
116
+ margin-top: 0.5em;
117
+ }
@@ -42,6 +42,7 @@ class Admin::PagesController < Admin::ResourceController
42
42
  assets = Asset.order('created_at DESC')
43
43
  @term = assets.ransack(params[:search] || '')
44
44
  @page = self.model = model_class.new_with_defaults(trusty_config)
45
+ @render_preview_button = render_preview_button?
45
46
  assign_page_attributes
46
47
  response_for :new
47
48
  end
@@ -52,6 +53,7 @@ class Admin::PagesController < Admin::ResourceController
52
53
  @page_url = generate_page_url(request.url, @page)
53
54
  @page_path = format_path(@page.path)
54
55
  @versions = format_versions(@page.versions)
56
+ @render_preview_button = render_preview_button?
55
57
  response_for :edit
56
58
  end
57
59
 
@@ -61,6 +63,12 @@ class Admin::PagesController < Admin::ResourceController
61
63
  redirect_to edit_admin_page_path(@page)
62
64
  end
63
65
 
66
+ def preview
67
+ render_preview
68
+ rescue PreviewStop => e
69
+ render text: e.message unless @performed_render
70
+ end
71
+
64
72
  def save_table_position
65
73
  new_position = params[:new_position]
66
74
  Page.save_order(new_position)
@@ -151,6 +159,20 @@ class Admin::PagesController < Admin::ResourceController
151
159
  end
152
160
  end
153
161
 
162
+ def render_preview
163
+ params.permit!
164
+ Page.transaction do
165
+ page = Admin::PreviewPageBuilder.new(
166
+ model_class:,
167
+ page_params: params[:page],
168
+ referer: request.referer,
169
+ ).build
170
+
171
+ page.pagination_parameters = pagination_parameters
172
+ process_with_exception(page)
173
+ end
174
+ end
175
+
154
176
  def process_with_exception(page)
155
177
  page.process(request, response)
156
178
  @performed_render = true
@@ -176,4 +198,11 @@ class Admin::PagesController < Admin::ResourceController
176
198
  raise "I'm not allowed to constantize #{page_class}!"
177
199
  end
178
200
  end
201
+
202
+ def render_preview_button?
203
+ page_class = @page.class.name
204
+ previewable_classes = ['Page']
205
+ previewable_classes += PREVIEW_PAGE_TYPES if defined?(PREVIEW_PAGE_TYPES)
206
+ previewable_classes.include?(page_class)
207
+ end
179
208
  end
@@ -202,8 +202,6 @@ class Admin::ResourceController < ApplicationController
202
202
  end
203
203
 
204
204
  def redirect_url
205
- return "#{edit_admin_page_url(model)}?view_page=true" if params[:save_and_view]
206
-
207
205
  params[:continue] ? { action: 'edit', id: model.id } : { action: 'index' }
208
206
  end
209
207
 
@@ -0,0 +1,74 @@
1
+ require 'rqrcode'
2
+
3
+ class Admin::SecurityController < ApplicationController
4
+ before_action :authenticate_user!
5
+ before_action :initialize_variables
6
+ before_action :initialize_two_factor_variables, only: %i[show edit update]
7
+
8
+ def show
9
+ set_standard_body_style
10
+ ensure_user_has_otp_secret
11
+ render :edit
12
+ end
13
+
14
+ def edit
15
+ render
16
+ end
17
+
18
+ def update
19
+ if @user.update(security_params)
20
+ sign_out(@user)
21
+ redirect_to new_user_session_path, notice: t('security_controller.password_updated')
22
+ else
23
+ flash[:error] = t('security_controller.error_updating_password')
24
+ render :edit
25
+ end
26
+ end
27
+
28
+ def verify_two_factor
29
+ if current_user.validate_and_consume_otp!(params[:otp_attempt])
30
+ current_user.update!(otp_required_for_login: true)
31
+ redirect_to admin_security_path, notice: t('security_controller.two_factor_enabled')
32
+ else
33
+ flash[:error] = t('security_controller.two_factor_invalid_code')
34
+ redirect_to admin_security_path
35
+ end
36
+ end
37
+
38
+ def disable_two_factor
39
+ if current_user.update!(otp_required_for_login: false)
40
+ redirect_to admin_security_path, notice: t('security_controller.two_factor_disabled')
41
+ else
42
+ flash[:error] = t('security_controller.two_factor_disabled_error')
43
+ redirect_to admin_security_path
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def initialize_variables
50
+ @user = current_user
51
+ @controller_name = 'user'
52
+ @template_name = 'security'
53
+ end
54
+
55
+ def ensure_user_has_otp_secret
56
+ return if current_user.otp_secret.present?
57
+
58
+ current_user.update!(otp_secret: User.generate_otp_secret)
59
+ end
60
+
61
+ def initialize_two_factor_variables
62
+ @two_factor_enabled = current_user.otp_required_for_login
63
+
64
+ unless @two_factor_enabled
65
+ otp_uri = current_user.otp_provisioning_uri(current_user.email, issuer: 'TrustyCMS')
66
+ qr = RQRCode::QRCode.new(otp_uri)
67
+ @qr_png_data = qr.as_png(size: 200).to_data_url
68
+ end
69
+ end
70
+
71
+ def security_params
72
+ params.require(:user).permit(:password, :password_confirmation)
73
+ end
74
+ end
@@ -0,0 +1,43 @@
1
+ class Admin::SessionsController < Devise::SessionsController
2
+ def create
3
+ user = find_user
4
+
5
+ if authenticated?(user)
6
+ handle_successful_authentication(user)
7
+ else
8
+ handle_failed_authentication
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def find_user
15
+ User.find_by(email: params[:user][:email])
16
+ end
17
+
18
+ def authenticated?(user)
19
+ user&.valid_password?(params[:user][:password])
20
+ end
21
+
22
+ def handle_successful_authentication(user)
23
+ if user.otp_required_for_login
24
+ start_two_factor_session(user)
25
+ redirect_to admin_two_factor_path
26
+ else
27
+ sign_in(:user, user)
28
+ redirect_to after_sign_in_path_for(user)
29
+ end
30
+ end
31
+
32
+ def handle_failed_authentication
33
+ self.resource = resource_class.new(sign_in_params)
34
+ clean_up_passwords(resource)
35
+ flash.now[:alert] = t('invalid_email_or_password')
36
+ render :new
37
+ end
38
+
39
+ def start_two_factor_session(user)
40
+ session[:pre_2fa_user_id] = user.id
41
+ session[:pre_2fa_started_at] = Time.current.to_i
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ class Admin::TwoFactorController < ApplicationController
2
+ skip_before_action :authenticate_user!
3
+ before_action :load_pre_2fa_user
4
+
5
+ MAX_2FA_SESSION_DURATION = 5.minutes.freeze
6
+
7
+ def show; end
8
+
9
+ def create
10
+ if @user.validate_and_consume_otp!(params[:otp_attempt])
11
+ session.delete(:pre_2fa_user_id)
12
+ session.delete(:pre_2fa_started_at)
13
+ sign_in(:user, @user)
14
+ redirect_to after_sign_in_path_for(@user)
15
+ else
16
+ reset_session
17
+ redirect_to new_user_session_path, alert: t('two_factor_controller.invalid_code')
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def load_pre_2fa_user
24
+ if current_user
25
+ redirect_to after_sign_in_path_for(current_user) and return
26
+ end
27
+
28
+ @user = User.find_by(id: session[:pre_2fa_user_id])
29
+
30
+ if !@user&.otp_required_for_login || session_expired?
31
+ reset_session
32
+ redirect_to new_user_session_path, alert: t('two_factor_controller.session_expired')
33
+ end
34
+ end
35
+
36
+ def session_expired?
37
+ started_at = session[:pre_2fa_started_at]
38
+ return true unless started_at
39
+
40
+ Time.current.to_i - started_at > MAX_2FA_SESSION_DURATION
41
+ end
42
+ end
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
6
6
 
7
7
  protect_from_forgery with: :exception
8
8
  before_action :authenticate_user!
9
+ before_action :configure_permitted_parameters, if: :devise_controller?
9
10
  before_action :set_timezone
10
11
  before_action :set_user_locale
11
12
  before_action :set_javascripts_and_stylesheets
@@ -44,6 +45,12 @@ class ApplicationController < ActionController::Base
44
45
  end
45
46
  end
46
47
 
48
+ protected
49
+
50
+ def configure_permitted_parameters
51
+ devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
52
+ end
53
+
47
54
  private
48
55
 
49
56
  def set_mailer
@@ -42,16 +42,6 @@ module ApplicationHelper
42
42
  submit_tag t('buttons.save_and_continue'), name: 'continue', class: 'button', accesskey: 's', id: 'save-and-continue-button'
43
43
  end
44
44
 
45
- def save_model_and_view_page_button(model, options = {})
46
- return nil unless generate_page_url(request.url, model)
47
-
48
- options[:label] ||= model.published? ? t('buttons.save_and_view_page') : t('buttons.save_and_view_draft')
49
- options[:class] ||= 'button'
50
- options[:name] ||= 'save_and_view'
51
- options[:id] ||= 'save-and-view-button'
52
- submit_tag options.delete(:label), options
53
- end
54
-
55
45
  def current_item?(item)
56
46
  if item.tab&.many? { |i| current_url?(i.relative_url) }
57
47
  # Accept only stricter URL matches if more than one matches
@@ -145,10 +145,6 @@ module TrustyCms
145
145
  @default_settings ||= %w{defaults.locale defaults.page.filter defaults.page.parts defaults.page.fields defaults.page.status}
146
146
  end
147
147
 
148
- def user_settings
149
- @user_settings ||= ['user.allow_password_reset?']
150
- end
151
-
152
148
  # A convenient drying method for specifying a prefix and options common to several settings.
153
149
  #
154
150
  # TrustyCms.config do |config|
data/app/models/user.rb CHANGED
@@ -3,7 +3,7 @@ class User < ActiveRecord::Base
3
3
  self.table_name = 'admins'
4
4
 
5
5
  # :confirmable, :lockable, :timeoutable and :omniauthable
6
- devise :database_authenticatable, :registerable,
6
+ devise :two_factor_authenticatable, :registerable,
7
7
  :recoverable, :rememberable, :trackable, :validatable
8
8
 
9
9
  alias_attribute :created_by_id, :id
@@ -0,0 +1,54 @@
1
+ module Admin
2
+ class PreviewPageBuilder
3
+ def initialize(model_class:, page_params:, referer:)
4
+ @model_class = model_class
5
+ @page_params = page_params
6
+ @referer = referer
7
+ end
8
+
9
+ def build
10
+ editing_existing_page? ? build_existing_page : build_new_page
11
+ end
12
+
13
+ private
14
+
15
+ def build_existing_page
16
+ page = find_page_from_referer.becomes(valid_model_class)
17
+ page.update(@page_params)
18
+ page
19
+ end
20
+
21
+ def build_new_page
22
+ page = valid_model_class.new(@page_params)
23
+
24
+ if creating_child_page?
25
+ parent = find_page_from_referer
26
+ page.parent = parent
27
+ page.layout_id ||= parent.layout_id
28
+ end
29
+
30
+ page.save!
31
+ page
32
+ end
33
+
34
+ def valid_model_class
35
+ Page.descendants.include?(@model_class) ? @model_class : Page
36
+ end
37
+
38
+ def find_page_from_referer
39
+ Page.find(extract_page_id_from_referer)
40
+ end
41
+
42
+ def extract_page_id_from_referer
43
+ @referer[%r{/admin/pages/(\d+)}, 1]
44
+ end
45
+
46
+ def editing_existing_page?
47
+ @referer =~ %r{/admin/pages/\d+/edit}
48
+ end
49
+
50
+ def creating_child_page?
51
+ @referer =~ %r{/admin/pages/\d+/children/new}
52
+ end
53
+ end
54
+ end
@@ -23,13 +23,6 @@
23
23
  %p
24
24
  = edit_config default_setting
25
25
 
26
- - form.edit_users do
27
- %fieldset
28
- %h4 Passwords
29
- - TrustyCms.config.user_settings.each do |user_setting|
30
- %p
31
- = edit_config user_setting
32
-
33
26
  - render_region :form_bottom do |form_bottom|
34
27
  - form_bottom.edit_buttons do
35
28
  .buttons
@@ -1,4 +1,4 @@
1
- #preferences.box
1
+ %fieldset
2
2
  - render_region :user do |user|
3
3
  - user.preferences do
4
4
  %h3
@@ -13,10 +13,6 @@
13
13
  = t('email_address')
14
14
  %span.uri
15
15
  = current_user.email
16
- %p.ruled
17
- %label
18
- = t('password')
19
- %big &bull;&bull;&bull;&bull;&bull;
20
16
  %p.ruled
21
17
  %label
22
18
  = t('language')
@@ -25,7 +21,25 @@
25
21
  .actions
26
22
  = button_to t("edit_preferences"), edit_admin_user_path(current_user), :method => :get
27
23
 
28
- #config.box
24
+ %fieldset
25
+ - render_region :user do |user|
26
+ - user.preferences do
27
+ %h3
28
+ = t('security')
29
+ %p.ruled
30
+ %label
31
+ = t('password')
32
+ %span.value
33
+ &bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;
34
+ %p.ruled
35
+ %label
36
+ = t('two_factor_authentication')
37
+ %span
38
+ = current_user.otp_required_for_login ? t('enabled') : t('disabled')
39
+ .actions
40
+ = button_to t('edit_security_settings'), admin_security_path, :method => :get
41
+
42
+ %fieldset
29
43
  - render_region :trusty_config do |config|
30
44
  - config.site do
31
45
  %h3
@@ -40,11 +54,6 @@
40
54
  %p.ruled
41
55
  = show_config default_setting
42
56
 
43
- - config.users do
44
- %h4 Passwords
45
- - TrustyCms.config.user_settings.each do |user_setting|
46
- %p.ruled
47
- = show_config user_setting
48
57
  - if current_user.admin?
49
58
  .actions
50
- = button_to t("edit_configuration"), edit_admin_configuration_path, :method => :get
59
+ = button_to t("edit_configuration"), edit_admin_configuration_path, :method => :get
@@ -20,7 +20,8 @@
20
20
  = render :partial => 'admin/page_fields/page_field', :collection => @page.fields
21
21
  = render_region :extended_metadata, :locals => {:f => fields}
22
22
  .drawer_handle
23
- %a.toggle{:href=>'#attributes', :rel=>'toggle[attributes]', :class=>"#{(meta_errors? ? 'less' : 'more')}"}
23
+ - toggle_class = meta_errors? ? 'less' : 'more'
24
+ %a.toggle{ href: '#attributes', rel: 'toggle[attributes]', class: toggle_class }
24
25
  = meta_label
25
26
  %i.fas.fa-angle-down
26
27
  - form.edit_page_parts do
@@ -66,32 +67,27 @@
66
67
  .error.hidden
67
68
  %span#published-at-error
68
69
 
69
- - render_region :form_bottom, :locals => {:f => fields} do |form_bottom|
70
+ - render_region :form_bottom, locals: { f: fields } do |form_bottom|
70
71
  - form_bottom.edit_buttons do
71
72
  - @buttons_partials.each do |partial|
72
- = render :partial => partial, :locals => {:f => fields}
73
+ = render partial: partial, locals: { f: fields }
74
+
73
75
  .page-actions
74
76
  = save_model_button(@page)
75
77
  = save_model_and_continue_editing_button(@page)
76
- = save_model_and_view_page_button(@page)
78
+ = submit_tag(t('preview'), class: 'button', id: 'show-preview') if @render_preview_button
77
79
  = t('or')
78
80
  = link_to t('cancel'), admin_pages_url(site_id: @site_id), class: 'alt'
79
- #view-page-url-data{ data: { url: @page_url } }
80
- - form_bottom.edit_timestamp do
81
- = updated_stamp @page
82
81
 
83
- -# Opens a new tab with the page URL when "Save and View Page" is selected
84
- :javascript
85
- document.addEventListener("DOMContentLoaded", function() {
86
- const params = new URLSearchParams(window.location.search);
87
- const dataDiv = document.getElementById("view-page-url-data");
88
- const baseUrl = dataDiv?.dataset?.url;
89
-
90
- if (params.get("view_page") === "true" && baseUrl) {
91
- const now = new Date().toISOString();
92
- const separator = baseUrl.includes("?") ? "&" : "?";
93
- const newTabUrl = `${baseUrl}${separator}trusty-no-cache=${encodeURIComponent(now)}`;
94
- window.open(newTabUrl, '_blank');
95
- }
96
- });
82
+ .fullcover.grey_out#preview_panel{ style: 'display: none;' }
83
+ %iframe.fullcover#page-preview{
84
+ name: 'page-preview',
85
+ src: "#{ActionController::Base.relative_url_root}/loading-iframe.html",
86
+ frameborder: 0,
87
+ scrolling: 'auto'
88
+ }
89
+ .preview_tools
90
+ = link_to t('edit_page'), {}, class: 'button cancel'
97
91
 
92
+ - form_bottom.edit_timestamp do
93
+ = updated_stamp(@page)
@@ -27,12 +27,9 @@
27
27
  = f.label :email, t("email_address"), :class => 'optional'
28
28
  = f.text_field 'email', :class => 'textbox', :maxlength => 255
29
29
 
30
- - form.edit_password do
31
- = render 'admin/users/password_fields', :f => f
32
-
33
30
  - render_region :form_bottom, :locals => {:f => f} do |form_bottom|
34
31
  - form_bottom.edit_buttons do
35
32
  .buttons
36
33
  = save_model_button @user
37
34
  = t('or')
38
- = link_to t('cancel'), admin_pages_url, class: 'alt'
35
+ = link_to t('cancel'), admin_configuration_path, class: 'alt'
@@ -0,0 +1,57 @@
1
+ - @page_title = @user.name + ' - Security'
2
+ - body_classes << 'edit_security'
3
+
4
+ - render_region :main do |main|
5
+ - main.edit_header do
6
+ %h1
7
+ = t('security')
8
+
9
+ - main.edit_form do
10
+ %fieldset
11
+ %h3
12
+ = t('change_password')
13
+ = form_for @user, :url => admin_security_path, :html => { :method => :put, 'data-onsubmit_status' => "#{t('saving_changes')}&#8230;" } do |f|
14
+ - render_region :form, :locals => {:f => f} do |form|
15
+ - form.edit_password do
16
+ %fieldset#change-password
17
+ %p
18
+ = f.label :password, t('new_password')
19
+ = f.password_field 'password', :value => '', :maxlength => 40, :autocomplete => 'new-password'
20
+ %p
21
+ = f.label :password_confirmation, t('password_confirmation')
22
+ = f.password_field 'password_confirmation', :value => '', :maxlength => 40, :autocomplete => 'new-password'
23
+
24
+ - render_region :form_bottom, :locals => {:f => f} do |form_bottom|
25
+ - form_bottom.edit_buttons do
26
+ .buttons
27
+ = save_model_button @user
28
+
29
+ - main.two_factor do
30
+ %fieldset
31
+ %h3
32
+ = t('two_factor_authentication')
33
+
34
+ - if @two_factor_enabled
35
+ %fieldset
36
+ %p
37
+ = t('security_controller.two_factor_is_enabled')
38
+ = button_to t('disable'),
39
+ disable_two_factor_admin_security_path,
40
+ method: :post,
41
+ data: { confirm: t('security_controller.two_factor_disable_confirm') },
42
+ class: "button button-margin-top"
43
+ - else
44
+ %fieldset
45
+ %p
46
+ = t('security_controller.scan_qr_instructions')
47
+ = image_tag @qr_png_data, alt: t('security_controller.qr_alt')
48
+ %p
49
+ = t('security_controller.manual_key_instructions')
50
+ %strong= current_user.otp_secret
51
+
52
+ .form-margin-top
53
+ = form_with url: verify_two_factor_admin_security_path, method: :post do |form|
54
+ %div
55
+ = form.label :otp_attempt, t('security_controller.enter_qr_code')
56
+ = form.text_field :otp_attempt, autofocus: true, size: 6, maxlength: 6
57
+ = form.submit t('security_controller.verify_and_enable')
@@ -1,10 +1,10 @@
1
1
  - body_classes << 'login_form'
2
2
  .login-form-content
3
3
  .visual
4
- = image_tag('/assets/admin/default_safe_login.svg', alt: 'web browser with padlock on top')
4
+ = image_tag('/assets/admin/default_safe_login.svg', alt: 'Web browser with padlock on top')
5
5
  .login
6
- %h1 Log in
7
- = form_for(resource, as: resource_name, url: authenticate_path) do |f|
6
+ %h1 Log In
7
+ = form_for(resource, as: resource_name, url: user_session_path) do |f|
8
8
  .field
9
9
  = f.label :email
10
10
  = f.email_field :email, autofocus: true, autocomplete: 'email'
@@ -18,8 +18,8 @@
18
18
  Remember Me
19
19
  %br
20
20
  .actions
21
- = f.submit 'Log in'
21
+ = f.submit 'Log In'
22
22
  - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
23
23
  = link_to 'Forgot your password?', new_password_path(resource_name)
24
24
  - if flash.alert
25
- .error= flash.alert
25
+ .error= flash.alert
@@ -0,0 +1,14 @@
1
+ - body_classes << 'login_form'
2
+ .login-form-content
3
+ .visual
4
+ = image_tag('/assets/admin/default_safe_login.svg', alt: 'Web browser with padlock on top')
5
+ .login
6
+ %h1
7
+ = t('two_factor_authentication')
8
+ = form_with url: admin_two_factor_path, method: :post, local: true do |form|
9
+ .field
10
+ = form.label :otp_attempt, t('security_controller.enter_qr_code')
11
+ = form.text_field :otp_attempt, autofocus: true, maxlength: 6, size: 6
12
+ %br
13
+ .actions
14
+ = form.submit t('security_controller.verify_code')
@@ -15,9 +15,6 @@
15
15
  = f.label :email, t('email_address') , :class => 'optional'
16
16
  = f.text_field 'email', :class => 'textbox', :maxlength => 255
17
17
 
18
- - form.edit_password do
19
- = render 'password_fields', :f => f
20
-
21
18
  - form.edit_roles do
22
19
  - if current_user.admin?
23
20
  %fieldset.multi_option
@@ -8,16 +8,14 @@
8
8
  = f.hidden_field :reset_password_token
9
9
  .field
10
10
  = f.label :password, 'New password'
11
- %br/
12
11
  - if @minimum_password_length
13
12
  %em
14
13
  (#{@minimum_password_length} characters minimum)
15
- %br/
14
+
16
15
  = f.password_field :password, autofocus: true, autocomplete: 'new-password'
16
+ %br/
17
17
  .field
18
18
  = f.label :password_confirmation, 'Confirm new password'
19
- %br/
20
19
  = f.password_field :password_confirmation, autocomplete: 'new-password'
21
20
  .actions
22
21
  = f.submit 'Change my password'
23
- = render 'devise/shared/links'
@@ -0,0 +1,5 @@
1
+ ActiveRecord::Encryption.configure(
2
+ primary_key: ENV["ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY"],
3
+ deterministic_key: ENV["ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY"],
4
+ key_derivation_salt: ENV["ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT"]
5
+ )
@@ -11,6 +11,10 @@ require 'devise'
11
11
  # Use this hook to configure devise mailer, warden hooks and so forth.
12
12
  # Many of these configuration options can be set straight in your model.
13
13
  Devise.setup do |config|
14
+ config.warden do |manager|
15
+ manager.default_strategies(:scope => :user).unshift :two_factor_authenticatable
16
+ end
17
+
14
18
  # The secret key used by Devise. Devise uses this key to generate
15
19
  # random tokens. Changing this key will render invalid all existing
16
20
  # confirmation, reset password and unlock tokens in the database.
@@ -12,7 +12,6 @@ Rails.application.reloader.to_prepare do
12
12
  config.define 'admin.pagination.per_page', type: :integer, default: 50
13
13
  config.define 'site.title', default: 'Your site title', allow_blank: false
14
14
  config.define 'site.host', default: 'www.example.com', allow_blank: false
15
- config.define 'user.allow_password_reset?', default: true
16
15
  config.define 'session_timeout', default: 2.weeks
17
16
  require 'multi_site/scoped_validation'
18
17
  end
@@ -52,11 +52,10 @@ en:
52
52
  buttons:
53
53
  create: 'Create %{name}'
54
54
  save_and_continue: 'Save and Continue Editing'
55
- save_and_view_draft: 'Save and View Draft'
56
- save_and_view_page: 'Save and View Page'
57
55
  save_changes: 'Save Changes'
58
56
  cancel: 'Cancel'
59
57
  change: 'Change'
58
+ change_password: 'Change Password'
60
59
  clipped_extension:
61
60
  actions: Actions
62
61
  all: 'All'
@@ -157,8 +156,6 @@ en:
157
156
  site:
158
157
  title: "site title"
159
158
  host: "site domain"
160
- user:
161
- allow_password_reset?: "allow password reset"
162
159
  content: 'Content'
163
160
  content_type: 'Content&#8209;Type'
164
161
  content_editor: 'Content Editor'
@@ -184,6 +181,8 @@ en:
184
181
  description: 'Description'
185
182
  design: 'Design'
186
183
  designer: 'Designer'
184
+ disable: 'Disable'
185
+ disabled: 'Disabled'
187
186
  draft: 'Draft'
188
187
  edit: 'Edit'
189
188
  editor: 'Editor'
@@ -191,14 +190,17 @@ en:
191
190
  edit_layout: 'Edit Layout'
192
191
  edit_page: 'Edit Page'
193
192
  edit_preferences: 'Edit Preferences'
193
+ edit_security_settings: 'Edit Security Settings'
194
194
  edit_settings: 'Edit Settings'
195
195
  edit_user: 'Edit User'
196
196
  email_address: 'E-mail Address'
197
+ enabled: 'Enabled'
197
198
  extension: 'Extension'
198
199
  extensions: 'Extensions'
199
200
  filter: 'Filter'
200
201
  hidden: 'Hidden'
201
202
  hide: 'Hide'
203
+ invalid_email_or_password: 'Invalid e-mail address or password.'
202
204
  keywords: 'Keywords'
203
205
  language: 'Language'
204
206
  layout: 'Layout'
@@ -269,11 +271,27 @@ en:
269
271
  validation_errors: "Validation errors occurred while processing this form. Please take a moment to review the form and correct any input errors before continuing."
270
272
  reviewed: 'Reviewed'
271
273
  roles: 'Roles'
272
- saving_changes: Saving Changes
273
- saving_preferences: Saving preferences
274
- scheduled: "Scheduled"
274
+ saving_changes: 'Saving Changes'
275
+ saving_preferences: 'Saving Preferences'
276
+ scheduled: 'Scheduled'
275
277
  search: 'Search'
276
278
  search_tags: 'Search Tags:'
279
+ security: 'Security'
280
+ security_controller:
281
+ enter_qr_code: 'Enter 6-Digit Verification Code'
282
+ error_updating_password: 'There was an error updating your password.'
283
+ manual_key_instructions: 'Or manually enter this secret key:'
284
+ password_updated: 'Password updated. Please log in again.'
285
+ scan_qr_instructions: 'Scan this QR code in your Authenticator app:'
286
+ two_factor_enabled: 'Two-Factor Authentication Enabled'
287
+ two_factor_is_enabled: 'Two-Factor Authentication is currently enabled.'
288
+ two_factor_disable_confirm: 'Are you sure you want to disable Two-Factor Authentication?'
289
+ two_factor_disabled: 'Two-Factor Authentication Disabled'
290
+ two_factor_disabled_error: 'Error Disabling Two-Factor Authentication'
291
+ two_factor_invalid_code: 'Invalid 2FA Code'
292
+ qr_alt: 'Scan this with Google Authenticator or Authy'
293
+ verify_code: 'Verify Code'
294
+ verify_and_enable: 'Verify and Enable 2FA'
277
295
  select:
278
296
  default: '<default>'
279
297
  inherit: '<inherit>'
@@ -314,6 +332,10 @@ en:
314
332
  at: 'at'
315
333
  by: 'by'
316
334
  last_updated: 'Last Updated'
335
+ two_factor_authentication: 'Two-Factor Authentication'
336
+ two_factor_controller:
337
+ invalid_code: 'Invalid two-factor authentication code.'
338
+ session_expired: 'Your session has expired. Please log in again.'
317
339
  type: 'Type'
318
340
  units:
319
341
  KB: "KB"
data/config/routes.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  TrustyCms::Application.routes.draw do
2
2
  root to: 'site#show_page'
3
- devise_for :users, module: :devise, skip: :registration
4
- as :user do
5
- post 'authenticate', to: 'devise/sessions#create', as: :authenticate
6
- end
3
+ devise_for :users,
4
+ controllers: { sessions: 'admin/sessions' },
5
+ skip: :registration
7
6
  post '/page-status/refresh' => 'page_status#refresh'
8
7
  get '/rad_social/mail' => 'social_mailer#social_mail_form', as: :rad_social_mail_form
9
8
  post '/rad_social/mail' => 'social_mailer#create_social_mail', as: :rad_create_social_mail
@@ -41,10 +40,16 @@ TrustyCms::Application.routes.draw do
41
40
  end
42
41
  end
43
42
 
43
+ match 'admin/preview' => 'admin/pages#preview', :as => :preview, :via => %i[post put]
44
44
  get 'admin' => 'admin/pages#index'
45
45
 
46
46
  namespace :admin do
47
47
  resource :preferences
48
+ resource :two_factor, only: [:show, :create], controller: 'two_factor', path: 'two-factor'
49
+ resource :security, controller: 'security' do
50
+ post :verify_two_factor, on: :collection
51
+ post :disable_two_factor, on: :collection
52
+ end
48
53
  resource :configuration, controller: 'configuration'
49
54
  resources :extensions, only: :index
50
55
  resources :page_parts
@@ -0,0 +1,7 @@
1
+ class AddDeviseTwoFactorToAdmins < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :admins, :otp_secret, :string
4
+ add_column :admins, :consumed_timestep, :integer
5
+ add_column :admins, :otp_required_for_login, :boolean
6
+ end
7
+ end
@@ -150,6 +150,7 @@ module TrustyCms
150
150
  settings = nav_tab('Settings')
151
151
  settings << nav_item('General', '/admin/configuration')
152
152
  settings << nav_item('Personal', '/admin/preferences')
153
+ settings << nav_item('Security', '/admin/security')
153
154
  settings << nav_item('Users', '/admin/users')
154
155
  settings << nav_item('Extensions', '/admin/extensions')
155
156
  nav << settings
@@ -190,13 +191,17 @@ module TrustyCms
190
191
  OpenStruct.new.tap do |user|
191
192
  user.preferences = RegionSet.new do |preferences|
192
193
  preferences.main.concat %w{edit_header edit_form}
193
- preferences.form.concat %w{edit_first_name edit_last_name edit_email edit_password}
194
+ preferences.form.concat %w{edit_first_name edit_last_name edit_email}
194
195
  preferences.form_bottom.concat %w{edit_buttons}
195
196
  end
197
+ user.security = RegionSet.new do |security|
198
+ security.main.concat %w{edit_header edit_form two_factor}
199
+ security.form.concat %w{edit_password}
200
+ security.form_bottom.concat %w{edit_buttons}
201
+ end
196
202
  user.edit = RegionSet.new do |edit|
197
203
  edit.main.concat %w{edit_header edit_form}
198
- edit.form.concat %w{edit_first_name edit_last_name edit_email edit_password
199
- edit_roles edit_notes}
204
+ edit.form.concat %w{edit_first_name edit_last_name edit_email edit_roles edit_notes}
200
205
  edit.form_bottom.concat %w{edit_buttons edit_timestamp}
201
206
  end
202
207
  user.index = RegionSet.new do |index|
@@ -231,11 +236,11 @@ module TrustyCms
231
236
  OpenStruct.new.tap do |configuration|
232
237
  configuration.show = RegionSet.new do |show|
233
238
  show.user.concat %w{preferences}
234
- show.trusty_config.concat %w{site defaults users}
239
+ show.trusty_config.concat %w{site defaults}
235
240
  end
236
241
  configuration.edit = RegionSet.new do |edit|
237
242
  edit.main.concat %w{edit_header edit_form}
238
- edit.form.concat %w{edit_site edit_defaults edit_users}
243
+ edit.form.concat %w{edit_site edit_defaults}
239
244
  edit.form_bottom.concat %w{edit_buttons}
240
245
  end
241
246
  end
@@ -1,3 +1,3 @@
1
1
  module TrustyCms
2
- VERSION = '7.0.27'.freeze
2
+ VERSION = '7.0.29'.freeze
3
3
  end
data/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "jquery-treetable": "^3.2.0-1",
14
14
  "jquery-ui": "^1.13.2",
15
15
  "jquery-ujs": "^1.2.2",
16
- "jquery-validation": "^1.19.5",
16
+ "jquery-validation": "^1.20.0",
17
17
  "js-cookie": "^3.0.1",
18
18
  "tablesaw": "^3.1.2"
19
19
  },
@@ -13,7 +13,6 @@ Rails.application.reloader.to_prepare do
13
13
  config.define 'admin.pagination.per_page', :type => :integer, :default => 50
14
14
  config.define 'site.title', :default => "Your site title", :allow_blank => false
15
15
  config.define 'site.host', :default => "www.example.com", :allow_blank => false
16
- config.define 'user.allow_password_reset?', :default => true
17
16
  end
18
17
 
19
18
  TrustyCms::Application.config do |config|
data/trusty_cms.gemspec CHANGED
@@ -33,6 +33,7 @@ a general purpose content management system--not merely a blogging engine.'
33
33
  s.add_dependency 'ckeditor', '>= 4.2.2', '< 4.4.0'
34
34
  s.add_dependency 'delocalize', '>= 0.2', '< 2.0'
35
35
  s.add_dependency 'devise'
36
+ s.add_dependency 'devise-two-factor'
36
37
  s.add_dependency 'drb'
37
38
  s.add_dependency 'execjs', '~> 2.7'
38
39
  s.add_dependency 'haml', '>= 5.0', '< 6.0'
@@ -55,6 +56,7 @@ a general purpose content management system--not merely a blogging engine.'
55
56
  s.add_dependency 'rdoc', '>= 5.1', '< 7.0'
56
57
  s.add_dependency 'RedCloth', '4.3.3'
57
58
  s.add_dependency 'roadie-rails'
59
+ s.add_dependency 'rqrcode'
58
60
  s.add_dependency 'sass-rails'
59
61
  s.add_dependency 'stringex', '>= 2.7.1', '< 2.9.0'
60
62
  s.add_dependency 'tzinfo', '>= 1.2.3', '< 2.1.0'
data/yarn.lock CHANGED
@@ -1029,10 +1029,10 @@ jquery-ujs@^1.2.2:
1029
1029
  dependencies:
1030
1030
  jquery ">=1.8.0"
1031
1031
 
1032
- jquery-validation@^1.19.5:
1033
- version "1.19.5"
1034
- resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.19.5.tgz#557495b7cad79716897057c4447ad3cd76fda811"
1035
- integrity sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==
1032
+ jquery-validation@^1.20.0:
1033
+ version "1.20.0"
1034
+ resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.20.0.tgz#dbff6d8fe61b07d4b6f844bf2f5405146556b991"
1035
+ integrity sha512-c8tg4ltIIP6L7l0bZ79sRzOJYquyjS48kQZ6iv8MJ2r0OYztxtkWYKTReZyU2/zVFYiINB29i0Z/IRNNuJQN1g==
1036
1036
 
1037
1037
  jquery@>=1.6:
1038
1038
  version "3.6.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trusty-cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.27
4
+ version: 7.0.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - TrustyCms CMS dev team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-03 00:00:00.000000000 Z
11
+ date: 2025-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activestorage-validator
@@ -140,6 +140,20 @@ dependencies:
140
140
  - - ">="
141
141
  - !ruby/object:Gem::Version
142
142
  version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: devise-two-factor
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :runtime
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
143
157
  - !ruby/object:Gem::Dependency
144
158
  name: drb
145
159
  requirement: !ruby/object:Gem::Requirement
@@ -472,6 +486,20 @@ dependencies:
472
486
  - - ">="
473
487
  - !ruby/object:Gem::Version
474
488
  version: '0'
489
+ - !ruby/object:Gem::Dependency
490
+ name: rqrcode
491
+ requirement: !ruby/object:Gem::Requirement
492
+ requirements:
493
+ - - ">="
494
+ - !ruby/object:Gem::Version
495
+ version: '0'
496
+ type: :runtime
497
+ prerelease: false
498
+ version_requirements: !ruby/object:Gem::Requirement
499
+ requirements:
500
+ - - ">="
501
+ - !ruby/object:Gem::Version
502
+ version: '0'
475
503
  - !ruby/object:Gem::Dependency
476
504
  name: sass-rails
477
505
  requirement: !ruby/object:Gem::Requirement
@@ -702,6 +730,7 @@ files:
702
730
  - app/assets/javascripts/admin/pagefield.js
703
731
  - app/assets/javascripts/admin/persist.min.js
704
732
  - app/assets/javascripts/admin/popup.js
733
+ - app/assets/javascripts/admin/preview.js
705
734
  - app/assets/javascripts/admin/sortable.js
706
735
  - app/assets/javascripts/admin/tabcontrol.js.erb
707
736
  - app/assets/javascripts/admin/tags.js
@@ -761,8 +790,11 @@ files:
761
790
  - app/controllers/admin/preferences_controller.rb
762
791
  - app/controllers/admin/references_controller.rb
763
792
  - app/controllers/admin/resource_controller.rb
793
+ - app/controllers/admin/security_controller.rb
794
+ - app/controllers/admin/sessions_controller.rb
764
795
  - app/controllers/admin/sites_controller.rb
765
796
  - app/controllers/admin/snippets_controller.rb
797
+ - app/controllers/admin/two_factor_controller.rb
766
798
  - app/controllers/admin/users_controller.rb
767
799
  - app/controllers/application_controller.rb
768
800
  - app/controllers/page_status_controller.rb
@@ -814,6 +846,7 @@ files:
814
846
  - app/models/trusty_cms/page_response_cache_director.rb
815
847
  - app/models/user.rb
816
848
  - app/models/user_action_observer.rb
849
+ - app/services/admin/preview_page_builder.rb
817
850
  - app/views/admin/assets/_asset.html.haml
818
851
  - app/views/admin/assets/_asset_table.html.haml
819
852
  - app/views/admin/assets/_errors.html.haml
@@ -869,6 +902,8 @@ files:
869
902
  - app/views/admin/removed/_show_bucket_link.html.haml
870
903
  - app/views/admin/removed/_upload_to_page.html.haml
871
904
  - app/views/admin/removed/bucket/_iframe.html.haml
905
+ - app/views/admin/security/edit.html.haml
906
+ - app/views/admin/sessions/new.html.haml
872
907
  - app/views/admin/sites/_form.haml
873
908
  - app/views/admin/sites/edit.haml
874
909
  - app/views/admin/sites/index.haml
@@ -880,9 +915,9 @@ files:
880
915
  - app/views/admin/snippets/index.html.haml
881
916
  - app/views/admin/snippets/new.html.haml
882
917
  - app/views/admin/snippets/remove.html.haml
918
+ - app/views/admin/two_factor/show.html.haml
883
919
  - app/views/admin/users/_choose_site.html.haml
884
920
  - app/views/admin/users/_form.html.haml
885
- - app/views/admin/users/_password_fields.html.haml
886
921
  - app/views/admin/users/edit.html.haml
887
922
  - app/views/admin/users/index.html.haml
888
923
  - app/views/admin/users/new.html.haml
@@ -890,7 +925,6 @@ files:
890
925
  - app/views/admin/welcome/login.html.haml
891
926
  - app/views/devise/passwords/edit.html.haml
892
927
  - app/views/devise/passwords/new.html.haml
893
- - app/views/devise/sessions/new.html.haml
894
928
  - app/views/devise/shared/_links.html.haml
895
929
  - app/views/devise_mailer/reset_password_instructions.html.haml
896
930
  - app/views/layouts/application.html.haml
@@ -913,6 +947,7 @@ files:
913
947
  - config/environments/development.rb
914
948
  - config/environments/production.rb
915
949
  - config/environments/test.rb
950
+ - config/initializers/active_record_encryption.rb
916
951
  - config/initializers/active_record_extensions.rb
917
952
  - config/initializers/assets.rb
918
953
  - config/initializers/devise.rb
@@ -979,6 +1014,7 @@ files:
979
1014
  - db/migrate/20250102212417_create_versions.rb
980
1015
  - db/migrate/20250103191133_create_version_associations.rb
981
1016
  - db/migrate/20250103191134_add_transaction_id_column_to_versions.rb
1017
+ - db/migrate/20250502162215_add_devise_two_factor_to_admins.rb
982
1018
  - db/schema.rb
983
1019
  - lib/active_record_extensions/active_record_extensions.rb
984
1020
  - lib/annotatable.rb
@@ -1,18 +0,0 @@
1
- %fieldset#display_password{:style=> (@user.new_record? or !@user.valid?) ? 'display: none' : nil}
2
- %label= t('password')
3
- %span.value
4
- &bull;&bull;&bull;&bull;&bull;
5
- %a.button{:href=>'#', :onclick=>"$('#display_password').hide(); $('#change_password').show()"}= t('change')
6
- %fieldset#change_password{:style=> (!@user.new_record? && @user.valid?) ? 'display: none' : nil}
7
- %p
8
- = f.label :password, t('new_password')
9
- = f.password_field 'password', :value => '', :maxlength => 40, :autocomplete => 'new-password'
10
- %p
11
- = f.label :password_confirmation, t('password_confirmation')
12
- = f.password_field 'password_confirmation', :value => '', :maxlength => 40, :autocomplete => 'new-password'
13
- - unless @user.new_record?
14
- %span
15
- = t('or')
16
- %a{:href=>'#', :class=>'cancel-button', :onclick=>" $('#display_password').show(); $('#change_password').hide()"}= t('cancel', class: 'alt')
17
-
18
-