mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-28 16:36:48 +09:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ffeeea78 | ||
|
|
77bcad65b5 | ||
|
|
238f73c26f | ||
|
|
1feb2d94b9 | ||
|
|
49c0ce496c | ||
|
|
88f3f942f7 | ||
|
|
6f256f5f2d | ||
|
|
089a8b35c0 | ||
|
|
b52c318518 | ||
|
|
de5493d8a9 | ||
|
|
74346342df | ||
|
|
e4a49d52b2 | ||
|
|
b8919161a8 | ||
|
|
80585a9585 | ||
|
|
b3d97ecb0e | ||
|
|
94692026d3 | ||
|
|
0672673916 | ||
|
|
5667262d1c | ||
|
|
1babf3f9ed | ||
|
|
4d04f9beee | ||
|
|
bebe518c74 | ||
|
|
13897eac59 | ||
|
|
6bb0ee2436 | ||
|
|
550d4c75bd | ||
|
|
7b7b3f8a67 | ||
|
|
aec8747710 | ||
|
|
71b5edae01 | ||
|
|
b1325ccee7 | ||
|
|
5266ffc049 | ||
|
|
bd3b0c5988 | ||
|
|
e8626f9d5d | ||
|
|
796195c2cc | ||
|
|
559629537b | ||
|
|
42c411b660 | ||
|
|
6c59b8c82b | ||
|
|
20ba1cf2ae | ||
|
|
d323c3cfc0 | ||
|
|
53671b48e0 | ||
|
|
66b4e41281 | ||
|
|
6476bab4cc | ||
|
|
f7c3aaa2a9 | ||
|
|
593f8dd04c | ||
|
|
d3b6507ce2 | ||
|
|
32be03a240 | ||
|
|
8e94a4aa1e | ||
|
|
15db4033bc | ||
|
|
f56e33e323 | ||
|
|
bea2c89c2b | ||
|
|
b7c7d99dcd | ||
|
|
6197f20a31 | ||
|
|
02023b0f0c | ||
|
|
af81a4c1ac | ||
|
|
d4a132865a |
No files matched your search
@@ -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
|
||||
|
||||
+85
-80
@@ -26,32 +26,32 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/rails.git
|
||||
revision: 02660cd9e84aa57c6cf3bf6e08cc18e18292e2fa
|
||||
revision: 1a02651ac37fb64b4de2a2b73461d86acf9c98fb
|
||||
branch: main
|
||||
specs:
|
||||
actioncable (8.1.0.alpha)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
actioncable (8.2.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.1.0.alpha)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
activejob (= 8.1.0.alpha)
|
||||
activerecord (= 8.1.0.alpha)
|
||||
activestorage (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
actionmailbox (8.2.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
activejob (= 8.2.0.alpha)
|
||||
activerecord (= 8.2.0.alpha)
|
||||
activestorage (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.1.0.alpha)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
actionview (= 8.1.0.alpha)
|
||||
activejob (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
actionmailer (8.2.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
actionview (= 8.2.0.alpha)
|
||||
activejob (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.1.0.alpha)
|
||||
actionview (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
actionpack (8.2.0.alpha)
|
||||
actionview (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
@@ -59,65 +59,65 @@ GIT
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.1.0.alpha)
|
||||
actiontext (8.2.0.alpha)
|
||||
action_text-trix (~> 2.1.15)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
activerecord (= 8.1.0.alpha)
|
||||
activestorage (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
activerecord (= 8.2.0.alpha)
|
||||
activestorage (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
actionview (8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
activejob (8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
activerecord (8.1.0.alpha)
|
||||
activemodel (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
activemodel (8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
activerecord (8.2.0.alpha)
|
||||
activemodel (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.1.0.alpha)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
activejob (= 8.1.0.alpha)
|
||||
activerecord (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
activestorage (8.2.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
activejob (= 8.2.0.alpha)
|
||||
activerecord (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.1.0.alpha)
|
||||
activesupport (8.2.0.alpha)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
json
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
rails (8.1.0.alpha)
|
||||
actioncable (= 8.1.0.alpha)
|
||||
actionmailbox (= 8.1.0.alpha)
|
||||
actionmailer (= 8.1.0.alpha)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
actiontext (= 8.1.0.alpha)
|
||||
actionview (= 8.1.0.alpha)
|
||||
activejob (= 8.1.0.alpha)
|
||||
activemodel (= 8.1.0.alpha)
|
||||
activerecord (= 8.1.0.alpha)
|
||||
activestorage (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
rails (8.2.0.alpha)
|
||||
actioncable (= 8.2.0.alpha)
|
||||
actionmailbox (= 8.2.0.alpha)
|
||||
actionmailer (= 8.2.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
actiontext (= 8.2.0.alpha)
|
||||
actionview (= 8.2.0.alpha)
|
||||
activejob (= 8.2.0.alpha)
|
||||
activemodel (= 8.2.0.alpha)
|
||||
activerecord (= 8.2.0.alpha)
|
||||
activestorage (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.1.0.alpha)
|
||||
railties (8.1.0.alpha)
|
||||
actionpack (= 8.1.0.alpha)
|
||||
activesupport (= 8.1.0.alpha)
|
||||
railties (= 8.2.0.alpha)
|
||||
railties (8.2.0.alpha)
|
||||
actionpack (= 8.2.0.alpha)
|
||||
activesupport (= 8.2.0.alpha)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
@@ -135,8 +135,8 @@ GEM
|
||||
ast (2.4.3)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.4.1)
|
||||
bigdecimal (3.2.2)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (3.3.1)
|
||||
brakeman (7.1.1)
|
||||
racc
|
||||
builder (3.3.0)
|
||||
@@ -151,17 +151,17 @@ GEM
|
||||
xpath (~> 3.2)
|
||||
chunky_png (1.4.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
connection_pool (2.5.5)
|
||||
crack (1.0.0)
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
date (3.4.1)
|
||||
date (3.5.0)
|
||||
debug (1.11.0)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
drb (2.2.3)
|
||||
erb (5.0.2)
|
||||
erb (6.0.0)
|
||||
erubi (1.13.1)
|
||||
faker (3.5.2)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
@@ -172,7 +172,7 @@ GEM
|
||||
geared_pagination (1.2.0)
|
||||
activesupport (>= 5.0)
|
||||
addressable (>= 2.5.0)
|
||||
globalid (1.2.1)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
hashdiff (1.2.0)
|
||||
i18n (1.14.7)
|
||||
@@ -181,7 +181,7 @@ GEM
|
||||
mini_magick (>= 4.9.5, < 6)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.8.1)
|
||||
irb (1.15.2)
|
||||
irb (1.15.3)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
@@ -201,17 +201,18 @@ GEM
|
||||
loofah (2.24.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
mail (2.9.0)
|
||||
logger
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
marcel (1.1.0)
|
||||
matrix (0.4.3)
|
||||
mini_magick (5.3.1)
|
||||
logger
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.5)
|
||||
minitest (5.26.2)
|
||||
mocha (2.7.1)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
mono_logger (1.1.2)
|
||||
@@ -220,7 +221,7 @@ GEM
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
net-http-persistent (4.0.6)
|
||||
connection_pool (~> 2.2, >= 2.2.4)
|
||||
net-imap (0.5.9)
|
||||
net-imap (0.5.12)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -229,16 +230,17 @@ GEM
|
||||
timeout
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.9-aarch64-linux-gnu)
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.18.10-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm64-darwin)
|
||||
nokogiri (1.18.10-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-darwin)
|
||||
nokogiri (1.18.10-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-gnu)
|
||||
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)
|
||||
@@ -246,7 +248,7 @@ GEM
|
||||
platform_agent (1.0.1)
|
||||
activesupport (>= 5.2.0)
|
||||
useragent (~> 0.16.3)
|
||||
pp (0.6.2)
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
prism (1.4.0)
|
||||
@@ -257,7 +259,7 @@ GEM
|
||||
puma (6.6.1)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.0)
|
||||
rack (3.2.4)
|
||||
rack-protection (4.1.1)
|
||||
base64 (>= 0.1.0)
|
||||
logger (>= 1.6.0)
|
||||
@@ -281,10 +283,11 @@ GEM
|
||||
activesupport (> 3.1)
|
||||
railties (> 3.1)
|
||||
rainbow (3.1.1)
|
||||
rake (13.3.0)
|
||||
rdoc (6.14.2)
|
||||
rake (13.3.1)
|
||||
rdoc (6.16.1)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
redis (5.4.1)
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.25.2)
|
||||
@@ -292,7 +295,7 @@ GEM
|
||||
redis-namespace (1.11.0)
|
||||
redis (>= 4)
|
||||
regexp_parser (2.11.2)
|
||||
reline (0.6.2)
|
||||
reline (0.6.3)
|
||||
io-console (~> 0.5)
|
||||
resque (2.7.0)
|
||||
mono_logger (~> 1)
|
||||
@@ -367,21 +370,21 @@ GEM
|
||||
sqlite3 (2.7.3-x86_64-linux-gnu)
|
||||
stimulus-rails (1.3.4)
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.1.7)
|
||||
stringio (3.1.8)
|
||||
thor (1.4.0)
|
||||
thruster (0.1.15-aarch64-linux)
|
||||
thruster (0.1.15-arm64-darwin)
|
||||
thruster (0.1.15-x86_64-darwin)
|
||||
thruster (0.1.15-x86_64-linux)
|
||||
tilt (2.6.1)
|
||||
timeout (0.4.3)
|
||||
timeout (0.4.4)
|
||||
tsort (0.2.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (3.1.5)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.0.3)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
web-push (3.0.2)
|
||||
jwt (~> 3.0)
|
||||
@@ -407,6 +410,7 @@ PLATFORMS
|
||||
|
||||
DEPENDENCIES
|
||||
bcrypt
|
||||
benchmark
|
||||
brakeman
|
||||
capybara
|
||||
debug
|
||||
@@ -418,6 +422,7 @@ DEPENDENCIES
|
||||
kredis
|
||||
mocha
|
||||
net-http-persistent
|
||||
ostruct
|
||||
platform_agent
|
||||
propshaft!
|
||||
puma (~> 6.6)
|
||||
@@ -431,7 +436,7 @@ DEPENDENCIES
|
||||
selenium-webdriver
|
||||
sentry-rails
|
||||
sentry-ruby
|
||||
sqlite3 (~> 2.7)
|
||||
sqlite3
|
||||
stimulus-rails
|
||||
thruster
|
||||
turbo-rails!
|
||||
|
||||
@@ -127,6 +127,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input[type=checkbox][disabled]) {
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(100%) contrast(0.5);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[disabled] &[type=submit],
|
||||
&[type=submit]:disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -3,7 +3,9 @@ class AccountsController < ApplicationController
|
||||
before_action :set_account
|
||||
|
||||
def edit
|
||||
set_page_and_extract_portion_from account_users.ordered, per_page: 500
|
||||
users = account_users.ordered.without_bots
|
||||
@administrators, @members = users.partition(&:administrator?)
|
||||
set_page_and_extract_portion_from users, per_page: 500
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -17,7 +19,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
|
||||
|
||||
@@ -11,6 +11,8 @@ class FirstRunsController < ApplicationController
|
||||
user = FirstRun.create!(user_params)
|
||||
start_new_session_for user
|
||||
|
||||
redirect_to root_url
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
redirect_to root_url
|
||||
end
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
class Rooms::InvolvementsController < ApplicationController
|
||||
include RoomScoped
|
||||
|
||||
before_action :ensure_can_administer, only: %i[ update ]
|
||||
|
||||
def show
|
||||
@involvement = @membership.involvement
|
||||
end
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@ class Account < ApplicationRecord
|
||||
include Joinable
|
||||
|
||||
has_one_attached :logo
|
||||
has_json :settings, restrict_room_creation_to_administrators: false
|
||||
end
|
||||
@@ -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:)
|
||||
|
||||
@@ -64,6 +64,30 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="margin-block-start pad-block pad-inline-double fill-shade border-radius">
|
||||
<%= form_with model: @account, method: :put, data: { controller: "form" }, class: "flex align-center gap center" do |form| %>
|
||||
<div class="flex-item-grow flex align-center gap txt-align-start">
|
||||
<%= image_tag "crown.svg", class: "colorize--black", aria: { hidden: "true" }, size: 18 %> Must be admin to create new rooms
|
||||
</div>
|
||||
<%= 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 %>
|
||||
|
||||
<label class="switch">
|
||||
<input type="checkbox"
|
||||
class="switch__input"
|
||||
<%= "checked" if Current.account.settings.restrict_room_creation_to_administrators? %>
|
||||
data-action="change->form#submit">
|
||||
<span class="switch__btn round"></span>
|
||||
<span class="for-screen-reader">
|
||||
Must be admin to create new rooms
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= account_logo_tag style: "txt-xx-large center" %>
|
||||
<h1 class="flex-item-grow txt-x-large"><%= @account.name %></h1>
|
||||
@@ -76,7 +100,13 @@
|
||||
|
||||
<menu class="flex flex-column gap margin-none pad">
|
||||
<turbo-frame id="account_users">
|
||||
<%= render partial: "accounts/users/user", collection: @page.records, as: :user %>
|
||||
<%= render partial: "accounts/users/user", collection: @administrators, as: :user %>
|
||||
|
||||
<% if @administrators.any? && @members.any? %>
|
||||
<hr class="separator full-width" style="--border-style: solid">
|
||||
<% end %>
|
||||
|
||||
<%= render partial: "accounts/users/user", collection: @members, as: :user %>
|
||||
<%= render "accounts/users/next_page_container", page: @page.next_param unless @page.last? %>
|
||||
</turbo-frame>
|
||||
</menu>
|
||||
|
||||
@@ -9,23 +9,26 @@
|
||||
|
||||
<hr class="separator" aria-hidden="true">
|
||||
|
||||
<% if Current.user.can_administer? && user != Current.user && user.active? %>
|
||||
<% if Current.user.can_administer? && user.active? %>
|
||||
<% unless user.bot? %>
|
||||
<%= form_with model: user, url: account_user_path(user), data: { controller: "form" }, method: :patch do | form | %>
|
||||
<label class="btn txt-small flex-item-no-shrink" for="<%= dom_id(user, :role) %>">
|
||||
<span class="for-screen-reader">Role: <%= user.administrator? ? "Administrator" : "Member" %></span>
|
||||
<%= image_tag "crown.svg", size: 20, aria: { hidden: "true" } %>
|
||||
<%= form.check_box :role, { data: { action: "form#submit" }, hidden: true, id: dom_id(user, :role) }, "administrator", "member" %>
|
||||
<%= form.check_box :role, { data: { action: "form#submit" }, hidden: true, id: dom_id(user, :role), disabled: user == Current.user }, "administrator", "member" %>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= button_to account_user_path(user), method: :delete, class: "btn txt-small flex-item-no-shrink btn--negative", data: {
|
||||
turbo_confirm: "Are you sure you want to permanently remove this person from the account? This can’t be undone."} do %>
|
||||
<%= image_tag "minus.svg", size: 20, aria: { hidden: "true" } %>
|
||||
<span class="for-screen-reader">Delete <%= user.name %></span>
|
||||
<% unless user == Current.user %>
|
||||
<%= button_to account_user_path(user), method: :delete, class: "btn txt-small flex-item-no-shrink btn--negative", data: {
|
||||
turbo_confirm: "Are you sure you want to permanently remove this person from the account? This can’t be undone."} do %>
|
||||
<%= image_tag "minus.svg", size: 20, aria: { hidden: "true" } %>
|
||||
<span class="for-screen-reader">Delete <%= user.name %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% elsif user == Current.user %>
|
||||
<% end %>
|
||||
<% if user == Current.user %>
|
||||
<%= link_to user_profile_path, class: "btn txt-small flex-item-no-shrink", target: "_top" do %>
|
||||
<%= image_tag "pencil.svg", size: 20, aria: { hidden: "true" } %>
|
||||
<span class="for-screen-reader">My settings</span>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<%= button_to user_ban_path(user), method: :delete,
|
||||
class: "btn btn--negative full-width",
|
||||
data: { turbo_confirm: "Are you sure you want to remove the ban on this user?" } do %>
|
||||
<%= image_tag "cancel.svg", aria: { hidden: "true", label: "Ban #{@user.name}" } %>
|
||||
<%= image_tag "cancel.svg", aria: { hidden: "true", label: "Remove Ban #{@user.name}" } %>
|
||||
<span>Remove ban</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -36,8 +36,10 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= 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 %>
|
||||
</div>
|
||||
|
||||
|
||||
+22
-14
@@ -30,6 +30,17 @@ EXPORT_DEPLOY_LOCATION = "/local/app/product_releases/"
|
||||
|
||||
abort "You must specify a version, e.g. `bin/release 1.0.0`" unless VERSION
|
||||
|
||||
current_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
||||
if current_branch != "main"
|
||||
print "\e[1;33mWarning: You are on branch '#{current_branch}', not 'main'. Continue? [y/N] \e[0m"
|
||||
abort "Release aborted." unless $stdin.gets.strip.downcase.chars.first == "y"
|
||||
end
|
||||
|
||||
unless `git status --porcelain`.strip.empty?
|
||||
print "\e[1;33mWarning: You have uncommitted changes. Continue? [y/N] \e[0m"
|
||||
abort "Release aborted." unless $stdin.gets.strip.downcase.chars.first == "y"
|
||||
end
|
||||
|
||||
def announcing(message)
|
||||
puts "\n\e[1;36m#{message}\e[0m"
|
||||
yield
|
||||
@@ -45,23 +56,20 @@ on(:local) do
|
||||
end
|
||||
end
|
||||
|
||||
announcing "Tagging as #{VERSION}" do
|
||||
on(:local) do
|
||||
execute :git, :fetch, :origin, "--tags"
|
||||
announcing "Creating GitHub release v#{VERSION}..." do
|
||||
notes_file = "tmp/release_notes.md"
|
||||
|
||||
unless system "git tag -a v#{VERSION} -m 'Version #{VERSION}\n\n- list important changes here' -e"
|
||||
abort "Failed to create tag; check that the version doesn't already exist"
|
||||
on(:local) do
|
||||
execute :mkdir, "-p", "tmp"
|
||||
|
||||
File.write(notes_file, "# What's Changed\n\n- \n")
|
||||
system ENV["EDITOR"] || "vim", notes_file
|
||||
|
||||
unless system "gh", "release", "create", "v#{VERSION}", "--title", "v#{VERSION}", "--notes-file", notes_file
|
||||
abort "Failed to create release; check that the version doesn't already exist"
|
||||
end
|
||||
|
||||
unless test :git, :push, "--tags"
|
||||
abort "Failed to push tag; check that the version doesn't already exist"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
announcing "Creating GitHub release..." do
|
||||
on(:local) do
|
||||
execute :gh, :release, :create, "v#{VERSION}", "--notes-from-tag"
|
||||
execute :rm, notes_file
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Bundler.require(*Rails.groups)
|
||||
module Campfire
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
config.load_defaults 8.1
|
||||
config.load_defaults 8.2
|
||||
|
||||
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
||||
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddAccountSettings < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :accounts, :settings, :json
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class AddSingletonConstraintToAccounts < ActiveRecord::Migration[8.2]
|
||||
def change
|
||||
add_column :accounts, :singleton_guard, :integer, default: 0, null: false
|
||||
add_index :accounts, :singleton_guard, unique: true
|
||||
end
|
||||
end
|
||||
Generated
+4
-1
@@ -10,13 +10,16 @@
|
||||
#
|
||||
# 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_12_12_154340) 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.integer "singleton_guard", default: 0, null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["singleton_guard"], name: "index_accounts_on_singleton_guard", unique: true
|
||||
end
|
||||
|
||||
create_table "action_text_rich_texts", force: :cascade do |t|
|
||||
|
||||
@@ -12,21 +12,32 @@ ActiveSupport.on_load(:action_text_content) do
|
||||
|
||||
def attachable_from_possibly_expired_sgid(sgid)
|
||||
if message = sgid&.split("--")&.first
|
||||
encoded_message = JSON.parse Base64.strict_decode64(message)
|
||||
encoded_message = JSON.parse(decode_base64(message))
|
||||
|
||||
decoded_gid = if data = encoded_message.dig("_rails", "data")
|
||||
data
|
||||
elsif data = encoded_message.dig("_rails", "message")
|
||||
# Rails 7 used an older format of GID that serialized the payload using Marshall
|
||||
# Since we intentionally skip signature verification, we can't safely unmarshal the data
|
||||
# To work around this, we manually extract the GID from the marshaled data
|
||||
decode_base64(data).match(%r{(gid://campfire/[^/]+/\d+)})&.to_s
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
model = GlobalID.find(decoded_gid)
|
||||
|
||||
model.model_name.to_s.in?(ATTACHABLES_PERMITTED_WITH_INVALID_SIGNATURES) ? model : nil
|
||||
if model = GlobalID.find(decoded_gid)
|
||||
model.model_name.to_s.in?(ATTACHABLES_PERMITTED_WITH_INVALID_SIGNATURES) ? model : nil
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
def decode_base64(message)
|
||||
Base64.strict_decode64(message)
|
||||
rescue => _e
|
||||
Base64.urlsafe_decode64(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,7 +16,7 @@ module RestrictedHTTP
|
||||
|
||||
def private_ip?(ip)
|
||||
IPAddr.new(ip).then do |ipaddr|
|
||||
ipaddr.private? || ipaddr.loopback? || LOCAL_IP.include?(ipaddr)
|
||||
ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? || ipaddr.ipv4_mapped? || LOCAL_IP.include?(ipaddr)
|
||||
end
|
||||
rescue IPAddr::InvalidAddressError
|
||||
true
|
||||
|
||||
@@ -10,6 +10,38 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "edit groups administrators separately from members with a divider" do
|
||||
get edit_account_url
|
||||
|
||||
assert_response :ok
|
||||
|
||||
# Verify the divider exists between administrator and member sections
|
||||
assert_select "turbo-frame#account_users hr.separator.full-width"
|
||||
|
||||
# Verify administrators appear before the divider and members appear after
|
||||
# by checking the order of user names in the response body
|
||||
administrators = users(:david, :jason).map(&:name)
|
||||
members = users(:jz, :kevin).map(&:name)
|
||||
|
||||
response_body = response.body
|
||||
|
||||
# Find positions of divider and user names
|
||||
divider_position = response_body.index('hr class="separator full-width"')
|
||||
assert divider_position, "Divider should exist in the response"
|
||||
|
||||
administrators.each do |name|
|
||||
name_position = response_body.index("<strong>#{name}</strong>")
|
||||
assert name_position, "Administrator #{name} should appear in the response"
|
||||
assert name_position < divider_position, "Administrator #{name} should appear before the divider"
|
||||
end
|
||||
|
||||
members.each do |name|
|
||||
name_position = response_body.index("<strong>#{name}</strong>")
|
||||
assert name_position, "Member #{name} should appear in the response"
|
||||
assert name_position > divider_position, "Member #{name} should appear after the divider"
|
||||
end
|
||||
end
|
||||
|
||||
test "update" do
|
||||
assert users(:david).administrator?
|
||||
|
||||
|
||||
@@ -30,4 +30,28 @@ class FirstRunsControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
assert parsed_cookies.signed[:session_token]
|
||||
end
|
||||
|
||||
test "create is not vulnerable to race conditions" do
|
||||
num_attackers = 5
|
||||
url = first_run_url
|
||||
barrier = Concurrent::CyclicBarrier.new(num_attackers)
|
||||
|
||||
num_attackers.times.map do |i|
|
||||
Thread.new do
|
||||
session = ActionDispatch::Integration::Session.new(Rails.application)
|
||||
barrier.wait # All threads wait here, then fire simultaneously
|
||||
|
||||
session.post url, params: {
|
||||
user: {
|
||||
name: "Attacker#{i}",
|
||||
email_address: "attacker#{i}@example.com",
|
||||
password: "password123"
|
||||
}
|
||||
}
|
||||
end
|
||||
end.each(&:join)
|
||||
|
||||
assert_equal 1, Account.count, "Race condition allowed #{Account.count} accounts to be created!"
|
||||
assert_equal 1, User.where(role: :administrator).count, "Race condition allowed multiple admin users!"
|
||||
end
|
||||
end
|
||||
@@ -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: {
|
||||
|
||||
@@ -43,4 +43,13 @@ class Rooms::InvolvementsControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "a non-admin can update their room involvement" do
|
||||
sign_in :jz
|
||||
|
||||
assert_changes -> { memberships(:jz_designers).reload.involvement }, from: "everything", to: "mentions" do
|
||||
put room_involvement_url(rooms(:designers)), params: { involvement: "mentions" }
|
||||
assert_redirected_to room_involvement_url(rooms(:designers))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
require "test_helper"
|
||||
|
||||
class ActionText::AttachmentTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@user = users(:david)
|
||||
end
|
||||
|
||||
test "from_node" do
|
||||
html = %Q(<action-text-attachment sgid="#{@user.attachable_sgid}"></action-text-attachment>)
|
||||
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
|
||||
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
assert_equal @user, attachment.attachable
|
||||
end
|
||||
|
||||
test "from_node with a Rails 7 SGID" do
|
||||
gid = @user.to_gid.to_s
|
||||
marshaled_gid = Base64.urlsafe_encode64(Marshal.dump(gid))
|
||||
rails7_payload = { "_rails" => { "message" => marshaled_gid, "exp" => nil, "pur" => "attachable" } }
|
||||
rails7_message = Base64.strict_encode64(JSON.generate(rails7_payload))
|
||||
rails7_sgid = "#{rails7_message}--invalidsignature"
|
||||
|
||||
html = %Q(<action-text-attachment sgid="#{rails7_sgid}"></action-text-attachment>)
|
||||
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
|
||||
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
assert_equal @user, attachment.attachable
|
||||
end
|
||||
|
||||
test "from_node with an invalid SGID" do
|
||||
room = rooms(:pets).tap { |r| r.extend ActionText::Attachable }
|
||||
|
||||
html = %Q(<action-text-attachment sgid="#{room.attachable_sgid}invalid"></action-text-attachment>)
|
||||
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
|
||||
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
assert_kind_of ActionText::Attachables::MissingAttachable, attachment.attachable
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -21,6 +21,28 @@ class Opengraph::LocationTest < ActiveSupport::TestCase
|
||||
assert_equal [ "is not public" ], location.errors[:url]
|
||||
end
|
||||
|
||||
test "link-local addresses are blocked" do
|
||||
Resolv.stubs(:getaddress).with("metadata.internal").returns("169.254.169.254")
|
||||
|
||||
location = Opengraph::Location.new("https://metadata.internal")
|
||||
assert_not location.valid?
|
||||
assert_equal [ "is not public" ], location.errors[:url]
|
||||
end
|
||||
|
||||
test "IPv6 addresses mapped to IPv4 addresses are blocked" do
|
||||
Resolv.stubs(:getaddress).with("metadata.internal").returns("::ffff:192.168.1.1")
|
||||
|
||||
location = Opengraph::Location.new("https://metadata.internal")
|
||||
assert_not location.valid?
|
||||
assert_equal [ "is not public" ], location.errors[:url]
|
||||
|
||||
Resolv.stubs(:getaddress).with("metadata.internal").returns("::ffff:c0a8:0101")
|
||||
|
||||
location = Opengraph::Location.new("https://metadata.internal")
|
||||
assert_not location.valid?
|
||||
assert_equal [ "is not public" ], location.errors[:url]
|
||||
end
|
||||
|
||||
test "avoid reading file urls when expecting HTML" do
|
||||
large_file = Opengraph::Location.new("https://www.example.com/100gb.zip")
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ require "rails/test_help"
|
||||
require "minitest/unit"
|
||||
require "mocha/minitest"
|
||||
require "webmock/minitest"
|
||||
require "turbo/broadcastable/test_helper"
|
||||
|
||||
WebMock.enable!
|
||||
|
||||
|
||||
Reference in new issue
Block a user