Merge branch 'develop' into frontend-switcher-9000
authorFloatingGhost <hannah@coffee-and-dreams.uk>
Fri, 14 Apr 2023 15:56:10 +0000 (16:56 +0100)
committerFloatingGhost <hannah@coffee-and-dreams.uk>
Fri, 14 Apr 2023 15:56:10 +0000 (16:56 +0100)
13 files changed:
config/config.exs
config/description.exs
lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex
lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex [new file with mode: 0644]
lib/pleroma/web/akkoma_api/views/frontend_switcher.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex
lib/pleroma/web/fallback/redirect_controller.ex
lib/pleroma/web/plugs/frontend_static.ex
lib/pleroma/web/plugs/http_security_plug.ex
lib/pleroma/web/plugs/instance_static.ex
lib/pleroma/web/router.ex
lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex [new file with mode: 0644]
test/pleroma/web/plugs/frontend_static_plug_test.exs

index e216caf9d2c9913aa2c20be6785d594117971c01..c1e05114888588e94fa38d9cdea55f95f7066704 100644 (file)
@@ -746,6 +746,9 @@ config :pleroma, :frontends,
   primary: %{"name" => "pleroma-fe", "ref" => "stable"},
   admin: %{"name" => "admin-fe", "ref" => "stable"},
   mastodon: %{"name" => "mastodon-fe", "ref" => "akkoma"},
+  pickable: [
+    "pleroma-fe/stable"
+  ],
   swagger: %{
     "name" => "swagger-ui",
     "ref" => "stable",
index 6520bc29aa7678656a0cbcf0a93a3033ad2121e0..d329f8afa156e3548a34c85b2e205ac58f215603 100644 (file)
@@ -3159,6 +3159,12 @@ config :pleroma, :config_description, [
         description:
           "A map containing available frontends and parameters for their installation.",
         children: frontend_options
+      },
+      %{
+          key: :pickable,
+          type: {:list, :string},
+          description:
+            "A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable."
       }
     ]
   },
index c13ff90968f6da314f3014fbb800725601c30412..307d3564341c129c26a6310fbb4260ad6fe9fbd6 100644 (file)
@@ -5,6 +5,16 @@ defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do
   alias Pleroma.Akkoma.FrontendSettingsProfile
 
   @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
+
+  plug(
+    OAuthScopesPlug,
+    @unauthenticated_access
+    when action in [
+           :available_frontends,
+           :update_preferred_frontend
+         ]
+  )
+
   plug(
     OAuthScopesPlug,
     %{@unauthenticated_access | scopes: ["read:accounts"]}
@@ -93,4 +103,22 @@ defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do
       |> json(profile.settings)
     end
   end
+
+  @doc "GET /api/v1/akkoma/preferred_frontend/available"
+  def available_frontends(conn, _params) do
+    available = Pleroma.Config.get([:frontends, :pickable])
+
+    conn
+    |> json(available)
+  end
+
+  @doc "PUT /api/v1/akkoma/preferred_frontend"
+  def update_preferred_frontend(
+        %{body_params: %{frontend_name: preferred_frontend}} = conn,
+        _params
+      ) do
+    conn
+    |> put_resp_cookie("preferred_frontend", preferred_frontend)
+    |> json(%{frontend_name: preferred_frontend})
+  end
 end
diff --git a/lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex b/lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex
new file mode 100644 (file)
index 0000000..2095db4
--- /dev/null
@@ -0,0 +1,20 @@
+defmodule Pleroma.Web.AkkomaAPI.FrontendSwitcherController do
+  use Pleroma.Web, :controller
+  alias Pleroma.Config
+
+  @doc "GET /akkoma/frontend"
+  def switch(conn, _params) do
+    pickable = Config.get([:frontends, :pickable], [])
+
+    conn
+    |> put_view(Pleroma.Web.AkkomaAPI.FrontendSwitcherView)
+    |> render("switch.html", choices: pickable)
+  end
+
+  @doc "POST /akkoma/frontend"
+  def do_switch(conn, params) do
+    conn
+    |> put_resp_cookie("preferred_frontend", params["frontend"])
+    |> html("<meta http-equiv=\"refresh\" content=\"0; url=/\">")
+  end
+end
diff --git a/lib/pleroma/web/akkoma_api/views/frontend_switcher.ex b/lib/pleroma/web/akkoma_api/views/frontend_switcher.ex
new file mode 100644 (file)
index 0000000..1564c9e
--- /dev/null
@@ -0,0 +1,3 @@
+defmodule Pleroma.Web.AkkomaAPI.FrontendSwitcherView do
+  use Pleroma.Web, :view
+end
index 40e81ad55b4568db9db08fe54854275c266dfb21..867a751b3362dc247b7d21ac94444d10d1c9a7de 100644 (file)
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
   @spec list_profiles_operation() :: Operation.t()
   def list_profiles_operation() do
     %Operation{
-      tags: ["Retrieve frontend setting profiles"],
+      tags: ["Frontends"],
       summary: "Frontend Settings Profiles",
       description: "List frontend setting profiles",
       operationId: "AkkomaAPI.FrontendSettingsController.list_profiles",
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
   @spec get_profile_operation() :: Operation.t()
   def get_profile_operation() do
     %Operation{
-      tags: ["Retrieve frontend setting profile"],
+      tags: ["Frontends"],
       summary: "Frontend Settings Profile",
       description: "Get frontend setting profile",
       operationId: "AkkomaAPI.FrontendSettingsController.get_profile",
@@ -60,7 +60,7 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
   @spec delete_profile_operation() :: Operation.t()
   def delete_profile_operation() do
     %Operation{
-      tags: ["Delete frontend setting profile"],
+      tags: ["Frontends"],
       summary: "Delete frontend Settings Profile",
       description: "Delete  frontend setting profile",
       operationId: "AkkomaAPI.FrontendSettingsController.delete_profile",
@@ -76,7 +76,7 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
   @spec update_profile_operation() :: Operation.t()
   def update_profile_operation() do
     %Operation{
-      tags: ["Update frontend setting profile"],
+      tags: ["Frontends"],
       summary: "Frontend Settings Profile",
       description: "Update frontend setting profile",
       operationId: "AkkomaAPI.FrontendSettingsController.update_profile_operation",
@@ -90,6 +90,57 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
     }
   end
 
+  def available_frontends_operation() do
+    %Operation{
+      tags: ["Frontends"],
+      summary: "Frontend Settings Profiles",
+      description: "List frontend setting profiles",
+      operationId: "AkkomaAPI.FrontendSettingsController.available_frontends",
+      responses: %{
+        200 =>
+          Operation.response("Frontends", "application/json", %Schema{
+            type: :array,
+            items: %Schema{
+              type: :string
+            }
+          })
+      }
+    }
+  end
+
+  def update_preferred_frontend_operation() do
+    %Operation{
+      tags: ["Frontends"],
+      summary: "Frontend Settings Profiles",
+      description: "List frontend setting profiles",
+      operationId: "AkkomaAPI.FrontendSettingsController.available_frontends",
+      requestBody:
+        request_body(
+          "Frontend",
+          %Schema{
+            type: :object,
+            required: [:frontend_name],
+            properties: %{
+              frontend_name: %Schema{
+                type: :string,
+                description: "Frontend name"
+              }
+            }
+          },
+          required: true
+        ),
+      responses: %{
+        200 =>
+          Operation.response("Frontends", "application/json", %Schema{
+            type: :array,
+            items: %Schema{
+              type: :string
+            }
+          })
+      }
+    }
+  end
+
   def frontend_name_param do
     Operation.parameter(:frontend_name, :path, :string, "Frontend name",
       example: "pleroma-fe",
index 49f659cf0138035d1b8da31d9296ff8889c3cecc..2e57fa42652874cb3de5c1bf20c5959acda59816 100644 (file)
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
   def redirector(conn, _params, code \\ 200) do
     conn
     |> put_resp_content_type("text/html")
-    |> send_file(code, index_file_path())
+    |> send_file(code, index_file_path(conn))
   end
 
   def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
   end
 
   def redirector_with_meta(conn, params) do
-    {:ok, index_content} = File.read(index_file_path())
+    {:ok, index_content} = File.read(index_file_path(conn))
 
     tags = build_tags(conn, params)
     preloads = preload_data(conn, params)
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
   end
 
   def redirector_with_preload(conn, params) do
-    {:ok, index_content} = File.read(index_file_path())
+    {:ok, index_content} = File.read(index_file_path(conn))
     preloads = preload_data(conn, params)
     tags = Metadata.build_static_tags(params)
     title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
@@ -77,8 +77,9 @@ defmodule Pleroma.Web.Fallback.RedirectController do
     |> text("")
   end
 
-  defp index_file_path do
-    Pleroma.Web.Plugs.InstanceStatic.file_path("index.html")
+  defp index_file_path(conn) do
+    frontend_type = Pleroma.Web.Plugs.FrontendStatic.preferred_or_fallback(conn, :primary)
+    Pleroma.Web.Plugs.InstanceStatic.file_path("index.html", frontend_type)
   end
 
   defp build_tags(conn, params) do
index 40f51e149a0da9d798ae81a1a84d977c47e5d4ab..1d68614568fb967b479ee4d41de3c0c5b6c1dd06 100644 (file)
@@ -5,17 +5,23 @@
 defmodule Pleroma.Web.Plugs.FrontendStatic do
   require Pleroma.Constants
 
+  @frontend_cookie_name "preferred_frontend"
+
   @moduledoc """
   This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
   """
   @behaviour Plug
 
-  def file_path(path, frontend_type \\ :primary) do
-    if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
-      instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+  defp instance_static_path do
+    Pleroma.Config.get([:instance, :static_dir], "instance/static")
+  end
+
+  def file_path(path, frontend_type \\ :primary)
 
+  def file_path(path, frontend_type) when is_atom(frontend_type) do
+    if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
       Path.join([
-        instance_static_path,
+        instance_static_path(),
         "frontends",
         configuration["name"],
         configuration["ref"],
@@ -26,6 +32,15 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
     end
   end
 
+  def file_path(path, frontend_type) when is_binary(frontend_type) do
+    Path.join([
+      instance_static_path(),
+      "frontends",
+      frontend_type,
+      path
+    ])
+  end
+
   def init(opts) do
     opts
     |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
@@ -38,7 +53,8 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
     with false <- api_route?(conn.path_info),
          false <- invalid_path?(conn.path_info),
          true <- enabled?(opts[:if]),
-         frontend_type <- Map.get(opts, :frontend_type, :primary),
+         fallback_frontend_type <- Map.get(opts, :frontend_type, :primary),
+         frontend_type <- preferred_or_fallback(conn, fallback_frontend_type),
          path when not is_nil(path) <- file_path("", frontend_type) do
       call_static(conn, opts, path)
     else
@@ -47,6 +63,27 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
     end
   end
 
+  def preferred_frontend(conn) do
+    %{req_cookies: cookies} =
+      conn
+      |> Plug.Conn.fetch_cookies()
+
+    Map.get(cookies, @frontend_cookie_name)
+  end
+
+  # Only override primary frontend
+  def preferred_or_fallback(conn, :primary) do
+    case preferred_frontend(conn) do
+      nil ->
+        :primary
+
+      frontend ->
+        frontend
+    end
+  end
+
+  def preferred_or_fallback(_conn, fallback), do: fallback
+
   defp enabled?(if_opt) when is_function(if_opt), do: if_opt.()
   defp enabled?(true), do: true
   defp enabled?(_), do: false
index b1f1ada94c98553a49393bd69e790638d5372c48..570aeefff8d88ce8492dbdb533fd4c84545b1b28 100644 (file)
@@ -8,6 +8,8 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
 
   require Logger
 
+  @mix_env Mix.env()
+
   def init(opts), do: opts
 
   def call(conn, _options) do
@@ -114,7 +116,12 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
     style_src = "style-src 'self' '#{nonce_tag}'"
     font_src = "font-src 'self'"
 
-    script_src = "script-src 'self' '#{nonce_tag}'"
+    script_src = "script-src 'self' '#{nonce_tag}' "
+    script_src = if @mix_env == :dev do
+      "script-src 'self' 'unsafe-eval' 'unsafe-inline'"
+    else
+      script_src
+    end
 
     report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
     insecure = if scheme == "https", do: "upgrade-insecure-requests"
index 723b256793b28485e2f947003ec383c66c92db12..5f9a6ee83e68390a9d6e5ce4f1575a82c859ab76 100644 (file)
@@ -12,11 +12,11 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do
   """
   @behaviour Plug
 
-  def file_path(path) do
+  def file_path(path, frontend_type \\ :primary) do
     instance_path =
       Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
 
-    frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary)
+    frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, frontend_type)
 
     (File.exists?(instance_path) && instance_path) ||
       (frontend_path && File.exists?(frontend_path) && frontend_path) ||
index 24ca5c37bffde89f9f81f9fd893ebb12ceb5fae1..7550eefdf5663db00af287db1767312b0b4c6fec 100644 (file)
@@ -466,6 +466,29 @@ defmodule Pleroma.Web.Router do
     put("/statuses/:id/emoji_reactions/:emoji", EmojiReactionController, :create)
   end
 
+  scope "/akkoma/", Pleroma.Web.AkkomaAPI do
+    pipe_through(:browser)
+
+    get("/frontend", FrontendSwitcherController, :switch)
+    post("/frontend", FrontendSwitcherController, :do_switch)
+  end
+
+  scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do
+    pipe_through(:api)
+
+    get(
+      "/api/v1/akkoma/preferred_frontend/available",
+      FrontendSettingsController,
+      :available_frontends
+    )
+
+    put(
+      "/api/v1/akkoma/preferred_frontend",
+      FrontendSettingsController,
+      :update_preferred_frontend
+    )
+  end
+
   scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do
     pipe_through(:authenticated_api)
     get("/metrics", MetricsController, :show)
diff --git a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
new file mode 100644 (file)
index 0000000..a0b0a23
--- /dev/null
@@ -0,0 +1,10 @@
+<h2>Switch Frontend</h2>
+
+<h3>After you submit, you will need to refresh manually to get your new frontend!</h3>
+
+<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %>
+  <%= select(f, :frontend, @choices) %>
+
+  <%= submit do: "submit" %>
+<% end %>
+
index 66e6ba4ca68203c19355d70f3314b11c69ad1343..815e888ee995445b4a1097817d65bfd7cb1dd390 100644 (file)
@@ -83,6 +83,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
       "main",
       "ostatus_subscribe",
       "oauth",
+      "akkoma",
       "objects",
       "activities",
       "notice",