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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -1
- data/INSTALL.md +21 -3
- data/README.md +17 -5
- data/app/assets/javascripts/admin/preview.js +40 -0
- data/app/assets/javascripts/admin.js +1 -0
- data/app/assets/stylesheets/admin/modules/_buttons.scss +4 -1
- data/app/assets/stylesheets/admin/partials/_forms.scss +4 -0
- data/app/controllers/admin/pages_controller.rb +29 -0
- data/app/controllers/admin/resource_controller.rb +0 -2
- data/app/controllers/admin/security_controller.rb +74 -0
- data/app/controllers/admin/sessions_controller.rb +43 -0
- data/app/controllers/admin/two_factor_controller.rb +42 -0
- data/app/controllers/application_controller.rb +7 -0
- data/app/helpers/application_helper.rb +0 -10
- data/app/models/trusty_cms/config.rb +0 -4
- data/app/models/user.rb +1 -1
- data/app/services/admin/preview_page_builder.rb +54 -0
- data/app/views/admin/configuration/edit.html.haml +0 -7
- data/app/views/admin/configuration/show.html.haml +21 -12
- data/app/views/admin/pages/_fields.html.haml +17 -21
- data/app/views/admin/preferences/edit.html.haml +1 -4
- data/app/views/admin/security/edit.html.haml +57 -0
- data/app/views/{devise → admin}/sessions/new.html.haml +5 -5
- data/app/views/admin/two_factor/show.html.haml +14 -0
- data/app/views/admin/users/_form.html.haml +0 -3
- data/app/views/devise/passwords/edit.html.haml +2 -4
- data/config/initializers/active_record_encryption.rb +5 -0
- data/config/initializers/devise.rb +4 -0
- data/config/initializers/trusty_cms_config.rb +0 -1
- data/config/locales/en.yml +29 -7
- data/config/routes.rb +9 -4
- data/db/migrate/20250502162215_add_devise_two_factor_to_admins.rb +7 -0
- data/lib/trusty_cms/admin_ui.rb +10 -5
- data/lib/trusty_cms/version.rb +1 -1
- data/package.json +1 -1
- data/spec/dummy/config/initializers/trusty_cms_config.rb +0 -1
- data/trusty_cms.gemspec +2 -0
- data/yarn.lock +4 -4
- metadata +40 -4
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 520f342b36983dbca58c14ba3b104f212ed80c271797d63081212d8945ca7ade
|
4
|
+
data.tar.gz: 8f0204809ddbd49f9d2db4dde486f92a05bd1da1f410b73764a6d79edab26b73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31fd3cb0dcbf88a788b127ac94404e9b813f6af5e09a2533eceab1b7974c821f9eae10884704df8ba59ad3c4a3ee7b5885a8b65024dcda1cfe0f1d6374b8db8e
|
7
|
+
data.tar.gz: 5ba2f9d8b2bcbd0875d058a35e7035058beed413c835ae5ac3e66a579ecc9b14aad4a86714f8c12eaf16e877e4c5c88291260b4f243523193e8d7bfa0a9089e4
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
trusty-cms (7.0.
|
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.
|
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
|
+
});
|
@@ -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 :
|
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
|
-
|
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 •••••
|
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
|
-
|
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
|
+
••••••••••••••••••
|
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
|
-
|
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, :
|
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 :
|
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
|
-
=
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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'),
|
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')}…" } 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: '
|
4
|
+
= image_tag('/assets/admin/default_safe_login.svg', alt: 'Web browser with padlock on top')
|
5
5
|
.login
|
6
|
-
%h1 Log
|
7
|
-
= form_for(resource, as: resource_name, url:
|
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
|
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
|
-
|
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'
|
@@ -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
|
data/config/locales/en.yml
CHANGED
@@ -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‑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
|
274
|
-
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,
|
4
|
-
|
5
|
-
|
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
|
data/lib/trusty_cms/admin_ui.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
243
|
+
edit.form.concat %w{edit_site edit_defaults}
|
239
244
|
edit.form_bottom.concat %w{edit_buttons}
|
240
245
|
end
|
241
246
|
end
|
data/lib/trusty_cms/version.rb
CHANGED
data/package.json
CHANGED
@@ -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.
|
1033
|
-
version "1.
|
1034
|
-
resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.
|
1035
|
-
integrity sha512-
|
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.
|
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-
|
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
|
-
•••••
|
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
|
-
|