diff --git a/CHANGELOG.md b/CHANGELOG.md index f39e07336..a7bbf55c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ All notable changes to this project will be documented in this file. - Manually lock and unlock enterprise users plausible/analytics#2197 - ARM64 support for docker images plausible/analytics#2103 - Add support for international domain names (IDNs) plausible/analytics#2034 +- Allow self-hosters to register an account on first launch ### Fixed - Plausible script does not prevent default if it's been prevented by an external script [plausible/analytics#1941](https://github.com/plausible/analytics/issues/1941) diff --git a/config/.env.test b/config/.env.test index 0f1a6f5d2..ec2510711 100644 --- a/config/.env.test +++ b/config/.env.test @@ -6,8 +6,6 @@ CRON_ENABLED=false LOG_LEVEL=warn ENVIRONMENT=test MAILER_ADAPTER=Bamboo.TestAdapter -ADMIN_USER_EMAIL=admin@email.com -ADMIN_USER_PWD=fakepassword ENABLE_EMAIL_VERIFICATION=true SELFHOST=false SITE_LIMIT=3 diff --git a/config/runtime.exs b/config/runtime.exs index 00002ac7b..08e14b159 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -58,9 +58,6 @@ db_url = db_socket_dir = get_var_from_path_or_env(config_dir, "DATABASE_SOCKET_DIR") -admin_user = get_var_from_path_or_env(config_dir, "ADMIN_USER_NAME") -admin_email = get_var_from_path_or_env(config_dir, "ADMIN_USER_EMAIL") - super_admin_user_ids = get_var_from_path_or_env(config_dir, "ADMIN_USER_IDS", "") |> String.split(",") @@ -71,7 +68,6 @@ super_admin_user_ids = end) |> Enum.filter(& &1) -admin_pwd = get_var_from_path_or_env(config_dir, "ADMIN_USER_PWD") env = get_var_from_path_or_env(config_dir, "ENVIRONMENT", "prod") mailer_adapter = get_var_from_path_or_env(config_dir, "MAILER_ADAPTER", "Bamboo.SMTPAdapter") mailer_email = get_var_from_path_or_env(config_dir, "MAILER_EMAIL", "hello@plausible.local") @@ -135,10 +131,10 @@ geolite2_country_db = ip_geolocation_db = get_var_from_path_or_env(config_dir, "IP_GEOLOCATION_DB", geolite2_country_db) geonames_source_file = get_var_from_path_or_env(config_dir, "GEONAMES_SOURCE_FILE") -disable_auth = - config_dir - |> get_var_from_path_or_env("DISABLE_AUTH", "false") - |> String.to_existing_atom() +if System.get_env("DISABLE_AUTH") do + require Logger + Logger.warn("DISABLE_AUTH env var is no longer supported") +end enable_email_verification = config_dir @@ -193,9 +189,6 @@ disable_cron = |> String.to_existing_atom() config :plausible, - admin_user: admin_user, - admin_email: admin_email, - admin_pwd: admin_pwd, environment: env, mailer_email: mailer_email, super_admin_user_ids: super_admin_user_ids, @@ -206,9 +199,8 @@ config :plausible, domain_blacklist: domain_blacklist config :plausible, :selfhost, - disable_authentication: disable_auth, enable_email_verification: enable_email_verification, - disable_registration: if(!disable_auth, do: disable_registration, else: false) + disable_registration: disable_registration config :plausible, PlausibleWeb.Endpoint, url: [scheme: base_url.scheme, host: base_url.host, path: base_url.path, port: base_url.port], diff --git a/lib/plausible_release.ex b/lib/plausible_release.ex index 69fde22d4..fe03b2941 100644 --- a/lib/plausible_release.ex +++ b/lib/plausible_release.ex @@ -1,5 +1,7 @@ defmodule Plausible.Release do use Plausible.Repo + require Logger + @app :plausible @start_apps [ :postgrex, @@ -7,24 +9,13 @@ defmodule Plausible.Release do :ecto ] - def init_admin do - prepare() + @spec selfhost? :: boolean + def selfhost? do + Application.fetch_env!(@app, :is_selfhost) + end - {admin_email, admin_user, admin_pwd} = - validate_admin( - {Application.get_env(:plausible, :admin_email), - Application.get_env(:plausible, :admin_user), - Application.get_env(:plausible, :admin_pwd)} - ) - - case Plausible.Auth.find_user_by(email: admin_email) do - nil -> - {:ok, _} = Plausible.Auth.create_user(admin_user, admin_email, admin_pwd) - IO.puts("Admin user created successful!") - - _ -> - IO.puts("Admin user already exists. I won't override, bailing") - end + def should_be_first_launch? do + selfhost?() and not (_has_users? = Repo.exists?(Plausible.Auth.User)) end def migrate do @@ -81,18 +72,6 @@ defmodule Plausible.Release do ############################## - defp validate_admin({nil, nil, nil}) do - random_user = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) - random_pwd = :crypto.strong_rand_bytes(20) |> Base.encode64() |> binary_part(0, 20) - random_email = "#{random_user}@#{System.get_env("HOST")}" - IO.puts("generated admin user/password: #{random_email} / #{random_pwd}") - {random_email, random_user, random_pwd} - end - - defp validate_admin({admin_email, admin_user, admin_password}) do - {admin_email, admin_user, admin_password} - end - defp repos do Application.fetch_env!(@app, :ecto_repos) end diff --git a/lib/plausible_web/controllers/auth_controller.ex b/lib/plausible_web/controllers/auth_controller.ex index adde571fe..e67a858e2 100644 --- a/lib/plausible_web/controllers/auth_controller.ex +++ b/lib/plausible_web/controllers/auth_controller.ex @@ -1,7 +1,7 @@ defmodule PlausibleWeb.AuthController do use PlausibleWeb, :controller use Plausible.Repo - alias Plausible.Auth + alias Plausible.{Auth, Release} require Logger plug PlausibleWeb.RequireLoggedOutPlug @@ -24,52 +24,63 @@ defmodule PlausibleWeb.AuthController do :activate_form ] - def register_form(conn, _params) do - if Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_registration) != false do - redirect(conn, to: Routes.auth_path(conn, :login_form)) - else - changeset = Plausible.Auth.User.changeset(%Plausible.Auth.User{}) + plug :maybe_disable_registration when action in [:register_form, :register] + plug :assign_is_selfhost - render(conn, "register_form.html", - changeset: changeset, - layout: {PlausibleWeb.LayoutView, "focus.html"} - ) + defp maybe_disable_registration(conn, _opts) do + selfhost_config = Application.get_env(:plausible, :selfhost) + disable_registration = Keyword.fetch!(selfhost_config, :disable_registration) + first_launch? = Release.should_be_first_launch?() + + cond do + first_launch? -> + conn + + disable_registration in [:invite_only, true] -> + conn |> redirect(to: Routes.auth_path(conn, :login_form)) |> halt() + + true -> + conn end end + defp assign_is_selfhost(conn, _opts) do + assign(conn, :is_selfhost, Plausible.Release.selfhost?()) + end + + def register_form(conn, _params) do + changeset = Auth.User.changeset(%Auth.User{}) + + render(conn, "register_form.html", + changeset: changeset, + layout: {PlausibleWeb.LayoutView, "focus.html"} + ) + end + def register(conn, params) do - if Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_registration) != false do - redirect(conn, to: Routes.auth_path(conn, :login_form)) - else - user = Plausible.Auth.User.new(params["user"]) + conn = put_layout(conn, {PlausibleWeb.LayoutView, "focus.html"}) + user = Plausible.Auth.User.new(params["user"]) - if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do - case Repo.insert(user) do - {:ok, user} -> - conn = set_user_session(conn, user) + if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do + case Repo.insert(user) do + {:ok, user} -> + conn = set_user_session(conn, user) - case user.email_verified do - false -> - send_email_verification(user) - redirect(conn, to: Routes.auth_path(conn, :activate_form)) + if user.email_verified do + redirect(conn, to: Routes.site_path(conn, :new)) + else + send_email_verification(user) + redirect(conn, to: Routes.auth_path(conn, :activate_form)) + end - true -> - redirect(conn, to: Routes.site_path(conn, :new)) - end - - {:error, changeset} -> - render(conn, "register_form.html", - changeset: changeset, - layout: {PlausibleWeb.LayoutView, "focus.html"} - ) - end - else - render(conn, "register_form.html", - changeset: user, - captcha_error: "Please complete the captcha to register", - layout: {PlausibleWeb.LayoutView, "focus.html"} - ) + {:error, changeset} -> + render(conn, "register_form.html", changeset: changeset) end + else + render(conn, "register_form.html", + changeset: user, + captcha_error: "Please complete the captcha to register" + ) end end diff --git a/lib/plausible_web/controllers/page_controller.ex b/lib/plausible_web/controllers/page_controller.ex index 64e217d08..03ec4f2aa 100644 --- a/lib/plausible_web/controllers/page_controller.ex +++ b/lib/plausible_web/controllers/page_controller.ex @@ -1,14 +1,15 @@ defmodule PlausibleWeb.PageController do use PlausibleWeb, :controller use Plausible.Repo - plug PlausibleWeb.AutoAuthPlug + @doc """ + The root path is never accessible in Plausible.Cloud because it is handled by the upstream reverse proxy. + + This controller action is only ever triggered in self-hosted Plausible. It redirects the user to /login where `PlausibleWeb.RequireLoggedOutPlug` plug kicks in. If they are already logged in, they are redirected to /sites, otherwise they'll see the /login page. + """ def index(conn, _params) do - if conn.assigns[:current_user] do - user = conn.assigns[:current_user] |> Repo.preload(:sites) - render(conn, "sites.html", sites: user.sites) - else - render(conn, "index.html") - end + conn + |> put_session(:login_dest, conn.request_path) + |> redirect(to: "/login") end end diff --git a/lib/plausible_web/plugs/auto_auth_plug.ex b/lib/plausible_web/plugs/auto_auth_plug.ex deleted file mode 100644 index b56c4fc8f..000000000 --- a/lib/plausible_web/plugs/auto_auth_plug.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule PlausibleWeb.AutoAuthPlug do - import Plug.Conn - alias PlausibleWeb.AuthController - - def init(options) do - options - end - - def call(conn, _opts) do - cond do - Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_authentication) -> - conn - |> AuthController.login(%{ - "email" => Application.fetch_env!(:plausible, :admin_email), - "password" => Application.fetch_env!(:plausible, :admin_pwd) - }) - |> halt - - true -> - Plug.Conn.put_session(conn, :login_dest, conn.request_path) - |> Phoenix.Controller.redirect(to: "/login") - |> halt - end - end -end diff --git a/lib/plausible_web/plugs/first_launch_plug.ex b/lib/plausible_web/plugs/first_launch_plug.ex new file mode 100644 index 000000000..6588e338b --- /dev/null +++ b/lib/plausible_web/plugs/first_launch_plug.ex @@ -0,0 +1,26 @@ +defmodule PlausibleWeb.FirstLaunchPlug do + @moduledoc """ + Redirects first-launch users to registration page. + """ + + @behaviour Plug + alias Plausible.Release + + @impl true + def init(opts) do + _path = Keyword.fetch!(opts, :redirect_to) + end + + @impl true + def call(%Plug.Conn{request_path: path} = conn, path), do: conn + + def call(conn, redirect_to) do + if Release.should_be_first_launch?() do + conn + |> Phoenix.Controller.redirect(to: redirect_to) + |> Plug.Conn.halt() + else + conn + end + end +end diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 2667b49f3..04b750f07 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -8,6 +8,7 @@ defmodule PlausibleWeb.Router do plug :fetch_session plug :fetch_flash plug :put_secure_browser_headers + plug PlausibleWeb.FirstLaunchPlug, redirect_to: "/register" plug PlausibleWeb.SessionTimeoutPlug, timeout_after_seconds: @two_weeks_in_seconds plug PlausibleWeb.AuthPlug plug PlausibleWeb.LastSeenPlug diff --git a/lib/plausible_web/templates/auth/register_form.html.eex b/lib/plausible_web/templates/auth/register_form.html.eex index 0b63c07ba..9e5c841af 100644 --- a/lib/plausible_web/templates/auth/register_form.html.eex +++ b/lib/plausible_web/templates/auth/register_form.html.eex @@ -1,5 +1,11 @@
Already have an account? <%= link("Log in", to: "/login", class: "underline text-gray-800 dark:text-gray-50") %> instead. diff --git a/lib/plausible_web/templates/layout/_header.html.eex b/lib/plausible_web/templates/layout/_header.html.eex index f6adc3bb2..5d56d9925 100644 --- a/lib/plausible_web/templates/layout/_header.html.eex +++ b/lib/plausible_web/templates/layout/_header.html.eex @@ -46,14 +46,6 @@
<% end %> - <% Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_authentication) -> %> -