diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9affdf4c..e8b62205 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -16,6 +16,9 @@ class UsersController < ApplicationController @users.active.where.not(role: 'integration') end + # Restrict visibility to roles at or below the current user's rank. + @users = @users.where(role: Whitelabel.manageable_roles(current_user.role)) + @pagy, @users = pagy(@users.preload(account: :account_accesses).where(account: current_account).order(id: :desc)) end @@ -40,7 +43,7 @@ class UsersController < ApplicationController end @user.password = SecureRandom.hex if @user.password.blank? - @user.role = User::ADMIN_ROLE unless role_valid?(@user.role) + @user.role = User.admin_role unless role_valid?(@user.role) if @user.save UserMailer.invitation_email(@user).deliver_later! @@ -92,7 +95,8 @@ class UsersController < ApplicationController private def role_valid?(role) - User::ROLES.include?(role) + # Role must exist AND be at or below the current user's own rank. + Whitelabel.manageable_roles(current_user.role).include?(role.to_s) end def build_user diff --git a/app/models/user.rb b/app/models/user.rb index 2950aeec..77537d61 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -53,6 +53,17 @@ class User < ApplicationRecord USER_ROLE = 'user' ].freeze + # Config-driven role list. Falls back to ROLES constant if no config. + def self.available_roles + Whitelabel.roles + rescue StandardError + ROLES + end + + def self.admin_role + available_roles.first + end + EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/ FULL_EMAIL_REGEXP = @@ -72,12 +83,12 @@ class User < ApplicationRecord devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable, :lockable - attribute :role, :string, default: ADMIN_ROLE + attribute :role, :string, default: -> { User.admin_role } attribute :uuid, :string, default: -> { SecureRandom.uuid } scope :active, -> { where(archived_at: nil) } scope :archived, -> { where.not(archived_at: nil) } - scope :admins, -> { where(role: ADMIN_ROLE) } + scope :admins, -> { where(role: admin_role) } validates :email, format: { with: /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\z/ } @@ -96,7 +107,7 @@ class User < ApplicationRecord def sidekiq? return true if Rails.env.development? - role == 'admin' + role == User.admin_role end def self.sign_in_after_reset_password diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index de66f062..105d5c66 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -6,54 +6,57 @@ <%= t('settings') %>
  • + <%# Profile is always visible — personal section %>
  • <%= link_to t('profile'), settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
  • -
  • - <%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300' %> -
  • + <% if Whitelabel.setting_section_visible?(current_user.role, 'account') %> +
  • + <%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300' %> +
  • + <% end %> <% unless Docuseal.multitenant? %> - <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'email') && can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
  • <%= link_to t('email'), settings_email_index_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'storage') && can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user %>
  • <%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> <% end %> - <% if can?(:read, AccountConfig) %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'notifications') && can?(:read, AccountConfig) %>
  • <%= link_to t('notifications'), settings_notifications_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'esign') && can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %>
  • <%= link_to t('e_signature'), settings_esign_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, AccountConfig) %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'personalization') && can?(:read, AccountConfig) %>
  • <%= link_to t('personalization'), settings_personalization_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, User) %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'users') && can?(:read, User) %>
  • <%= link_to t('users'), settings_users_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> <%= render 'shared/settings_nav_extra' %> <% if Docuseal.demo? || !Docuseal.multitenant? %> - <% if can?(:read, AccessToken) && current_user.role != User::USER_ROLE %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'api') && can?(:read, AccessToken) %>
  • <%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> <% end %> <% if Docuseal.demo? || !Docuseal.multitenant? || (current_user != true_user && !current_account.testing?) %> - <% if can?(:read, WebhookUrl) %> + <% if Whitelabel.setting_section_visible?(current_user.role, 'webhooks') && can?(:read, WebhookUrl) %>
  • <%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
  • diff --git a/app/views/users/_role_select.html.erb b/app/views/users/_role_select.html.erb index 7aa6ce0a..b7915280 100644 --- a/app/views/users/_role_select.html.erb +++ b/app/views/users/_role_select.html.erb @@ -1,9 +1,9 @@
    <%= f.label :role, class: 'label' %> <%= f.select :role, nil, {}, class: 'base-select' do %> - - - + <% Whitelabel.manageable_roles(current_user.role).each do |role_slug| %> + + <% end %> <% end %> <% if Docuseal.multitenant? %>