diff --git a/CHANGELOG.md b/CHANGELOG.md
index c12deb72b..07a4496b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## 2023.04
+
+## Added
+- Nodeinfo keys for unauthenticated timeline visibility
+- Option to disable federated timeline
+- Option to make the bubble timeline publicly accessible
+- Ability to swap between installed standard frontends
+ - *mastodon frontends are still not counted as standard frontends due to the complexity in serving them correctly*.
+
+### Upgrade Notes
+- Elixir 1.14 is now required. If your distribution does not package this, you can
+ use [asdf](https://asdf-vm.com/). At time of writing, elixir 1.14.3 / erlang 25.3
+ is confirmed to work.
+
## 2023.03
## Fixed
@@ -19,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Removed
- Possibility of using the `style` parameter on `span` elements. This will break certain MFM parameters.
+- Option for "default" image description.
## 2023.02
diff --git a/Dockerfile b/Dockerfile
index 0551a4c9e..c6506c48c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6
+FROM hexpm/elixir:1.14.3-erlang-25.3-alpine-3.17.2
ENV MIX_ENV=prod
ENV ERL_EPMD_ADDRESS=127.0.0.1
diff --git a/README.md b/README.md
index 8d35212aa..e4aa25715 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,9 @@ If your platform is not supported, or you just want to be able to edit the sourc
### Docker
Docker installation is supported via [this setup](https://docs.akkoma.dev/stable/installation/docker_en/)
+### Packages
+Akkoma is packaged for [YunoHost](https://yunohost.org) and can be found and installed from the [YunoHost app catalogue](https://yunohost.org/#/apps).
+
### Compilation Troubleshooting
If you ever encounter compilation issues during the updating of Akkoma, you can try these commands and see if they fix things:
diff --git a/config/config.exs b/config/config.exs
index 5eaa8ce76..95c576385 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -65,7 +65,6 @@
link_name: false,
proxy_remote: false,
filename_display_max_length: 30,
- default_description: nil,
base_url: nil
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
@@ -261,7 +260,8 @@
privileged_staff: false,
local_bubble: [],
max_frontend_settings_json_chars: 100_000,
- export_prometheus_metrics: true
+ export_prometheus_metrics: true,
+ federated_timeline_available: true
config :pleroma, :welcome,
direct_message: [
@@ -745,6 +745,9 @@
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",
@@ -810,7 +813,7 @@
private_instance? = :if_instance_is_private
config :pleroma, :restrict_unauthenticated,
- timelines: %{local: private_instance?, federated: private_instance?},
+ timelines: %{local: private_instance?, federated: private_instance?, bubble: true},
profiles: %{local: private_instance?, remote: private_instance?},
activities: %{local: private_instance?, remote: private_instance?}
diff --git a/config/custom_emoji.txt b/config/custom_emoji.txt
index e69de29bb..7b2e51265 100644
--- a/config/custom_emoji.txt
+++ b/config/custom_emoji.txt
@@ -0,0 +1,2 @@
+hehe, /emoji/hehe.png, Akkoma
+nothehe, /emoji/nothehe.png, Akkoma
diff --git a/config/description.exs b/config/description.exs
index 2a2d70a7b..bd20cb239 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -969,6 +969,12 @@
key: :export_prometheus_metrics,
type: :boolean,
description: "Enable prometheus metrics (at /api/v1/akkoma/metrics)"
+ },
+ %{
+ key: :federated_timeline_available,
+ type: :boolean,
+ description:
+ "Let people view the 'firehose' feed of all public statuses from all instances."
}
]
},
@@ -2993,6 +2999,11 @@
key: :federated,
type: :boolean,
description: "Disallow viewing the whole known network timeline."
+ },
+ %{
+ key: :bubble,
+ type: :boolean,
+ description: "Disallow viewing the bubble timeline."
}
]
},
@@ -3148,6 +3159,12 @@
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."
}
]
},
diff --git a/config/test.exs b/config/test.exs
index 3056dbd03..4448eeb73 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -23,8 +23,7 @@
config :pleroma, Pleroma.Upload,
filters: [],
- link_name: false,
- default_description: :filename
+ link_name: false
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md
index 4e84b9a44..1c4d9ec5d 100644
--- a/docs/docs/configuration/cheatsheet.md
+++ b/docs/docs/configuration/cheatsheet.md
@@ -562,7 +562,6 @@ the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). Th
* `proxy_remote`: If you're using a remote uploader, Akkoma will proxy media requests instead of redirecting to it.
* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.
* `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30.
-* `default_description`: Sets which default description an image has if none is set explicitly. Options: nil (default) - Don't set a default, :filename - use the filename of the file, a string (e.g. "attachment") - Use this string
!!! warning
`strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include
index 68c61129a..d8cf9f9da 100644
--- a/docs/docs/installation/generic_dependencies.include
+++ b/docs/docs/installation/generic_dependencies.include
@@ -1,8 +1,8 @@
## Required dependencies
* PostgreSQL 9.6+
-* Elixir 1.12+ (1.13+ recommended)
-* Erlang OTP 22.2+
+* Elixir 1.14+
+* Erlang OTP 24+
* git
* file / libmagic
* gcc (clang might also work)
diff --git a/docs/docs/installation/yunohost_en.md b/docs/docs/installation/yunohost_en.md
new file mode 100644
index 000000000..0d3adb4fe
--- /dev/null
+++ b/docs/docs/installation/yunohost_en.md
@@ -0,0 +1,9 @@
+# Installing on Yunohost
+
+[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Akkoma which allows you to install Akkoma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/akkoma_ynh).
+
+## Questions
+
+Questions and problems related to the YunoHost parts can be done through the [YunoHost channels](https://yunohost.org/en/help).
+
+For questions about Akkoma, check out the [Akkoma community channels](../../#community-channels).
diff --git a/elixir_buildpack.config b/elixir_buildpack.config
index 946408c12..ee9e051a6 100644
--- a/elixir_buildpack.config
+++ b/elixir_buildpack.config
@@ -1,2 +1,2 @@
-elixir_version=1.9.4
-erlang_version=22.3.4.1
+elixir_version=1.14.3
+erlang_version=25.3
diff --git a/lib/mix/tasks/pleroma/diagnostics.ex b/lib/mix/tasks/pleroma/diagnostics.ex
index 3914540ca..87be38b78 100644
--- a/lib/mix/tasks/pleroma/diagnostics.ex
+++ b/lib/mix/tasks/pleroma/diagnostics.ex
@@ -82,4 +82,46 @@ def run(["user_timeline", nickname, reading_nickname]) do
Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity)
|> IO.puts()
end
+
+ def run(["notifications", nickname]) do
+ start_pleroma()
+ user = Repo.get_by!(User, nickname: nickname)
+ account_ap_id = user.ap_id
+ options = %{account_ap_id: user.ap_id}
+
+ query =
+ user
+ |> Pleroma.Notification.for_user_query(options)
+ |> where([n, a], a.actor == ^account_ap_id)
+ |> limit(20)
+
+ Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity)
+ |> IO.puts()
+ end
+
+ def run(["known_network", nickname]) do
+ start_pleroma()
+ user = Repo.get_by!(User, nickname: nickname)
+
+ params =
+ %{}
+ |> Map.put(:type, ["Create"])
+ |> Map.put(:local_only, false)
+ |> Map.put(:blocking_user, user)
+ |> Map.put(:muting_user, user)
+ |> Map.put(:reply_filtering_user, user)
+ # Restricts unfederated content to authenticated users
+ |> Map.put(:includes_local_public, not is_nil(user))
+ |> Map.put(:restrict_unlisted, true)
+
+ query =
+ Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query(
+ [Pleroma.Constants.as_public()],
+ params
+ )
+ |> limit(20)
+
+ Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity)
+ |> IO.puts()
+ end
end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index c5b514742..c94667fb9 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -277,6 +277,13 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
def get_create_by_object_ap_id_with_object(_), do: nil
+ def get_local_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
+ ap_id
+ |> create_by_object_ap_id()
+ |> where(local: true)
+ |> Repo.one()
+ end
+
@spec create_by_id_with_object(String.t()) :: t() | nil
def create_by_id_with_object(id) do
get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"])
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 6ddfa5042..5c70748b6 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -162,7 +162,7 @@ def local do
%Instance{
host: Pleroma.Web.Endpoint.host(),
favicon: Pleroma.Web.Endpoint.url() <> "/favicon.png",
- nodeinfo: Pleroma.Web.Nodeinfo.NodeinfoController.raw_nodeinfo()
+ nodeinfo: Pleroma.Web.Nodeinfo.Nodeinfo.get_nodeinfo("2.1")
}
end
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 3b5419db7..2f65540be 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -65,15 +65,6 @@ defmodule Pleroma.Upload do
}
defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
- defp get_description(opts, upload) do
- case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
- {description, _} when is_binary(description) -> description
- {_, :filename} -> upload.name
- {_, str} when is_binary(str) -> str
- _ -> ""
- end
- end
-
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
@doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
def store(upload, opts \\ []) do
@@ -82,7 +73,7 @@ def store(upload, opts \\ []) do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
- description = get_description(opts, upload),
+ description = Map.get(opts, :description) || "",
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index f94202af5..ead37ccca 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -366,21 +366,21 @@ def invisible?(%User{invisible: true}), do: true
def invisible?(_), do: false
def avatar_url(user, options \\ []) do
- case user.avatar do
- %{"url" => [%{"href" => href} | _]} ->
- href
-
- _ ->
- unless options[:no_default] do
- Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
- end
- end
+ default = Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
+ do_optional_url(user.avatar, default, options)
end
def banner_url(user, options \\ []) do
- case user.banner do
- %{"url" => [%{"href" => href} | _]} -> href
- _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
+ do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options)
+ end
+
+ defp do_optional_url(field, default, options) do
+ case field do
+ %{"url" => [%{"href" => href} | _]} when is_binary(href) ->
+ href
+
+ _ ->
+ unless options[:no_default], do: default
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8e55df0d8..649bf9095 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1502,13 +1502,22 @@ def fetch_activities_bounded(
@spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()}
def upload(file, opts \\ []) do
- with {:ok, data} <- Upload.store(file, opts) do
+ with {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do
obj_data = Maps.put_if_present(data, "actor", opts[:actor])
Repo.insert(%Object{data: obj_data})
end
end
+ defp sanitize_upload_file(%Plug.Upload{filename: filename} = upload) when is_binary(filename) do
+ %Plug.Upload{
+ upload
+ | filename: Path.basename(filename)
+ }
+ end
+
+ defp sanitize_upload_file(upload), do: upload
+
@spec get_actor_url(any()) :: binary() | nil
defp get_actor_url(url) when is_binary(url), do: url
defp get_actor_url(%{"href" => href}) when is_binary(href), do: href
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index c07f91b2e..4e6842d85 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Activity
alias Pleroma.Delivery
alias Pleroma.Object
- alias Pleroma.Object.Fetcher
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
@@ -293,33 +292,12 @@ def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
|> json("Invalid HTTP Signature")
end
- # POST /relay/inbox -or- POST /internal/fetch/inbox
- def inbox(conn, %{"type" => "Create"} = params) do
- if FederatingPlug.federating?() do
- post_inbox_relayed_create(conn, params)
- else
- conn
- |> put_status(:bad_request)
- |> json("Not federating")
- end
- end
-
def inbox(conn, _params) do
conn
|> put_status(:bad_request)
|> json("error, missing HTTP Signature")
end
- defp post_inbox_relayed_create(conn, params) do
- Logger.debug(
- "Signature missing or not from author, relayed Create message, fetching object from source"
- )
-
- Fetcher.fetch_object_from_id(params["object"]["id"])
-
- json(conn, "ok")
- end
-
defp represent_service_actor(%User{} = user, conn) do
conn
|> put_resp_content_type("application/activity+json")
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index b187d3a48..3071c1b77 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -108,15 +108,28 @@ defp blocked_instances do
Config.get([:mrf_simple, :reject], [])
end
+ defp allowed_instances do
+ Config.get([:mrf_simple, :accept])
+ end
+
def should_federate?(url) do
%{host: host} = URI.parse(url)
- quarantined_instances =
- blocked_instances()
+ with allowed <- allowed_instances(),
+ false <- Enum.empty?(allowed) do
+ allowed
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
+ |> Pleroma.Web.ActivityPub.MRF.subdomain_match?(host)
+ else
+ _ ->
+ quarantined_instances =
+ blocked_instances()
+ |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
+ |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
- !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
+ not Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
+ end
end
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
diff --git a/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex
index c13ff9096..307d35643 100644
--- a/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex
+++ b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex
@@ -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 @@ def update_profile(%{body_params: %{settings: settings, version: version}} = con
|> 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
index 000000000..2095db4b5
--- /dev/null
+++ b/lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex
@@ -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("")
+ 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
index 000000000..1564c9e44
--- /dev/null
+++ b/lib/pleroma/web/akkoma_api/views/frontend_switcher.ex
@@ -0,0 +1,3 @@
+defmodule Pleroma.Web.AkkomaAPI.FrontendSwitcherView do
+ use Pleroma.Web, :view
+end
diff --git a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex
index 40e81ad55..867a751b3 100644
--- a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex
@@ -12,7 +12,7 @@ def open_api_operation(action) 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 @@ def list_profiles_operation() 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 @@ def get_profile_operation() 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 @@ def delete_profile_operation() 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 @@ def update_profile_operation() 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",
diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
index 3eb6f700b..45c97cab6 100644
--- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
@@ -70,7 +70,8 @@ def public_operation do
operationId: "TimelineController.public",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
- 401 => Operation.response("Error", "application/json", ApiError)
+ 401 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
}
}
end
diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex
index 49f659cf0..2e57fa426 100644
--- a/lib/pleroma/web/fallback/redirect_controller.ex
+++ b/lib/pleroma/web/fallback/redirect_controller.ex
@@ -20,7 +20,7 @@ def api_not_implemented(conn, _params) 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 @@ def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id}
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 @@ def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) 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 = "
#{Pleroma.Config.get([:instance, :name])}"
@@ -77,8 +77,9 @@ def empty(conn, _params) 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
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 2d0e36420..1d4e734a4 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:skip_public_check when action in [:public, :hashtag])
+ plug(:skip_public_check when action in [:public, :hashtag, :bubble])
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
@@ -28,19 +28,25 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list)
plug(RateLimiter, [name: :timeline, bucket_name: :bubble_timeline] when action == :bubble)
- plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct, :bubble])
+ plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
- when action in [:public, :hashtag]
+ when action in [:public, :hashtag, :bubble]
)
+ require Logger
+
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation
# GET /api/v1/timelines/home
def home(%{assigns: %{user: user}} = conn, params) do
+ %{nickname: nickname} = user
+
+ Logger.debug("TimelineController.home: #{nickname}")
+
followed_hashtags =
user
|> User.followed_hashtags()
@@ -58,11 +64,15 @@ def home(%{assigns: %{user: user}} = conn, params) do
|> Map.put(:followed_hashtags, followed_hashtags)
|> Map.delete(:local)
+ Logger.debug("TimelineController.home: #{nickname} - fetching activities")
+
activities =
[user.ap_id | User.following(user)]
|> ActivityPub.fetch_activities(params)
|> Enum.reverse()
+ Logger.debug("TimelineController.home: #{nickname} - rendering")
+
conn
|> add_link_headers(activities)
|> render("index.json",
@@ -75,6 +85,8 @@ def home(%{assigns: %{user: user}} = conn, params) do
# GET /api/v1/timelines/direct
def direct(%{assigns: %{user: user}} = conn, params) do
+ Logger.debug("TimelineController.direct: #{user.nickname}")
+
params =
params
|> Map.put(:type, "Create")
@@ -82,11 +94,15 @@ def direct(%{assigns: %{user: user}} = conn, params) do
|> Map.put(:user, user)
|> Map.put(:visibility, "direct")
+ Logger.debug("TimelineController.direct: #{user.nickname} - fetching activities")
+
activities =
[user.ap_id]
|> ActivityPub.fetch_activities_query(params)
|> Pagination.fetch_paginated(params)
+ Logger.debug("TimelineController.direct: #{user.nickname} - rendering")
+
conn
|> add_link_headers(activities)
|> render("index.json",
@@ -96,21 +112,22 @@ def direct(%{assigns: %{user: user}} = conn, params) do
)
end
- defp restrict_unauthenticated?(true = _local_only) do
- Config.restrict_unauthenticated_access?(:timelines, :local)
- end
-
- defp restrict_unauthenticated?(_) do
- Config.restrict_unauthenticated_access?(:timelines, :federated)
+ defp restrict_unauthenticated?(type) do
+ Config.restrict_unauthenticated_access?(:timelines, type)
end
# GET /api/v1/timelines/public
def public(%{assigns: %{user: user}} = conn, params) do
+ Logger.debug("TimelineController.public")
local_only = params[:local]
+ timeline_type = if local_only, do: :local, else: :federated
+
+ with {:enabled, true} <-
+ {:enabled, local_only || Config.get([:instance, :federated_timeline_available], true)},
+ {:authenticated, true} <-
+ {:authenticated, !(is_nil(user) and restrict_unauthenticated?(timeline_type))} do
+ Logger.debug("TimelineController.public: fetching activities")
- if is_nil(user) and restrict_unauthenticated?(local_only) do
- fail_on_bad_auth(conn)
- else
activities =
params
|> Map.put(:type, ["Create"])
@@ -123,6 +140,8 @@ def public(%{assigns: %{user: user}} = conn, params) do
|> Map.put(:includes_local_public, not is_nil(user))
|> ActivityPub.fetch_public_activities()
+ Logger.debug("TimelineController.public: rendering")
+
conn
|> add_link_headers(activities, %{"local" => local_only})
|> render("index.json",
@@ -131,20 +150,32 @@ def public(%{assigns: %{user: user}} = conn, params) do
as: :activity,
with_muted: Map.get(params, :with_muted, false)
)
+ else
+ {:enabled, false} ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Federated timeline is disabled"})
+
+ {:authenticated, false} ->
+ fail_on_bad_auth(conn)
end
end
# GET /api/v1/timelines/bubble
def bubble(%{assigns: %{user: user}} = conn, params) do
- bubble_instances =
- Enum.uniq(
- Config.get([:instance, :local_bubble], []) ++
- [Pleroma.Web.Endpoint.host()]
- )
+ Logger.debug("TimelineController.bubble")
- if is_nil(user) do
+ if is_nil(user) and restrict_unauthenticated?(:bubble) do
fail_on_bad_auth(conn)
else
+ bubble_instances =
+ Enum.uniq(
+ Config.get([:instance, :local_bubble], []) ++
+ [Pleroma.Web.Endpoint.host()]
+ )
+
+ Logger.debug("TimelineController.bubble: fetching activities")
+
activities =
params
|> Map.put(:type, ["Create"])
@@ -154,6 +185,8 @@ def bubble(%{assigns: %{user: user}} = conn, params) do
|> Map.put(:instance, bubble_instances)
|> ActivityPub.fetch_public_activities()
+ Logger.debug("TimelineController.bubble: rendering")
+
conn
|> add_link_headers(activities)
|> render("index.json",
@@ -195,7 +228,7 @@ defp hashtag_fetching(params, user, local_only) do
def hashtag(%{assigns: %{user: user}} = conn, params) do
local_only = params[:local]
- if is_nil(user) and restrict_unauthenticated?(local_only) do
+ if is_nil(user) and restrict_unauthenticated?(if local_only, do: :local, else: :federated) do
fail_on_bad_auth(conn)
else
activities = hashtag_fetching(params, user, local_only)
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 2717da99d..2b5354873 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -67,6 +67,9 @@ def features do
"pleroma:api/v1/notifications:include_types_filter",
"quote_posting",
"editing",
+ if !Enum.empty?(Config.get([:instance, :local_bubble], [])) do
+ "bubble_timeline"
+ end,
if Config.get([:media_proxy, :enabled]) do
"media_proxy"
end,
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 3868da8d9..47d1616c4 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -21,6 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.PleromaAPI.EmojiReactionController
+ require Logger
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
@@ -87,6 +88,7 @@ defp reblogged?(activity, %User{ap_id: ap_id}) do
defp reblogged?(_activity, _user), do: false
def render("index.json", opts) do
+ Logger.debug("Rendering index")
reading_user = opts[:for]
# To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
activities = Enum.filter(opts.activities, & &1)
@@ -136,8 +138,10 @@ def render("index.json", opts) do
def render(
"show.json",
- %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
+ %{activity: %{id: id, data: %{"type" => "Announce", "object" => _object}} = activity} =
+ opts
) do
+ Logger.debug("Rendering reblog #{id}")
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
object = Object.normalize(activity, fetch: false)
@@ -209,7 +213,9 @@ def render(
}
end
- def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
+ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = activity} = opts) do
+ Logger.debug("Rendering status #{id}")
+
with %Object{} = object <- Object.normalize(activity, fetch: false) do
user = CommonAPI.get_user(activity.data["actor"])
user_follower_address = user.follower_address
@@ -428,6 +434,7 @@ def render("show.json", _) do
end
def render("history.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
+ Logger.debug("Rendering history for #{activity.id}")
object = Object.normalize(activity, fetch: false)
hashtags = Object.hashtags(object)
@@ -614,6 +621,8 @@ def render("attachment_meta.json", %{
def render("attachment_meta.json", _), do: nil
def render("context.json", %{activity: activity, activities: activities, user: user}) do
+ Logger.debug("Rendering context for #{activity.id}")
+
%{ancestors: ancestors, descendants: descendants} =
activities
|> Enum.reverse()
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex
index bf0d65f45..532ae53a7 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex
@@ -71,7 +71,15 @@ def get_nodeinfo("2.0") do
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
privilegedStaff: Config.get([:instance, :privileged_staff]),
- localBubbleInstances: Config.get([:instance, :local_bubble], [])
+ localBubbleInstances: Config.get([:instance, :local_bubble], []),
+ publicTimelineVisibility: %{
+ federated:
+ !Config.restrict_unauthenticated_access?(:timelines, :federated) &&
+ Config.get([:instance, :federated_timeline_available], true),
+ local: !Config.restrict_unauthenticated_access?(:timelines, :local),
+ bubble: !Config.restrict_unauthenticated_access?(:timelines, :bubble)
+ },
+ federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true)
}
}
end
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index a0dee7c6b..ea2d86f92 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -5,12 +5,8 @@
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
use Pleroma.Web, :controller
- alias Pleroma.Config
- alias Pleroma.Stats
- alias Pleroma.User
- alias Pleroma.Web.Federator.Publisher
- alias Pleroma.Web.MastodonAPI.InstanceView
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Nodeinfo.Nodeinfo
def schemas(conn, _params) do
response = %{
@@ -29,101 +25,15 @@ def schemas(conn, _params) do
json(conn, response)
end
- # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
- # under software.
- def raw_nodeinfo do
- stats = Stats.get_stats()
-
- staff_accounts =
- User.all_superusers()
- |> Enum.map(fn u -> u.ap_id end)
- |> Enum.filter(fn u -> not Enum.member?(Config.get([:instance, :staff_transparency]), u) end)
-
- features = InstanceView.features()
- federation = InstanceView.federation()
-
- %{
- version: "2.0",
- software: %{
- name: Pleroma.Application.name() |> String.downcase(),
- version: Pleroma.Application.version()
- },
- protocols: Publisher.gather_nodeinfo_protocol_names(),
- services: %{
- inbound: [],
- outbound: []
- },
- openRegistrations: Config.get([:instance, :registrations_open]),
- usage: %{
- users: %{
- total: Map.get(stats, :user_count, 0)
- },
- localPosts: Map.get(stats, :status_count, 0)
- },
- metadata: %{
- nodeName: Config.get([:instance, :name]),
- nodeDescription: Config.get([:instance, :description]),
- private: !Config.get([:instance, :public], true),
- suggestions: %{
- enabled: false
- },
- staffAccounts: staff_accounts,
- federation: federation,
- pollLimits: Config.get([:instance, :poll_limits]),
- postFormats: Config.get([:instance, :allowed_post_formats]),
- uploadLimits: %{
- general: Config.get([:instance, :upload_limit]),
- avatar: Config.get([:instance, :avatar_upload_limit]),
- banner: Config.get([:instance, :banner_upload_limit]),
- background: Config.get([:instance, :background_upload_limit])
- },
- fieldsLimits: %{
- maxFields: Config.get([:instance, :max_account_fields]),
- maxRemoteFields: Config.get([:instance, :max_remote_account_fields]),
- nameLength: Config.get([:instance, :account_field_name_length]),
- valueLength: Config.get([:instance, :account_field_value_length])
- },
- accountActivationRequired: Config.get([:instance, :account_activation_required], false),
- invitesEnabled: Config.get([:instance, :invites_enabled], false),
- mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
- features: features,
- restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
- skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
- localBubbleInstances: Config.get([:instance, :local_bubble], [])
- }
- }
- end
-
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
# and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json
- def nodeinfo(conn, %{"version" => "2.0"}) do
+ def nodeinfo(conn, %{"version" => version}) when version in ["2.0", "2.1"] do
conn
|> put_resp_header(
"content-type",
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
)
- |> json(raw_nodeinfo())
- end
-
- def nodeinfo(conn, %{"version" => "2.1"}) do
- raw_response = raw_nodeinfo()
-
- updated_software =
- raw_response
- |> Map.get(:software)
- |> Map.put(:repository, Pleroma.Application.repository())
-
- response =
- raw_response
- |> Map.put(:software, updated_software)
- |> Map.put(:version, "2.1")
-
- conn
- |> put_resp_header(
- "content-type",
- "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#; charset=utf-8"
- )
- |> json(response)
+ |> json(Nodeinfo.get_nodeinfo(version))
end
def nodeinfo(conn, _) do
diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
index 7731d847f..95a22895e 100644
--- a/lib/pleroma/web/o_status/o_status_controller.ex
+++ b/lib/pleroma/web/o_status/o_status_controller.ex
@@ -36,7 +36,7 @@ def object(%{assigns: %{format: format}} = conn, _params)
def object(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <-
- {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
+ {:activity, Activity.get_local_create_by_object_ap_id(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
{_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do
redirect(conn, to: "/notice/#{activity.id}")
diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
index 40f51e149..41b8ba46b 100644
--- a/lib/pleroma/web/plugs/frontend_static.ex
+++ b/lib/pleroma/web/plugs/frontend_static.ex
@@ -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 @@ def file_path(path, frontend_type \\ :primary) 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 @@ def call(conn, opts) 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,31 @@ def call(conn, opts) 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 ->
+ if Enum.member?(Pleroma.Config.get([:frontends, :pickable], []), frontend) do
+ frontend
+ else
+ :primary
+ end
+ 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
diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex
index b1f1ada94..d7cff7343 100644
--- a/lib/pleroma/web/plugs/http_security_plug.ex
+++ b/lib/pleroma/web/plugs/http_security_plug.ex
@@ -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,14 @@ defp csp_string(conn) 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"
diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex
index 723b25679..5f9a6ee83 100644
--- a/lib/pleroma/web/plugs/instance_static.ex
+++ b/lib/pleroma/web/plugs/instance_static.ex
@@ -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) ||
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index faaf3d679..7550eefdf 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -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)
@@ -598,7 +621,6 @@ defmodule Pleroma.Web.Router do
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/list/:list_id", TimelineController, :list)
- get("/timelines/bubble", TimelineController, :bubble)
get("/announcements", AnnouncementController, :index)
post("/announcements/:id/dismiss", AnnouncementController, :mark_read)
@@ -653,6 +675,7 @@ defmodule Pleroma.Web.Router do
get("/timelines/public", TimelineController, :public)
get("/timelines/tag/:tag", TimelineController, :hashtag)
+ get("/timelines/bubble", TimelineController, :bubble)
get("/polls/:id", PollController, :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
index 000000000..a0b0a2361
--- /dev/null
+++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
@@ -0,0 +1,10 @@
+Switch Frontend
+
+After you submit, you will need to refresh manually to get your new frontend!
+
+<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %>
+ <%= select(f, :frontend, @choices) %>
+
+ <%= submit do: "submit" %>
+<% end %>
+
diff --git a/mix.exs b/mix.exs
index 098ea15b0..6544529bd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("3.7.1"),
- elixir: "~> 1.12",
+ version: version("3.7.2"),
+ elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
diff --git a/mix.lock b/mix.lock
index 369d1ed25..bee2c1585 100644
--- a/mix.lock
+++ b/mix.lock
@@ -27,7 +27,7 @@
"earmark": {:hex, :earmark, "1.4.37", "56ce845c543393aa3f9b294c818c3d783452a4a67e4ab18c4303a954a8b59363", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "d86d5e12868db86d5321b00e62a4bbcb4150346e4acc9a90a041fb188a5cb106"},
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
- "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
+ "ecto": {:hex, :ecto, "3.9.5", "9f0aa7ae44a1577b651c98791c6988cd1b69b21bc724e3fd67090b97f7604263", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4f3115d8cbacdc0bfa4b742865459fb1371d0715515842a1fb17fe31920b74c"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
@@ -38,7 +38,7 @@
"ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
- "ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"},
+ "ex_doc": {:hex, :ex_doc, "0.29.3", "f07444bcafb302db86e4f02d8bbcd82f2e881a0dcf4f3e4740e4b8128b9353f7", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3dc6787d7b08801ec3b51e9bd26be5e8826fbf1a17e92d1ebc252e1a1c75bfe1"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
"excoveralls": {:hex, :excoveralls, "0.15.1", "83c8cf7973dd9d1d853dce37a2fb98aaf29b564bf7d01866e409abf59dac2c0e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8416bd90c0082d56a2178cf46c837595a06575f70a5624f164a1ffe37de07e7"},
@@ -86,14 +86,14 @@
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.17", "74938b02f3c531bed3f87fe1ea39af6b5b2d26ab1405e77e76b8ef5df9ffa8a1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4b5710e19a29b8dc93b7af4bab4739c067a3cb759af01ffc3057165453dce38"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.4", "615f8f393135de7e0cbb4bd00ba238b1e0cd324b0d90efbaee613c2f02ca5e5c", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3971221846232021ab5e3c7489fd62ec5bfd6a2e01cae10a317ccf6fb350571c"},
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
- "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
- "plug_crypto": {:hex, :plug_crypto, "1.2.4", "34c380ef387cc7e8d537854ddd4b7096c79a4397d53587cb80419c782b03fdbc", [:mix], [], "hexpm", "4de415f03faec94d9da9be8c12cb51e9c98cbf66d732b6df669d4562d8e91acc"},
+ "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
+ "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
@@ -117,7 +117,7 @@
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
"tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"},
- "timex": {:hex, :timex, "3.7.9", "790cdfc4acfce434e442f98c02ea6d84d0239073bfd668968f82ac63e9a6788d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "64691582e5bb87130f721fc709acfb70f24405833998fabf35be968984860ce1"},
+ "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
diff --git a/priv/static/emoji/hehe.png b/priv/static/emoji/hehe.png
new file mode 100644
index 000000000..c02592d97
Binary files /dev/null and b/priv/static/emoji/hehe.png differ
diff --git a/priv/static/emoji/nothehe.png b/priv/static/emoji/nothehe.png
new file mode 100644
index 000000000..427bb6d61
Binary files /dev/null and b/priv/static/emoji/nothehe.png differ
diff --git a/test/pleroma/upload_test.exs b/test/pleroma/upload_test.exs
index 8f242630f..ad6065b43 100644
--- a/test/pleroma/upload_test.exs
+++ b/test/pleroma/upload_test.exs
@@ -54,7 +54,7 @@ test "it returns file" do
assert result ==
%{
"id" => result["id"],
- "name" => "image.jpg",
+ "name" => "",
"type" => "Document",
"mediaType" => "image/jpeg",
"url" => [
@@ -154,19 +154,6 @@ test "copies the file to the configured folder with deduping" do
"e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781.jpg"
end
- test "copies the file to the configured folder without deduping" do
- File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
-
- file = %Plug.Upload{
- content_type: "image/jpeg",
- path: Path.absname("test/fixtures/image_tmp.jpg"),
- filename: "an [image.jpg"
- }
-
- {:ok, data} = Upload.store(file)
- assert data["name"] == "an [image.jpg"
- end
-
test "fixes incorrect content type when base64 is given" do
params = %{
img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}"
@@ -184,7 +171,7 @@ test "adds extension when base64 is given" do
}
{:ok, data} = Upload.store(params)
- assert String.ends_with?(data["name"], ".jpg")
+ assert String.ends_with?(List.first(data["url"])["href"], ".jpg")
end
test "copies the file to the configured folder with anonymizing filename" do
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index a590946c2..12ccc6bf4 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -2509,6 +2509,16 @@ test "avatar fallback" do
assert User.avatar_url(user, no_default: true) == nil
end
+ test "avatar object with nil in href" do
+ user = insert(:user, avatar: %{"url" => [%{"href" => nil}]})
+ assert User.avatar_url(user) != nil
+ end
+
+ test "banner object with nil in href" do
+ user = insert(:user, banner: %{"url" => [%{"href" => nil}]})
+ assert User.banner_url(user) != nil
+ end
+
test "get_host/1" do
user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain")
assert User.get_host(user) == "lain.com"
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 2008ebf04..0d4a7ec2e 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -662,35 +662,6 @@ test "accept follow activity", %{conn: conn} do
assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
end
- @tag capture_log: true
- test "without valid signature, " <>
- "it only accepts Create activities and requires enabled federation",
- %{conn: conn} do
- data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
- non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
-
- conn = put_req_header(conn, "content-type", "application/activity+json")
-
- clear_config([:instance, :federating], false)
-
- conn
- |> post("/inbox", data)
- |> json_response(403)
-
- conn
- |> post("/inbox", non_create_data)
- |> json_response(403)
-
- clear_config([:instance, :federating], true)
-
- ret_conn = post(conn, "/inbox", data)
- assert "ok" == json_response(ret_conn, 200)
-
- conn
- |> post("/inbox", non_create_data)
- |> json_response(400)
- end
-
test "accepts Add/Remove activities", %{conn: conn} do
object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 17c52fc91..b65575f01 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -1303,33 +1303,19 @@ test "returns reblogs for users for whom reblogs have not been muted" do
%{test_file: test_file}
end
+ test "strips / from filename", %{test_file: file} do
+ file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"}
+ {:ok, %Object{} = object} = ActivityPub.upload(file)
+ [%{"href" => href}] = object.data["url"]
+ assert Regex.match?(~r"/bad.jpg$", href)
+ refute Regex.match?(~r"/nested/", href)
+ end
+
test "sets a description if given", %{test_file: file} do
{:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
assert object.data["name"] == "a cool file"
end
- test "it sets the default description depending on the configuration", %{test_file: file} do
- clear_config([Pleroma.Upload, :default_description])
-
- clear_config([Pleroma.Upload, :default_description], nil)
- {:ok, %Object{} = object} = ActivityPub.upload(file)
- assert object.data["name"] == ""
-
- clear_config([Pleroma.Upload, :default_description], :filename)
- {:ok, %Object{} = object} = ActivityPub.upload(file)
- assert object.data["name"] == "an_image.jpg"
-
- clear_config([Pleroma.Upload, :default_description], "unnamed attachment")
- {:ok, %Object{} = object} = ActivityPub.upload(file)
- assert object.data["name"] == "unnamed attachment"
- end
-
- test "copies the file to the configured folder", %{test_file: file} do
- clear_config([Pleroma.Upload, :default_description], :filename)
- {:ok, %Object{} = object} = ActivityPub.upload(file)
- assert object.data["name"] == "an_image.jpg"
- end
-
test "works with base64 encoded images" do
file = %{
img: data_uri()
diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
index 50b9febea..7ff8cff6b 100644
--- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
@@ -124,6 +124,23 @@ test "/api/v2/media, upload_limit", %{conn: conn, user: user} do
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
+
+ test "Do not allow nested filename", %{conn: conn, image: image} do
+ image = %Plug.Upload{
+ image
+ | filename: "../../../../../nested/file.jpg"
+ }
+
+ desc = "Description of the image"
+
+ media =
+ conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/media", %{"file" => image, "description" => desc})
+ |> json_response_and_validate_schema(:ok)
+
+ refute Regex.match?(~r"/nested/", media["url"])
+ end
end
describe "Update media description" do
diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
index aa9006681..eed12234f 100644
--- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -408,6 +408,25 @@ test "should not return local-only posts for anonymous users" do
assert [] = result
end
+
+ test "should return 404 if disabled" do
+ clear_config([:instance, :federated_timeline_available], false)
+
+ result =
+ build_conn()
+ |> get("/api/v1/timelines/public")
+ |> json_response_and_validate_schema(404)
+
+ assert %{"error" => "Federated timeline is disabled"} = result
+ end
+
+ test "should not return 404 if local is specified" do
+ clear_config([:instance, :federated_timeline_available], false)
+
+ build_conn()
+ |> get("/api/v1/timelines/public?local=true")
+ |> json_response_and_validate_schema(200)
+ end
end
defp local_and_remote_activities do
@@ -1036,9 +1055,8 @@ test "with `%{local: true, federated: false}`, forbids unauthenticated access to
end
describe "bubble" do
- setup do: oauth_access(["read:statuses"])
-
- test "filtering", %{conn: conn, user: user} do
+ test "filtering" do
+ %{conn: conn, user: user} = oauth_access(["read:statuses"])
clear_config([:instance, :local_bubble], [])
# our endpoint host has a port in it so let's set the AP ID
local_user = insert(:user, %{ap_id: "https://localhost/users/user"})
@@ -1060,7 +1078,7 @@ test "filtering", %{conn: conn, user: user} do
assert local_activity.id in one_instance
- # If we have others, also include theirs
+ # If we have others, also include theirs
clear_config([:instance, :local_bubble], ["example.com"])
two_instances =
@@ -1072,6 +1090,20 @@ test "filtering", %{conn: conn, user: user} do
assert local_activity.id in two_instances
assert remote_activity.id in two_instances
end
+
+ test "restrict_unauthenticated with bubble timeline", %{conn: conn} do
+ clear_config([:restrict_unauthenticated, :timelines, :bubble], true)
+
+ conn
+ |> get("/api/v1/timelines/bubble")
+ |> json_response_and_validate_schema(:unauthorized)
+
+ clear_config([:restrict_unauthenticated, :timelines, :bubble], false)
+
+ conn
+ |> get("/api/v1/timelines/bubble")
+ |> json_response_and_validate_schema(200)
+ end
end
defp create_remote_activity(user) do
diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
index e9b8825bf..4aec31eac 100644
--- a/test/pleroma/web/mastodon_api/update_credentials_test.exs
+++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs
@@ -396,6 +396,34 @@ test "updates the user's background, upload_limit, returns a HTTP 413", %{
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
+ test "Strip / from upload files", %{user: user, conn: conn} do
+ new_image = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "../../../../nested/an_image.jpg"
+ }
+
+ assert user.avatar == %{}
+
+ res =
+ patch(conn, "/api/v1/accounts/update_credentials", %{
+ "avatar" => new_image,
+ "header" => new_image,
+ "pleroma_background_image" => new_image
+ })
+
+ assert user_response = json_response_and_validate_schema(res, 200)
+ assert user_response["avatar"]
+ assert user_response["header"]
+ assert user_response["pleroma"]["background_image"]
+ refute Regex.match?(~r"/nested/", user_response["avatar"])
+ refute Regex.match?(~r"/nested/", user_response["header"])
+ refute Regex.match?(~r"/nested/", user_response["pleroma"]["background_image"])
+
+ user = User.get_by_id(user.id)
+ refute user.avatar == %{}
+ end
+
test "requires 'write:accounts' permission" do
token1 = insert(:oauth_token, scopes: ["read"])
token2 = insert(:oauth_token, scopes: ["write", "follow"])
diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
index c9036d67d..6ef89f799 100644
--- a/test/pleroma/web/mastodon_api/views/account_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs
@@ -269,8 +269,8 @@ test "Represent a Service(bot) account" do
}
with_mock(
- Pleroma.Web.Nodeinfo.NodeinfoController,
- raw_nodeinfo: fn -> %{version: "2.0"} end
+ Pleroma.Web.Nodeinfo.Nodeinfo,
+ get_nodeinfo: fn _ -> %{version: "2.0"} end
) do
assert expected ==
AccountView.render("show.json", %{user: user, skip_visibility_check: true})
diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs
index 05a078266..ff14db460 100644
--- a/test/pleroma/web/node_info_test.exs
+++ b/test/pleroma/web/node_info_test.exs
@@ -292,4 +292,38 @@ test "shows extra information in the mrf_simple_info field for relevant entries"
assert response["metadata"]["federation"]["mrf_simple_info"] == expected_config
end
end
+
+ describe "public timeline visibility" do
+ test "shows public timeline visibility", %{conn: conn} do
+ clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: false})
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ assert response["metadata"]["publicTimelineVisibility"]["local"] == true
+ assert response["metadata"]["publicTimelineVisibility"]["federated"] == true
+
+ clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false})
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ assert response["metadata"]["publicTimelineVisibility"]["local"] == false
+ assert response["metadata"]["publicTimelineVisibility"]["federated"] == true
+
+ clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true})
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ assert response["metadata"]["publicTimelineVisibility"]["local"] == true
+ assert response["metadata"]["publicTimelineVisibility"]["federated"] == false
+ end
+ end
end
diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs
index 66e6ba4ca..815e888ee 100644
--- a/test/pleroma/web/plugs/frontend_static_plug_test.exs
+++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs
@@ -83,6 +83,7 @@ test "api routes are detected correctly" do
"main",
"ostatus_subscribe",
"oauth",
+ "akkoma",
"objects",
"activities",
"notice",
diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
index 21c574ba3..c42b82810 100644
--- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
+++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
@@ -69,6 +69,47 @@ test "it considers a mapped identity to be invalid when the associated instance
assert %{valid_signature: false} == conn.assigns
end
+ test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed" do
+ clear_config([:activitypub, :authorized_fetch_mode], true)
+
+ clear_config([:mrf_simple, :accept], [
+ {"mastodon.example.org", "anime is allowed"}
+ ])
+
+ on_exit(fn ->
+ Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false)
+ Pleroma.Config.put([:mrf_simple, :accept], [])
+ end)
+
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
+ |> set_signature("http://mastodon.example.org/users/admin")
+ |> MappedSignatureToIdentityPlug.call(%{})
+
+ assert conn.assigns[:valid_signature]
+ refute is_nil(conn.assigns.user)
+ end
+
+ test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed" do
+ clear_config([:activitypub, :authorized_fetch_mode], true)
+
+ clear_config([:mrf_simple, :accept], [
+ {"misskey.example.org", "anime is allowed"}
+ ])
+
+ on_exit(fn ->
+ Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false)
+ Pleroma.Config.put([:mrf_simple, :accept], [])
+ end)
+
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
+ |> set_signature("http://mastodon.example.org/users/admin")
+ |> MappedSignatureToIdentityPlug.call(%{})
+
+ assert %{valid_signature: false} == conn.assigns
+ end
+
@tag skip: "known breakage; the testsuite presently depends on it"
test "it considers a mapped identity to be invalid when the identity cannot be found" do
conn =