diff --git a/Gemfile b/Gemfile index 1532614..6820e1d 100644 --- a/Gemfile +++ b/Gemfile @@ -4,9 +4,11 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Rails gem "rails", github: "rails/rails", branch: "main" +gem "ostruct" +gem "benchmark" # Drivers -gem "sqlite3", "~> 2.7" +gem "sqlite3" gem "redis", "~> 5.4" # Deployment diff --git a/Gemfile.lock b/Gemfile.lock index 30b671e..75a617f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -135,6 +135,7 @@ GEM ast (2.4.3) base64 (0.3.0) bcrypt (3.1.20) + benchmark (0.5.0) bigdecimal (3.3.1) brakeman (7.1.1) racc @@ -239,6 +240,7 @@ GEM nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) openssl (3.3.0) + ostruct (0.6.3) parallel (1.27.0) parser (3.3.9.0) ast (~> 2.4.1) @@ -408,6 +410,7 @@ PLATFORMS DEPENDENCIES bcrypt + benchmark brakeman capybara debug @@ -419,6 +422,7 @@ DEPENDENCIES kredis mocha net-http-persistent + ostruct platform_agent propshaft! puma (~> 6.6) @@ -432,7 +436,7 @@ DEPENDENCIES selenium-webdriver sentry-rails sentry-ruby - sqlite3 (~> 2.7) + sqlite3 stimulus-rails thruster turbo-rails! diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index be05d66..462ae01 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -17,7 +17,7 @@ class AccountsController < ApplicationController end def account_params - params.require(:account).permit(:name, :logo) + params.require(:account).permit(:name, :logo, settings: {}) end def account_users diff --git a/app/controllers/rooms/closeds_controller.rb b/app/controllers/rooms/closeds_controller.rb index c6aa7f2..0e8bdf7 100644 --- a/app/controllers/rooms/closeds_controller.rb +++ b/app/controllers/rooms/closeds_controller.rb @@ -3,6 +3,7 @@ class Rooms::ClosedsController < RoomsController before_action :ensure_can_administer, only: %i[ update ] before_action :remember_last_room_visited, only: :show before_action :force_room_type, only: %i[ edit update ] + before_action :ensure_permission_to_create_rooms, only: %i[ new create ] DEFAULT_ROOM_NAME = "New room" diff --git a/app/controllers/rooms/opens_controller.rb b/app/controllers/rooms/opens_controller.rb index 5f40138..c2bda9e 100644 --- a/app/controllers/rooms/opens_controller.rb +++ b/app/controllers/rooms/opens_controller.rb @@ -3,6 +3,7 @@ class Rooms::OpensController < RoomsController before_action :ensure_can_administer, only: %i[ update ] before_action :remember_last_room_visited, only: :show before_action :force_room_type, only: %i[ edit update ] + before_action :ensure_permission_to_create_rooms, only: %i[ new create ] DEFAULT_ROOM_NAME = "New room" diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index b2d8142..2c308c8 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -31,6 +31,12 @@ class RoomsController < ApplicationController head :forbidden unless Current.user.can_administer?(@room) end + def ensure_permission_to_create_rooms + if Current.account.settings.restrict_room_creation_to_administrators? && !Current.user.administrator? + head :forbidden + end + end + def find_messages messages = @room.messages.with_creator.with_attachment_details.with_boosts diff --git a/app/models/account.rb b/app/models/account.rb index 36a7648..f0b7c98 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,4 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo + has_json :settings, restrict_room_creation_to_administrators: false end diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 1afefa6..d8e7b43 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -53,7 +53,7 @@ class Webhook < ApplicationRecord end def extract_text_from(response) - response.body.dup.force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ]) + String.new(response.body).force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ]) end def receive_text_reply_to(room, text:) diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb index 93a828a..1f9f868 100644 --- a/app/views/accounts/edit.html.erb +++ b/app/views/accounts/edit.html.erb @@ -64,6 +64,30 @@ <% end %> <% end %> + +
+ <%= form_with model: @account, method: :put, data: { controller: "form" }, class: "flex align-center gap center" do |form| %> +
+ <%= image_tag "crown.svg", class: "colorize--black", aria: { hidden: "true" }, size: 18 %> Must be admin to create new rooms +
+ <%= form.fields_for :settings, @account.settings do |settings_form| %> + <%= settings_form.hidden_field :restrict_room_creation_to_administrators, + value: !Current.account.settings.restrict_room_creation_to_administrators? %> + <% end %> + + + + <% end %> +
<% else %> <%= account_logo_tag style: "txt-xx-large center" %>

<%= @account.name %>

diff --git a/app/views/users/sidebars/show.html.erb b/app/views/users/sidebars/show.html.erb index 35ab9b1..709c310 100644 --- a/app/views/users/sidebars/show.html.erb +++ b/app/views/users/sidebars/show.html.erb @@ -36,8 +36,10 @@ <% end %> - <%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %> - <%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %> + <% if Current.user.administrator? || !Current.account.settings.restrict_room_creation_to_administrators? %> + <%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %> + <%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %> + <% end %> <% end %> diff --git a/db/migrate/20251126130131_add_account_settings.rb b/db/migrate/20251126130131_add_account_settings.rb new file mode 100644 index 0000000..375f53d --- /dev/null +++ b/db/migrate/20251126130131_add_account_settings.rb @@ -0,0 +1,5 @@ +class AddAccountSettings < ActiveRecord::Migration[7.2] + def change + add_column :accounts, :settings, :json + end +end diff --git a/db/schema.rb b/db/schema.rb index 07e0f23..658630d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,12 +10,13 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_11_26_115722) do +ActiveRecord::Schema[8.2].define(version: 2025_11_26_130131) do create_table "accounts", force: :cascade do |t| t.datetime "created_at", null: false t.text "custom_styles" t.string "join_code", null: false t.string "name", null: false + t.json "settings" t.datetime "updated_at", null: false end diff --git a/test/controllers/rooms/closeds_controller_test.rb b/test/controllers/rooms/closeds_controller_test.rb index 5172453..d8976ac 100644 --- a/test/controllers/rooms/closeds_controller_test.rb +++ b/test/controllers/rooms/closeds_controller_test.rb @@ -29,6 +29,16 @@ class Rooms::ClosedsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to room_url(Room.last) end + test "create forbidden by non-admin when account restricts creation to admins" do + accounts(:signal).settings.restrict_room_creation_to_administrators = true + accounts(:signal).save! + + sign_in :jz + post rooms_closeds_url, params: { room: { name: "My New Room" }, user_ids: [ users(:david).id, users(:kevin).id, users(:jason).id ] } + assert_response :forbidden + end + + test "update with membership revisions" do assert_difference -> { rooms(:designers).reload.users.count }, -1 do put rooms_closed_url(rooms(:designers)), params: { diff --git a/test/controllers/rooms/opens_controller_test.rb b/test/controllers/rooms/opens_controller_test.rb index 8820008..0f91294 100644 --- a/test/controllers/rooms/opens_controller_test.rb +++ b/test/controllers/rooms/opens_controller_test.rb @@ -24,6 +24,15 @@ class Rooms::OpensControllerTest < ActionDispatch::IntegrationTest assert_redirected_to room_url(Room.last) end + test "create forbidden by non-admin when account restricts creation to admins" do + accounts(:signal).settings.restrict_room_creation_to_administrators = true + accounts(:signal).save! + + sign_in :jz + post rooms_opens_url, params: { room: { name: "My New Room" } } + assert_response :forbidden + end + test "only admins or creators can update" do sign_in :jz diff --git a/test/models/account_test.rb b/test/models/account_test.rb index 94d4f83..2585ba0 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -1,4 +1,18 @@ require "test_helper" class AccountTest < ActiveSupport::TestCase + test "settings" do + accounts(:signal).settings.restrict_room_creation_to_administrators = true + assert accounts(:signal).settings.restrict_room_creation_to_administrators? + assert_equal({ "restrict_room_creation_to_administrators" => true }, accounts(:signal)[:settings]) + + accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "true" }) + assert accounts(:signal).reload.settings.restrict_room_creation_to_administrators? + + accounts(:signal).settings.restrict_room_creation_to_administrators = false + assert_not accounts(:signal).settings.restrict_room_creation_to_administrators? + assert_equal({ "restrict_room_creation_to_administrators" => false }, accounts(:signal)[:settings]) + accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" }) + assert_not accounts(:signal).reload.settings.restrict_room_creation_to_administrators? + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 33a20be..9c1310c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require "rails/test_help" require "minitest/unit" require "mocha/minitest" require "webmock/minitest" +require "turbo/broadcastable/test_helper" WebMock.enable!