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.)
+
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('