diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eca905460..af9863b7b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base skip_before_action :track_ahoy_visit + before_action :verify_private_forem protect_from_forgery with: :exception, prepend: true include SessionCurrentUser @@ -15,6 +16,17 @@ class ApplicationController < ActionController::Base error_too_many_requests(exc) end + def verify_private_forem + return if %w[shell async_info ga_events].include?(controller_name) + return if user_signed_in? || SiteConfig.public + + if api_action? + authenticate! + else + render template: "devise/registrations/new" + end + end + def not_found raise ActiveRecord::RecordNotFound, "Not Found" end @@ -107,4 +119,8 @@ class ApplicationController < ActionController::Base def anonymous_user User.new(ip_address: request.env["HTTP_FASTLY_CLIENT_IP"]) end + + def api_action? + self.class.to_s.start_with?("Api::") + end end diff --git a/app/controllers/internal/configs_controller.rb b/app/controllers/internal/configs_controller.rb index 747185684..a68ce94ef 100644 --- a/app/controllers/internal/configs_controller.rb +++ b/app/controllers/internal/configs_controller.rb @@ -35,6 +35,7 @@ module Internal health_check_token feed_style sponsor_headline + public ] allowed_params = allowed_params | diff --git a/app/models/site_config.rb b/app/models/site_config.rb index 280c8fcea..56f4c786b 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -132,6 +132,9 @@ class SiteConfig < RailsSettings::Base # These are the default UX settings, which can be overridded by individual user preferences. # basic (current default), rich (cover image on all posts), compact (more minimal) field :feed_style, type: :string, default: "basic" + # a non-public forem will redirect all unauthenticated pages to the registration page. + # a public forem could have more fine-grained authentication (listings ar private etc.) in future + field :public, type: :boolean, default: 1 # Broadcast field :welcome_notifications_live_at, type: :date diff --git a/app/views/internal/configs/show.html.erb b/app/views/internal/configs/show.html.erb index 5ed5d6875..097144ef8 100644 --- a/app/views/internal/configs/show.html.erb +++ b/app/views/internal/configs/show.html.erb @@ -736,6 +736,11 @@ placeholder: "basic, rich, or compact" %>
Determines which default feed the users sees (rich content, more minimal, etc.)
+
+ <%= internal_config_label :public %> + <%= f.check_box :public, checked: SiteConfig.public %> +
Are most of the site pages (posts, profiles, etc. public?) — BE VERY CAUTIOUS IN CHANGING THIS
+
diff --git a/spec/requests/api/v0/users_spec.rb b/spec/requests/api/v0/users_spec.rb index 5fa2a383b..7a94ba3b0 100644 --- a/spec/requests/api/v0/users_spec.rb +++ b/spec/requests/api/v0/users_spec.rb @@ -26,6 +26,12 @@ RSpec.describe "Api::V0::Users", type: :request do expect(response).to have_http_status(:ok) end + it "returns unauthenticated if no authentication and site config is set to private" do + SiteConfig.public = false + get api_user_path("by_username"), params: { url: user.username } + expect(response).to have_http_status(:unauthorized) + end + it "returns the correct json representation of the user", :aggregate_failures do get api_user_path(user.id) @@ -70,6 +76,24 @@ RSpec.describe "Api::V0::Users", type: :request do expect(response_user["joined_at"]).to eq(user.created_at.strftime("%b %e, %Y")) expect(response_user["profile_image"]).to eq(ProfileImage.new(user).get(width: 320)) end + + it "returns 200 if no authentication and site config is set to private but user is authenticated" do + SiteConfig.public = false + get me_api_users_path, params: { access_token: access_token.token } + + response_user = response.parsed_body + + expect(response_user["type_of"]).to eq("user") + + %w[ + id username name summary twitter_username github_username website_url location + ].each do |attr| + expect(response_user[attr]).to eq(user.public_send(attr)) + end + + expect(response_user["joined_at"]).to eq(user.created_at.strftime("%b %e, %Y")) + expect(response_user["profile_image"]).to eq(ProfileImage.new(user).get(width: 320)) + end end end end diff --git a/spec/requests/async_info_spec.rb b/spec/requests/async_info_spec.rb index 179de25ba..a76154498 100644 --- a/spec/requests/async_info_spec.rb +++ b/spec/requests/async_info_spec.rb @@ -13,6 +13,12 @@ RSpec.describe "AsyncInfo", type: :request do get "/async_info/base_data" expect(response.parsed_body.keys).to match_array(%w[broadcast param token]) end + + it "renders normal response even if site config is private" do + SiteConfig.public = false + get "/async_info/base_data" + expect(response.parsed_body.keys).to match_array(%w[broadcast param token]) + end end context "when logged in" do diff --git a/spec/requests/ga_events_spec.rb b/spec/requests/ga_events_spec.rb index 1071ceab3..4f5d7a37c 100644 --- a/spec/requests/ga_events_spec.rb +++ b/spec/requests/ga_events_spec.rb @@ -13,5 +13,13 @@ RSpec.describe "GaEvents", type: :request, vcr: vcr_option do }.to_json expect(response.body).to eq("") end + + it "renders normal response even if site config is private" do + SiteConfig.public = false + post "/fallback_activity_recorder", params: { + path: "/ben", user_language: "en" + }.to_json + expect(response.body).to eq("") + end end end diff --git a/spec/requests/internal/configs_spec.rb b/spec/requests/internal/configs_spec.rb index 8077372c5..f0dceb74d 100644 --- a/spec/requests/internal/configs_spec.rb +++ b/spec/requests/internal/configs_spec.rb @@ -498,6 +498,20 @@ RSpec.describe "/internal/config", type: :request do confirmation: confirmation_message } expect(SiteConfig.feed_style).to eq(feed_style) end + + it "updates public to true" do + is_public = true + post "/internal/config", params: { site_config: { public: is_public }, + confirmation: confirmation_message } + expect(SiteConfig.public).to eq(is_public) + end + + it "updates public to false" do + is_public = false + post "/internal/config", params: { site_config: { public: is_public }, + confirmation: confirmation_message } + expect(SiteConfig.public).to eq(is_public) + end end end end diff --git a/spec/requests/shells_spec.rb b/spec/requests/shells_spec.rb index 31f57b40c..d73abd602 100644 --- a/spec/requests/shells_spec.rb +++ b/spec/requests/shells_spec.rb @@ -11,6 +11,12 @@ RSpec.describe "Shells", type: :request do get "/shell_top" expect(response.header["Surrogate-Key"]).to include("shell-top") end + + it "renders normal response even if site config is private" do + SiteConfig.public = false + get "/shell_top" + expect(response.body).to include("user-signed-in") + end end describe "GET /shell_bottom" do @@ -23,5 +29,12 @@ RSpec.describe "Shells", type: :request do get "/shell_bottom" expect(response.header["Surrogate-Key"]).to include("shell-bottom") end + + it "renders normal response even if site config is private" do + SiteConfig.public = false + get "/shell_bottom" + expect(response.body).to include("footer-container") + end + end end diff --git a/spec/requests/stories_index_spec.rb b/spec/requests/stories_index_spec.rb index f733df249..7c82e5092 100644 --- a/spec/requests/stories_index_spec.rb +++ b/spec/requests/stories_index_spec.rb @@ -19,6 +19,12 @@ RSpec.describe "StoriesIndex", type: :request do expect(response.body).to include(CGI.escapeHTML(article.title)) end + it "renders registration page if site config is private" do + SiteConfig.public = false + get "/" + expect(response.body).to include("Great to have you") + end + it "renders proper description" do get "/" expect(response.body).to include(SiteConfig.community_description) @@ -334,6 +340,12 @@ RSpec.describe "StoriesIndex", type: :request do expect(response.body).to include("crayons-tabs__item crayons-tabs__item--current") end + it "renders properly even if site config is private" do + SiteConfig.public = false + get "/t/#{tag.name}" + expect(response.body).to include("crayons-tabs__item crayons-tabs__item--current") + end + it "has mod-action-button" do get "/t/#{tag.name}" expect(response.body).to include('