trusty-cms 7.0.28 → 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/app/assets/stylesheets/admin/modules/_buttons.scss +4 -1
- data/app/assets/stylesheets/admin/partials/_forms.scss +4 -0
- 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/models/trusty_cms/config.rb +0 -4
- data/app/models/user.rb +1 -1
- data/app/views/admin/configuration/edit.html.haml +0 -7
- data/app/views/admin/configuration/show.html.haml +21 -12
- 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 -5
- data/config/routes.rb +8 -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 +38 -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`.
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
@@ -55,6 +55,7 @@ en:
|
|
55
55
|
save_changes: 'Save Changes'
|
56
56
|
cancel: 'Cancel'
|
57
57
|
change: 'Change'
|
58
|
+
change_password: 'Change Password'
|
58
59
|
clipped_extension:
|
59
60
|
actions: Actions
|
60
61
|
all: 'All'
|
@@ -155,8 +156,6 @@ en:
|
|
155
156
|
site:
|
156
157
|
title: "site title"
|
157
158
|
host: "site domain"
|
158
|
-
user:
|
159
|
-
allow_password_reset?: "allow password reset"
|
160
159
|
content: 'Content'
|
161
160
|
content_type: 'Content‑Type'
|
162
161
|
content_editor: 'Content Editor'
|
@@ -182,6 +181,8 @@ en:
|
|
182
181
|
description: 'Description'
|
183
182
|
design: 'Design'
|
184
183
|
designer: 'Designer'
|
184
|
+
disable: 'Disable'
|
185
|
+
disabled: 'Disabled'
|
185
186
|
draft: 'Draft'
|
186
187
|
edit: 'Edit'
|
187
188
|
editor: 'Editor'
|
@@ -189,14 +190,17 @@ en:
|
|
189
190
|
edit_layout: 'Edit Layout'
|
190
191
|
edit_page: 'Edit Page'
|
191
192
|
edit_preferences: 'Edit Preferences'
|
193
|
+
edit_security_settings: 'Edit Security Settings'
|
192
194
|
edit_settings: 'Edit Settings'
|
193
195
|
edit_user: 'Edit User'
|
194
196
|
email_address: 'E-mail Address'
|
197
|
+
enabled: 'Enabled'
|
195
198
|
extension: 'Extension'
|
196
199
|
extensions: 'Extensions'
|
197
200
|
filter: 'Filter'
|
198
201
|
hidden: 'Hidden'
|
199
202
|
hide: 'Hide'
|
203
|
+
invalid_email_or_password: 'Invalid e-mail address or password.'
|
200
204
|
keywords: 'Keywords'
|
201
205
|
language: 'Language'
|
202
206
|
layout: 'Layout'
|
@@ -267,11 +271,27 @@ en:
|
|
267
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."
|
268
272
|
reviewed: 'Reviewed'
|
269
273
|
roles: 'Roles'
|
270
|
-
saving_changes: Saving Changes
|
271
|
-
saving_preferences: Saving
|
272
|
-
scheduled:
|
274
|
+
saving_changes: 'Saving Changes'
|
275
|
+
saving_preferences: 'Saving Preferences'
|
276
|
+
scheduled: 'Scheduled'
|
273
277
|
search: 'Search'
|
274
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'
|
275
295
|
select:
|
276
296
|
default: '<default>'
|
277
297
|
inherit: '<inherit>'
|
@@ -312,6 +332,10 @@ en:
|
|
312
332
|
at: 'at'
|
313
333
|
by: 'by'
|
314
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.'
|
315
339
|
type: 'Type'
|
316
340
|
units:
|
317
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
|
@@ -46,6 +45,11 @@ TrustyCms::Application.routes.draw do
|
|
46
45
|
|
47
46
|
namespace :admin do
|
48
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
|
49
53
|
resource :configuration, controller: 'configuration'
|
50
54
|
resources :extensions, only: :index
|
51
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
|
@@ -762,8 +790,11 @@ files:
|
|
762
790
|
- app/controllers/admin/preferences_controller.rb
|
763
791
|
- app/controllers/admin/references_controller.rb
|
764
792
|
- app/controllers/admin/resource_controller.rb
|
793
|
+
- app/controllers/admin/security_controller.rb
|
794
|
+
- app/controllers/admin/sessions_controller.rb
|
765
795
|
- app/controllers/admin/sites_controller.rb
|
766
796
|
- app/controllers/admin/snippets_controller.rb
|
797
|
+
- app/controllers/admin/two_factor_controller.rb
|
767
798
|
- app/controllers/admin/users_controller.rb
|
768
799
|
- app/controllers/application_controller.rb
|
769
800
|
- app/controllers/page_status_controller.rb
|
@@ -871,6 +902,8 @@ files:
|
|
871
902
|
- app/views/admin/removed/_show_bucket_link.html.haml
|
872
903
|
- app/views/admin/removed/_upload_to_page.html.haml
|
873
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
|
874
907
|
- app/views/admin/sites/_form.haml
|
875
908
|
- app/views/admin/sites/edit.haml
|
876
909
|
- app/views/admin/sites/index.haml
|
@@ -882,9 +915,9 @@ files:
|
|
882
915
|
- app/views/admin/snippets/index.html.haml
|
883
916
|
- app/views/admin/snippets/new.html.haml
|
884
917
|
- app/views/admin/snippets/remove.html.haml
|
918
|
+
- app/views/admin/two_factor/show.html.haml
|
885
919
|
- app/views/admin/users/_choose_site.html.haml
|
886
920
|
- app/views/admin/users/_form.html.haml
|
887
|
-
- app/views/admin/users/_password_fields.html.haml
|
888
921
|
- app/views/admin/users/edit.html.haml
|
889
922
|
- app/views/admin/users/index.html.haml
|
890
923
|
- app/views/admin/users/new.html.haml
|
@@ -892,7 +925,6 @@ files:
|
|
892
925
|
- app/views/admin/welcome/login.html.haml
|
893
926
|
- app/views/devise/passwords/edit.html.haml
|
894
927
|
- app/views/devise/passwords/new.html.haml
|
895
|
-
- app/views/devise/sessions/new.html.haml
|
896
928
|
- app/views/devise/shared/_links.html.haml
|
897
929
|
- app/views/devise_mailer/reset_password_instructions.html.haml
|
898
930
|
- app/views/layouts/application.html.haml
|
@@ -915,6 +947,7 @@ files:
|
|
915
947
|
- config/environments/development.rb
|
916
948
|
- config/environments/production.rb
|
917
949
|
- config/environments/test.rb
|
950
|
+
- config/initializers/active_record_encryption.rb
|
918
951
|
- config/initializers/active_record_extensions.rb
|
919
952
|
- config/initializers/assets.rb
|
920
953
|
- config/initializers/devise.rb
|
@@ -981,6 +1014,7 @@ files:
|
|
981
1014
|
- db/migrate/20250102212417_create_versions.rb
|
982
1015
|
- db/migrate/20250103191133_create_version_associations.rb
|
983
1016
|
- db/migrate/20250103191134_add_transaction_id_column_to_versions.rb
|
1017
|
+
- db/migrate/20250502162215_add_devise_two_factor_to_admins.rb
|
984
1018
|
- db/schema.rb
|
985
1019
|
- lib/active_record_extensions/active_record_extensions.rb
|
986
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
|
-
|