diff --git a/app/controllers/concerns/invitable.rb b/app/controllers/concerns/invitable.rb index a295e859f..dc93b30ec 100644 --- a/app/controllers/concerns/invitable.rb +++ b/app/controllers/concerns/invitable.rb @@ -9,7 +9,7 @@ module Invitable def invite_code_required? return false if @invitation.present? if self_hosted? - Setting.onboarding_state == "invite_only" + Setting.onboarding_state == "invite_only" && Setting.invite_only_default_family_id.blank? else ENV["REQUIRE_INVITE_CODE"] == "true" end diff --git a/app/controllers/invite_codes_controller.rb b/app/controllers/invite_codes_controller.rb index e97cb6ec0..f9bcf6760 100644 --- a/app/controllers/invite_codes_controller.rb +++ b/app/controllers/invite_codes_controller.rb @@ -1,12 +1,12 @@ class InviteCodesController < ApplicationController before_action :ensure_self_hosted + before_action :ensure_super_admin def index @invite_codes = InviteCode.all end def create - raise StandardError, "You are not allowed to generate invite codes" unless Current.user.admin? InviteCode.generate! redirect_back_or_to invite_codes_path, notice: "Code generated" end @@ -22,4 +22,8 @@ class InviteCodesController < ApplicationController def ensure_self_hosted redirect_to root_path unless self_hosted? end + + def ensure_super_admin + redirect_to root_path, alert: t("settings.hostings.not_authorized") unless Current.user.super_admin? + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 93cc303bd..074f46cbd 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -18,6 +18,11 @@ class RegistrationsController < ApplicationController @user.family = @invitation.family @user.role = @invitation.role @user.email = @invitation.email + elsif (default_family_id = Setting.invite_only_default_family_id).present? && + Setting.onboarding_state == "invite_only" && + (default_family = Family.find_by(id: default_family_id)) + @user.family = default_family + @user.role = :member else family = Family.new @user.family = family diff --git a/app/controllers/settings/hostings_controller.rb b/app/controllers/settings/hostings_controller.rb index e63a65c71..f3a63e9a7 100644 --- a/app/controllers/settings/hostings_controller.rb +++ b/app/controllers/settings/hostings_controller.rb @@ -4,6 +4,7 @@ class Settings::HostingsController < ApplicationController guard_feature unless: -> { self_hosted? } before_action :ensure_admin, only: [ :update, :clear_cache, :disconnect_external_assistant ] + before_action :ensure_super_admin_for_onboarding, only: :update def show @breadcrumbs = [ @@ -43,6 +44,11 @@ class Settings::HostingsController < ApplicationController Setting.require_email_confirmation = hosting_params[:require_email_confirmation] end + if hosting_params.key?(:invite_only_default_family_id) + value = hosting_params[:invite_only_default_family_id].presence + Setting.invite_only_default_family_id = value + end + if hosting_params.key?(:brand_fetch_client_id) Setting.brand_fetch_client_id = hosting_params[:brand_fetch_client_id] end @@ -160,7 +166,7 @@ class Settings::HostingsController < ApplicationController private def hosting_params return ActionController::Parameters.new unless params.key?(:setting) - params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :brand_fetch_client_id, :brand_fetch_high_res_logos, :twelve_data_api_key, :openai_access_token, :openai_uri_base, :openai_model, :openai_json_mode, :exchange_rate_provider, :securities_provider, :syncs_include_pending, :auto_sync_enabled, :auto_sync_time, :external_assistant_url, :external_assistant_token, :external_assistant_agent_id) + params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :invite_only_default_family_id, :brand_fetch_client_id, :brand_fetch_high_res_logos, :twelve_data_api_key, :openai_access_token, :openai_uri_base, :openai_model, :openai_json_mode, :exchange_rate_provider, :securities_provider, :syncs_include_pending, :auto_sync_enabled, :auto_sync_time, :external_assistant_url, :external_assistant_token, :external_assistant_agent_id) end def update_assistant_type @@ -175,6 +181,12 @@ class Settings::HostingsController < ApplicationController redirect_to settings_hosting_path, alert: t(".not_authorized") unless Current.user.admin? end + def ensure_super_admin_for_onboarding + onboarding_params = %i[onboarding_state invite_only_default_family_id] + return unless onboarding_params.any? { |p| hosting_params.key?(p) } + redirect_to settings_hosting_path, alert: t(".not_authorized") unless Current.user.super_admin? + end + def sync_auto_sync_scheduler! AutoSyncScheduler.sync! rescue StandardError => error diff --git a/app/models/setting.rb b/app/models/setting.rb index 376dedc27..a53e70273 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -73,6 +73,7 @@ class Setting < RailsSettings::Base field :onboarding_state, type: :string, default: DEFAULT_ONBOARDING_STATE field :require_invite_for_signup, type: :boolean, default: false field :require_email_confirmation, type: :boolean, default: ENV.fetch("REQUIRE_EMAIL_CONFIRMATION", "true") == "true" + field :invite_only_default_family_id, type: :string, default: nil def self.validate_onboarding_state!(state) return if ONBOARDING_STATES.include?(state) diff --git a/app/views/settings/hostings/_invite_code_settings.html.erb b/app/views/settings/hostings/_invite_code_settings.html.erb index 14e4439e3..cb02f7757 100644 --- a/app/views/settings/hostings/_invite_code_settings.html.erb +++ b/app/views/settings/hostings/_invite_code_settings.html.erb @@ -40,6 +40,29 @@ <% if Setting.onboarding_state == "invite_only" %> +
+
+

<%= t(".default_family_title") %>

+

<%= t(".default_family_description") %>

+
+ + <%= styled_form_with model: Setting.new, + url: settings_hosting_path, + method: :patch, + data: { controller: "auto-submit-form", auto_submit_form_trigger_event_value: "change" } do |form| %> +
+ <%= form.select :invite_only_default_family_id, + options_for_select( + [ [ t(".default_family_none"), "" ] ] + + Family.all.map { |f| [ f.name, f.id ] }, + Setting.invite_only_default_family_id + ), + { label: false }, + { data: { auto_submit_form_target: "auto" } } %> +
+ <% end %> +
+
<%= t(".generated_tokens") %> diff --git a/app/views/settings/hostings/show.html.erb b/app/views/settings/hostings/show.html.erb index adb78ec51..354cf86a4 100644 --- a/app/views/settings/hostings/show.html.erb +++ b/app/views/settings/hostings/show.html.erb @@ -22,8 +22,10 @@ <%= settings_section title: t(".sync_settings") do %> <%= render "settings/hostings/sync_settings" %> <% end %> -<%= settings_section title: t(".invites") do %> - <%= render "settings/hostings/invite_code_settings" %> +<% if Current.user.super_admin? %> + <%= settings_section title: t(".invites") do %> + <%= render "settings/hostings/invite_code_settings" %> + <% end %> <% end %> <%= settings_section title: t(".danger_zone") do %> <%= render "settings/hostings/danger_zone_settings" %> diff --git a/config/locales/views/settings/hostings/en.yml b/config/locales/views/settings/hostings/en.yml index cfe44a8ad..814d0b13c 100644 --- a/config/locales/views/settings/hostings/en.yml +++ b/config/locales/views/settings/hostings/en.yml @@ -7,6 +7,9 @@ en: email_confirmation_description: When enabled, users must confirm their email address when changing it. email_confirmation_title: Require email confirmation + default_family_title: Default family for new users + default_family_description: "Put new users on this family/group only if they have no invitation." + default_family_none: None (create new family) generate_tokens: Generate new code generated_tokens: Generated codes title: Onboarding diff --git a/test/controllers/invite_codes_controller_test.rb b/test/controllers/invite_codes_controller_test.rb index ea39395fb..2403a3732 100644 --- a/test/controllers/invite_codes_controller_test.rb +++ b/test/controllers/invite_codes_controller_test.rb @@ -4,17 +4,21 @@ class InviteCodesControllerTest < ActionDispatch::IntegrationTest setup do Rails.application.config.app_mode.stubs(:self_hosted?).returns(true) end - test "admin can generate invite codes" do - sign_in users(:family_admin) + test "super admin can generate invite codes" do + sign_in users(:sure_support_staff) assert_difference("InviteCode.count") do post invite_codes_url, params: {} end end - test "non-admin cannot generate invite codes" do - sign_in users(:family_member) + test "non-super-admin cannot generate invite codes" do + sign_in users(:family_admin) - assert_raises(StandardError) { post invite_codes_url, params: {} } + assert_no_difference("InviteCode.count") do + post invite_codes_url, params: {} + end + + assert_redirected_to root_path end end diff --git a/test/controllers/settings/hostings_controller_test.rb b/test/controllers/settings/hostings_controller_test.rb index 91f8b1f26..f4706c07c 100644 --- a/test/controllers/settings/hostings_controller_test.rb +++ b/test/controllers/settings/hostings_controller_test.rb @@ -51,6 +51,8 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest end test "can update onboarding state when self hosting is enabled" do + sign_in users(:sure_support_staff) + with_self_hosting do patch settings_hosting_url, params: { setting: { onboarding_state: "invite_only" } } diff --git a/test/system/settings_test.rb b/test/system/settings_test.rb index 25f2fee70..d994881c5 100644 --- a/test/system/settings_test.rb +++ b/test/system/settings_test.rb @@ -44,6 +44,7 @@ class SettingsTest < ApplicationSystemTestCase end test "can update self hosting settings" do + sign_in users(:sure_support_staff) Rails.application.config.app_mode.stubs(:self_hosted?).returns(true) Provider::Registry.stubs(:get_provider).with(:twelve_data).returns(nil) Provider::Registry.stubs(:get_provider).with(:yahoo_finance).returns(nil)