diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5afc57017..8034cc0b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@ 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/).
+## [unreleased]
+### Changed
+- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use ` ` for newlines
+
## [2.0.0] - 2019-03-08
### Security
- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
diff --git a/config/benchmark.exs b/config/benchmark.exs
index 84c6782a2..ff59395cf 100644
--- a/config/benchmark.exs
+++ b/config/benchmark.exs
@@ -61,8 +61,6 @@
config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock
-config :pleroma_job_queue, disabled: true
-
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 2,
total_user_limit: 3,
diff --git a/config/config.exs b/config/config.exs
index f0dab24b5..3ec1868b2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -482,10 +482,6 @@
federator_outgoing: 5
]
-config :pleroma, :fetch_initial_posts,
- enabled: false,
- pages: 5
-
config :auto_linker,
opts: [
extra: true,
diff --git a/config/description.exs b/config/description.exs
index 531d73145..ee10515a7 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1780,25 +1780,6 @@
}
]
},
- %{
- group: :pleroma_job_queue,
- key: :queues,
- type: :group,
- description: "[Deprecated] Replaced with `Oban`/`:queues` (keeping the same format)"
- },
- %{
- group: :pleroma,
- key: Pleroma.Web.Federator.RetryQueue,
- type: :group,
- description: "[Deprecated] See `Oban` and `:workers` sections for configuration notes",
- children: [
- %{
- key: :max_retries,
- type: :integer,
- description: "[Deprecated] Replaced as `Oban`/`:queues`/`:outgoing_federation` value"
- }
- ]
- },
%{
group: :pleroma,
key: Oban,
@@ -2007,25 +1988,6 @@
}
]
},
- %{
- group: :pleroma,
- key: :fetch_initial_posts,
- type: :group,
- description: "Fetching initial posts settings",
- children: [
- %{
- key: :enabled,
- type: :boolean,
- description: "Fetch posts when a new user is federated with"
- },
- %{
- key: :pages,
- type: :integer,
- description: "The amount of pages to fetch",
- suggestions: [5]
- }
- ]
- },
%{
group: :auto_linker,
key: :opts,
@@ -2596,19 +2558,6 @@
}
]
},
- %{
- group: :tesla,
- type: :group,
- description: "Tesla settings",
- children: [
- %{
- key: :adapter,
- type: :module,
- description: "Tesla adapter",
- suggestions: [Tesla.Adapter.Hackney, Tesla.Adapter.Gun]
- }
- ]
- },
%{
group: :pleroma,
key: :chat,
diff --git a/config/test.exs b/config/test.exs
index bce9dd4aa..040e67e4a 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -94,6 +94,8 @@
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
+config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
+
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 06de90f71..476a4a2bf 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -180,7 +180,7 @@ Post here request with grant_type=refresh_token to obtain new access token. Retu
## Account Registration
`POST /api/v1/accounts`
-Has theses additionnal parameters (which are the same as in Pleroma-API):
+Has theses additional parameters (which are the same as in Pleroma-API):
* `fullname`: optional
* `bio`: optional
* `captcha_solution`: optional, contains provider-specific captcha solution,
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index 761d5c69c..12e63ef9f 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -288,10 +288,11 @@ Pleroma Conversations have the same general structure that Mastodon Conversation
2. Pleroma Conversations statuses can be requested by Conversation id.
3. Pleroma Conversations can be replied to.
-Conversations have the additional field "recipients" under the "pleroma" key. This holds a list of all the accounts that will receive a message in this conversation.
+Conversations have the additional field `recipients` under the `pleroma` key. This holds a list of all the accounts that will receive a message in this conversation.
The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation.
+⚠ Conversation IDs can be found in direct messages with the `pleroma.direct_conversation_id` key, do not confuse it with `pleroma.conversation_id`.
## `GET /api/v1/pleroma/conversations/:id/statuses`
### Timeline for a given conversation
diff --git a/docs/clients.md b/docs/clients.md
index 8ac9ad3de..1eae0f0c6 100644
--- a/docs/clients.md
+++ b/docs/clients.md
@@ -1,5 +1,5 @@
# Pleroma Clients
-Note: Additionnal clients may be working but theses are officially supporting Pleroma.
+Note: Additional clients may be working but theses are officially supporting Pleroma.
Feel free to contact us to be added to this list!
## Desktop
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 833d243e8..287ecd049 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -151,14 +151,6 @@ config :pleroma, :mrf_user_allowlist,
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
-### :fetch_initial_posts
-
-!!! warning
- Be careful with this setting, fetching posts may lead to new users being discovered whose posts will then also be fetched. This can lead to serious load on your instance and database.
-
-* `enabled`: If enabled, when a new user is discovered by your instance, fetch some of their latest posts.
-* `pages`: The amount of pages to fetch
-
## Pleroma.ScheduledActivity
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx
index 7f48b614b..688be3e71 100644
--- a/installation/pleroma.nginx
+++ b/installation/pleroma.nginx
@@ -90,8 +90,6 @@ server {
proxy_ignore_client_abort on;
proxy_buffering on;
chunked_transfer_encoding on;
- proxy_ignore_headers Cache-Control;
- proxy_hide_header Cache-Control;
proxy_pass http://127.0.0.1:4000;
}
}
diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex
index 4acc1a3e0..9e65bedad 100644
--- a/lib/pleroma/activity/ir/topics.ex
+++ b/lib/pleroma/activity/ir/topics.ex
@@ -39,7 +39,7 @@ defp visibility_tags(object, activity) do
end
end
- defp item_creation_tags(tags, %{data: %{"type" => "Create"}} = object, activity) do
+ defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
end
diff --git a/lib/pleroma/earmark_renderer.ex b/lib/pleroma/earmark_renderer.ex
new file mode 100644
index 000000000..6211a3b4a
--- /dev/null
+++ b/lib/pleroma/earmark_renderer.ex
@@ -0,0 +1,256 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+#
+# This file is derived from Earmark, under the following copyright:
+# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
+# SPDX-License-Identifier: Apache-2.0
+# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
+defmodule Pleroma.EarmarkRenderer do
+ @moduledoc false
+
+ alias Earmark.Block
+ alias Earmark.Context
+ alias Earmark.HtmlRenderer
+ alias Earmark.Options
+
+ import Earmark.Inline, only: [convert: 3]
+ import Earmark.Helpers.HtmlHelpers
+ import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
+ import Earmark.Context, only: [append: 2, set_value: 2]
+ import Earmark.Options, only: [get_mapper: 1]
+
+ @doc false
+ def render(blocks, %Context{options: %Options{}} = context) do
+ messages = get_messages(context)
+
+ {contexts, html} =
+ get_mapper(context.options).(
+ blocks,
+ &render_block(&1, put_in(context.options.messages, []))
+ )
+ |> Enum.unzip()
+
+ all_messages =
+ contexts
+ |> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
+
+ {put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
+ end
+
+ #############
+ # Paragraph #
+ #############
+ defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
+ lines = convert(lines, lnb, context)
+ add_attrs(lines, "
#{lines.value}
", attrs, [], lnb)
+ end
+
+ ########
+ # Html #
+ ########
+ defp render_block(%Block.Html{html: html}, context) do
+ {context, html}
+ end
+
+ defp render_block(%Block.HtmlComment{lines: lines}, context) do
+ {context, lines}
+ end
+
+ defp render_block(%Block.HtmlOneline{html: html}, context) do
+ {context, html}
+ end
+
+ #########
+ # Ruler #
+ #########
+ defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
+ add_attrs(context, " ", attrs, [], lnb)
+ end
+
+ ###########
+ # Heading #
+ ###########
+ defp render_block(
+ %Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
+ context
+ ) do
+ converted = convert(content, lnb, context)
+ html = "#{converted.value} "
+ add_attrs(converted, html, attrs, [], lnb)
+ end
+
+ ##############
+ # Blockquote #
+ ##############
+
+ defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
+ {context1, body} = render(blocks, context)
+ html = "#{body} "
+ add_attrs(context1, html, attrs, [], lnb)
+ end
+
+ #########
+ # Table #
+ #########
+
+ defp render_block(
+ %Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
+ context
+ ) do
+ {context1, html} = add_attrs(context, "", attrs, [], lnb)
+ context2 = set_value(context1, html)
+
+ context3 =
+ if header do
+ append(add_trs(append(context2, ""), [header], "th", aligns, lnb), " ")
+ else
+ # Maybe an error, needed append(context, html)
+ context2
+ end
+
+ context4 = append(add_trs(append(context3, ""), rows, "td", aligns, lnb), " ")
+
+ {context4, [context4.value, "
"]}
+ end
+
+ ########
+ # Code #
+ ########
+
+ defp render_block(
+ %Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
+ %Context{options: options} = context
+ ) do
+ class =
+ if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
+
+ tag = ~s[]
+ lines = options.render_code.(block)
+ html = ~s[#{tag}#{lines}
]
+ add_attrs(context, html, attrs, [], lnb)
+ end
+
+ #########
+ # Lists #
+ #########
+
+ defp render_block(
+ %Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
+ context
+ ) do
+ {context1, content} = render(items, context)
+ html = "<#{type}#{start}>#{content}#{type}>"
+ add_attrs(context1, html, attrs, [], lnb)
+ end
+
+ # format a single paragraph list item, and remove the para tags
+ defp render_block(
+ %Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
+ context
+ )
+ when length(blocks) == 1 do
+ {context1, content} = render(blocks, context)
+ content = Regex.replace(~r{?p>}, content, "")
+ html = "#{content} "
+ add_attrs(context1, html, attrs, [], lnb)
+ end
+
+ # format a spaced list item
+ defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
+ {context1, content} = render(blocks, context)
+ html = "#{content} "
+ add_attrs(context1, html, attrs, [], lnb)
+ end
+
+ ##################
+ # Footnote Block #
+ ##################
+
+ defp render_block(%Block.FnList{blocks: footnotes}, context) do
+ items =
+ Enum.map(footnotes, fn note ->
+ blocks = append_footnote_link(note)
+ %Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
+ end)
+
+ {context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
+ {context1, Enum.join([~s["])}
+ end
+
+ #######################################
+ # Isolated IALs are rendered as paras #
+ #######################################
+
+ defp render_block(%Block.Ial{verbatim: verbatim}, context) do
+ {context, "{:#{verbatim}}
"}
+ end
+
+ ####################
+ # IDDef is ignored #
+ ####################
+
+ defp render_block(%Block.IdDef{}, context), do: {context, ""}
+
+ #####################################
+ # And here are the inline renderers #
+ #####################################
+
+ defdelegate br, to: HtmlRenderer
+ defdelegate codespan(text), to: HtmlRenderer
+ defdelegate em(text), to: HtmlRenderer
+ defdelegate strong(text), to: HtmlRenderer
+ defdelegate strikethrough(text), to: HtmlRenderer
+
+ defdelegate link(url, text), to: HtmlRenderer
+ defdelegate link(url, text, title), to: HtmlRenderer
+
+ defdelegate image(path, alt, title), to: HtmlRenderer
+
+ defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
+
+ # Table rows
+ defp add_trs(context, rows, tag, aligns, lnb) do
+ numbered_rows =
+ rows
+ |> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
+
+ numbered_rows
+ |> Enum.reduce(context, fn {row, lnb}, ctx ->
+ append(add_tds(append(ctx, ""), row, tag, aligns, lnb), " ")
+ end)
+ end
+
+ defp add_tds(context, row, tag, aligns, lnb) do
+ Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
+ end
+
+ defp add_td_fn(row, tag, aligns, lnb) do
+ fn n, ctx ->
+ style =
+ case Enum.at(aligns, n - 1, :default) do
+ :default -> ""
+ align -> " style=\"text-align: #{align}\""
+ end
+
+ col = Enum.at(row, n - 1)
+ converted = convert(col, lnb, set_messages(ctx, []))
+ append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}#{tag}>")
+ end
+ end
+
+ ###############################
+ # Append Footnote Return Link #
+ ###############################
+
+ defdelegate append_footnote_link(note), to: HtmlRenderer
+ defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
+
+ defdelegate render_code(lines), to: HtmlRenderer
+
+ defp code_classes(language, prefix) do
+ ["" | String.split(prefix || "")]
+ |> Enum.map(fn pfx -> "#{pfx}#{language}" end)
+ |> Enum.join(" ")
+ end
+end
diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex
index 6f9b840a9..054d2297f 100644
--- a/lib/pleroma/plugs/ensure_authenticated_plug.ex
+++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex
@@ -15,9 +15,24 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do
conn
end
- def call(conn, _) do
+ def call(conn, options) do
+ perform =
+ cond do
+ options[:if_func] -> options[:if_func].()
+ options[:unless_func] -> !options[:unless_func].()
+ true -> true
+ end
+
+ if perform do
+ fail(conn)
+ else
+ conn
+ end
+ end
+
+ def fail(conn) do
conn
|> render_error(:forbidden, "Invalid credentials.")
- |> halt
+ |> halt()
end
end
diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex
index d3943586d..7d947339f 100644
--- a/lib/pleroma/plugs/federating_plug.ex
+++ b/lib/pleroma/plugs/federating_plug.ex
@@ -10,14 +10,20 @@ def init(options) do
end
def call(conn, _opts) do
- if Pleroma.Config.get([:instance, :federating]) do
+ if federating?() do
conn
else
- conn
- |> put_status(404)
- |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
- |> Phoenix.Controller.render("404.json")
- |> halt()
+ fail(conn)
end
end
+
+ def federating?, do: Pleroma.Config.get([:instance, :federating])
+
+ defp fail(conn) do
+ conn
+ |> put_status(404)
+ |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
+ |> Phoenix.Controller.render("404.json")
+ |> halt()
+ end
end
diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
index c3f6351c8..1529da717 100644
--- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
+++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
@@ -78,7 +78,7 @@ def init(plug_opts) do
end
def call(conn, plug_opts) do
- if disabled?() do
+ if disabled?(conn) do
handle_disabled(conn)
else
action_settings = action_settings(plug_opts)
@@ -87,9 +87,9 @@ def call(conn, plug_opts) do
end
defp handle_disabled(conn) do
- if Config.get(:env) == :prod do
- Logger.warn("Rate limiter is disabled for localhost/socket")
- end
+ Logger.warn(
+ "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter."
+ )
conn
end
@@ -109,16 +109,21 @@ defp handle(conn, action_settings) do
end
end
- def disabled? do
+ def disabled?(conn) do
localhost_or_socket =
- Config.get([Pleroma.Web.Endpoint, :http, :ip])
- |> Tuple.to_list()
- |> Enum.join(".")
- |> String.match?(~r/^local|^127.0.0.1/)
+ case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do
+ {127, 0, 0, 1} -> true
+ {0, 0, 0, 0, 0, 0, 0, 1} -> true
+ {:local, _} -> true
+ _ -> false
+ end
- remote_ip_disabled = not Config.get([Pleroma.Plugs.RemoteIp, :enabled])
+ remote_ip_not_found =
+ if Map.has_key?(conn.assigns, :remote_ip_found),
+ do: !conn.assigns.remote_ip_found,
+ else: false
- localhost_or_socket and remote_ip_disabled
+ localhost_or_socket and remote_ip_not_found
end
@inspect_bucket_not_found {:error, :not_found}
diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex
index 2eca4f8f6..0ac9050d0 100644
--- a/lib/pleroma/plugs/remote_ip.ex
+++ b/lib/pleroma/plugs/remote_ip.ex
@@ -7,6 +7,8 @@ defmodule Pleroma.Plugs.RemoteIp do
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
"""
+ import Plug.Conn
+
@behaviour Plug
@headers ~w[
@@ -26,11 +28,12 @@ defmodule Pleroma.Plugs.RemoteIp do
def init(_), do: nil
- def call(conn, _) do
+ def call(%{remote_ip: original_remote_ip} = conn, _) do
config = Pleroma.Config.get(__MODULE__, [])
if Keyword.get(config, :enabled, false) do
- RemoteIp.call(conn, remote_ip_opts(config))
+ %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config))
+ assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
else
conn
end
diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/plugs/static_fe_plug.ex
index deebe4879..156e6788e 100644
--- a/lib/pleroma/plugs/static_fe_plug.ex
+++ b/lib/pleroma/plugs/static_fe_plug.ex
@@ -21,6 +21,9 @@ def call(conn, _) do
defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
defp accepts_html?(conn) do
- conn |> get_req_header("accept") |> List.first() |> String.contains?("text/html")
+ case get_req_header(conn, "accept") do
+ [accept | _] -> String.contains?(accept, "text/html")
+ _ -> false
+ end
end
end
diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex
index f372829a2..36ff024a7 100644
--- a/lib/pleroma/plugs/uploaded_media.ex
+++ b/lib/pleroma/plugs/uploaded_media.ex
@@ -14,9 +14,14 @@ defmodule Pleroma.Plugs.UploadedMedia do
# no slashes
@path "media"
+ @default_cache_control_header "public, max-age=1209600"
+
def init(_opts) do
static_plug_opts =
- []
+ [
+ headers: %{"cache-control" => @default_cache_control_header},
+ cache_control_for_etags: @default_cache_control_header
+ ]
|> Keyword.put(:from, "__unconfigured_media_plug")
|> Keyword.put(:at, "/__unconfigured_media_plug")
|> Plug.Static.init()
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 35b973b56..4bbeb493c 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -5,7 +5,7 @@
defmodule Pleroma.ReverseProxy do
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match if-range range)
- @resp_cache_headers ~w(etag date last-modified cache-control)
+ @resp_cache_headers ~w(etag date last-modified)
@keep_resp_headers @resp_cache_headers ++
~w(content-type content-disposition content-encoding content-range) ++
~w(accept-ranges vary)
@@ -32,9 +32,6 @@ defmodule Pleroma.ReverseProxy do
* request: `#{inspect(@keep_req_headers)}`
* response: `#{inspect(@keep_resp_headers)}`
- If no caching headers (`#{inspect(@resp_cache_headers)}`) are returned by upstream, `cache-control` will be
- set to `#{inspect(@default_cache_control_header)}`.
-
Options:
* `redirect_on_failure` (default `false`). Redirects the client to the real remote URL if there's any HTTP
@@ -291,16 +288,17 @@ defp build_resp_headers(headers, opts) do
defp build_resp_cache_headers(headers, _opts) do
has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end)
- has_cache_control? = List.keymember?(headers, "cache-control", 0)
cond do
- has_cache? && has_cache_control? ->
- headers
-
has_cache? ->
- # There's caching header present but no cache-control -- we need to explicitely override it
- # to public as Plug defaults to "max-age=0, private, must-revalidate"
- List.keystore(headers, "cache-control", 0, {"cache-control", "public"})
+ # There's caching header present but no cache-control -- we need to set our own
+ # as Plug defaults to "max-age=0, private, must-revalidate"
+ List.keystore(
+ headers,
+ "cache-control",
+ 0,
+ {"cache-control", @default_cache_control_header}
+ )
true ->
List.keystore(
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 7531757f5..911dde6e2 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -16,6 +16,7 @@ defmodule Pleroma.User do
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.FollowingRelationship
+ alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.Notification
alias Pleroma.Object
@@ -839,10 +840,6 @@ def get_or_fetch_by_nickname(nickname) do
_e ->
with [_nick, _domain] <- String.split(nickname, "@"),
{:ok, user} <- fetch_by_nickname(nickname) do
- if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
- fetch_initial_posts(user)
- end
-
{:ok, user}
else
_e -> {:error, "not found " <> nickname}
@@ -850,11 +847,6 @@ def get_or_fetch_by_nickname(nickname) do
end
end
- @doc "Fetch some posts when the user has just been federated with"
- def fetch_initial_posts(user) do
- BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
- end
-
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do
User.Query.build(%{followers: user, deactivated: false})
@@ -1320,16 +1312,6 @@ def perform(:delete, %User{} = user) do
Repo.delete(user)
end
- def perform(:fetch_initial_posts, %User{} = user) do
- pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
-
- # Insert all the posts in reverse order, so they're in the right order on the timeline
- user.source_data["outbox"]
- |> Utils.fetch_ordered_collection(pages)
- |> Enum.reverse()
- |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
- end
-
def perform(:deactivate_async, user, status), do: deactivate(user, status)
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
@@ -1458,18 +1440,7 @@ def get_or_fetch_by_ap_id(ap_id) do
if !is_nil(user) and !needs_update?(user) do
{:ok, user}
else
- # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
- should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
-
- resp = fetch_by_ap_id(ap_id)
-
- if should_fetch_initial do
- with {:ok, %User{} = user} <- resp do
- fetch_initial_posts(user)
- end
- end
-
- resp
+ fetch_by_ap_id(ap_id)
end
end
@@ -2062,4 +2033,27 @@ def set_invisible(user, invisible) do
|> validate_required([:invisible])
|> update_and_set_cache()
end
+
+ def sanitize_html(%User{} = user) do
+ sanitize_html(user, nil)
+ end
+
+ # User data that mastodon isn't filtering (treated as plaintext):
+ # - field name
+ # - display name
+ def sanitize_html(%User{} = user, filter) do
+ fields =
+ user
+ |> User.fields()
+ |> Enum.map(fn %{"name" => name, "value" => value} ->
+ %{
+ "name" => name,
+ "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
+ }
+ end)
+
+ user
+ |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
+ |> Map.put(:fields, fields)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 779de0e4d..8b9eb4a2c 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Delivery
alias Pleroma.Object
alias Pleroma.Object.Fetcher
+ alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
@@ -18,23 +19,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.FederatingPlug
alias Pleroma.Web.Federator
require Logger
action_fallback(:errors)
+ @federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers]
+
+ plug(FederatingPlug when action in @federating_only_actions)
+
+ plug(
+ EnsureAuthenticatedPlug,
+ [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions
+ )
+
+ plug(
+ EnsureAuthenticatedPlug
+ when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers]
+ )
+
plug(
Pleroma.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
when action in [:activity, :object]
)
- plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay])
- def relay_active?(conn, _) do
+ defp relay_active?(conn, _) do
if Pleroma.Config.get([:instance, :allow_relay]) do
conn
else
@@ -127,11 +142,13 @@ defp set_cache_ttl_for(conn, entity) do
end
# GET /relay/following
- def following(%{assigns: %{relay: true}} = conn, _params) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("following.json", %{user: Relay.get_actor()})
+ def relay_following(conn, _params) do
+ with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("following.json", %{user: Relay.get_actor()})
+ end
end
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
@@ -164,11 +181,13 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end
# GET /relay/followers
- def followers(%{assigns: %{relay: true}} = conn, _params) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("followers.json", %{user: Relay.get_actor()})
+ def relay_followers(conn, _params) do
+ with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("followers.json", %{user: Relay.get_actor()})
+ end
end
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
@@ -200,13 +219,16 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end
end
- def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
+ def outbox(
+ %{assigns: %{user: for_user}} = conn,
+ %{"nickname" => nickname, "page" => page?} = params
+ )
when page? in [true, "true"] do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- User.ensure_keys_present(user) do
activities =
if params["max_id"] do
- ActivityPub.fetch_user_activities(user, nil, %{
+ ActivityPub.fetch_user_activities(user, for_user, %{
"max_id" => params["max_id"],
# This is a hack because postgres generates inefficient queries when filtering by
# 'Answer', poll votes will be hidden by the visibility filter in this case anyway
@@ -214,7 +236,7 @@ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
"limit" => 10
})
else
- ActivityPub.fetch_user_activities(user, nil, %{
+ ActivityPub.fetch_user_activities(user, for_user, %{
"limit" => 10,
"include_poll_votes" => true
})
@@ -255,8 +277,16 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
json(conn, "ok")
end
- # only accept relayed Creates
- def inbox(conn, %{"type" => "Create"} = params) do
+ # POST /relay/inbox -or- POST /internal/fetch/inbox
+ def inbox(conn, params) do
+ if params["type"] == "Create" && FederatingPlug.federating?() do
+ post_inbox_relayed_create(conn, params)
+ else
+ post_inbox_fallback(conn, params)
+ end
+ end
+
+ defp post_inbox_relayed_create(conn, params) do
Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source"
)
@@ -266,10 +296,11 @@ def inbox(conn, %{"type" => "Create"} = params) do
json(conn, "ok")
end
- def inbox(conn, params) do
+ defp post_inbox_fallback(conn, params) do
headers = Enum.into(conn.req_headers, %{})
- if String.contains?(headers["signature"], params["actor"]) do
+ if headers["signature"] && params["actor"] &&
+ String.contains?(headers["signature"], params["actor"]) do
Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
)
@@ -277,7 +308,9 @@ def inbox(conn, params) do
Logger.debug(inspect(conn.req_headers))
end
- json(conn, dgettext("errors", "error"))
+ conn
+ |> put_status(:bad_request)
+ |> json(dgettext("errors", "error"))
end
defp represent_service_actor(%User{} = user, conn) do
@@ -311,10 +344,8 @@ def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|> render("user.json", %{user: user})
end
- def whoami(_conn, _params), do: {:error, :not_found}
-
def read_inbox(
- %{assigns: %{user: %{nickname: nickname} = user}} = conn,
+ %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname, "page" => page?} = params
)
when page? in [true, "true"] do
@@ -337,7 +368,7 @@ def read_inbox(
})
end
- def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
+ def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
"nickname" => nickname
}) do
with {:ok, user} <- User.ensure_keys_present(user) do
@@ -348,15 +379,7 @@ def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
end
end
- def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
- err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
-
- conn
- |> put_status(:forbidden)
- |> json(err)
- end
-
- def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
+ def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
"nickname" => nickname
}) do
err =
@@ -370,7 +393,7 @@ def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
|> json(err)
end
- def handle_user_activity(user, %{"type" => "Create"} = params) do
+ defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do
object =
params["object"]
|> Map.merge(Map.take(params, ["to", "cc"]))
@@ -386,7 +409,7 @@ def handle_user_activity(user, %{"type" => "Create"} = params) do
})
end
- def handle_user_activity(user, %{"type" => "Delete"} = params) do
+ defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
with %Object{} = object <- Object.normalize(params["object"]),
true <- user.is_moderator || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do
@@ -396,7 +419,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
end
end
- def handle_user_activity(user, %{"type" => "Like"} = params) do
+ defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
with %Object{} = object <- Object.normalize(params["object"]),
{:ok, activity, _object} <- ActivityPub.like(user, object) do
{:ok, activity}
@@ -405,7 +428,7 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do
end
end
- def handle_user_activity(_, _) do
+ defp handle_user_activity(_, _) do
{:error, dgettext("errors", "Unhandled activity type")}
end
@@ -434,7 +457,7 @@ def update_outbox(
end
end
- def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
+ def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
err =
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
nickname: nickname,
@@ -446,13 +469,13 @@ def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} =
|> json(err)
end
- def errors(conn, {:error, :not_found}) do
+ defp errors(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> json(dgettext("errors", "Not found"))
end
- def errors(conn, _e) do
+ defp errors(conn, _e) do
conn
|> put_status(:internal_server_error)
|> json(dgettext("errors", "error"))
@@ -492,7 +515,7 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
- HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field
"""
- def upload_media(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
+ def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(
file,
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 2bc958670..15dd2ed45 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -784,45 +784,6 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do
defp build_flag_object(_), do: []
- @doc """
- Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after
- the first one to `pages_left` pages.
- If the amount of pages is higher than the collection has, it returns whatever was there.
- """
- def fetch_ordered_collection(from, pages_left, acc \\ []) do
- with {:ok, response} <- Tesla.get(from),
- {:ok, collection} <- Jason.decode(response.body) do
- case collection["type"] do
- "OrderedCollection" ->
- # If we've encountered the OrderedCollection and not the page,
- # just call the same function on the page address
- fetch_ordered_collection(collection["first"], pages_left)
-
- "OrderedCollectionPage" ->
- if pages_left > 0 do
- # There are still more pages
- if Map.has_key?(collection, "next") do
- # There are still more pages, go deeper saving what we have into the accumulator
- fetch_ordered_collection(
- collection["next"],
- pages_left - 1,
- acc ++ collection["orderedItems"]
- )
- else
- # No more pages left, just return whatever we already have
- acc ++ collection["orderedItems"]
- end
- else
- # Got the amount of pages needed, add them all to the accumulator
- acc ++ collection["orderedItems"]
- end
-
- _ ->
- {:error, "Not an OrderedCollection or OrderedCollectionPage"}
- end
- end
- end
-
#### Report-related helpers
def get_reports(params, page, page_size) do
params =
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index c0358b678..bc21ac6c7 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -73,6 +73,7 @@ def render("user.json", %{user: user}) do
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
+ user = User.sanitize_html(user)
endpoints = render("endpoints.json", %{user: user})
@@ -81,12 +82,6 @@ def render("user.json", %{user: user}) do
fields =
user
|> User.fields()
- |> Enum.map(fn %{"name" => name, "value" => value} ->
- %{
- "name" => Pleroma.HTML.strip_tags(name),
- "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
- }
- end)
|> Enum.map(&Map.put(&1, "type", "PropertyValue"))
%{
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 47b7d2da3..175260bc2 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -745,14 +745,14 @@ def report_notes_delete(%{assigns: %{user: user}} = conn, %{
end
end
- def list_statuses(%{assigns: %{user: admin}} = conn, params) do
+ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
godmode = params["godmode"] == "true" || params["godmode"] == true
local_only = params["local_only"] == "true" || params["local_only"] == true
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
activities =
- ActivityPub.fetch_statuses(admin, %{
+ ActivityPub.fetch_statuses(nil, %{
"godmode" => godmode,
"local_only" => local_only,
"limit" => page_size,
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 619390ef4..1e03849de 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.AdminAPI.AccountView do
use Pleroma.Web, :view
- alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.MediaProxy
@@ -26,7 +25,8 @@ def render("index.json", %{users: users}) do
def render("show.json", %{user: user}) do
avatar = User.avatar_url(user) |> MediaProxy.url()
- display_name = HTML.strip_tags(user.name || user.nickname)
+ display_name = Pleroma.HTML.strip_tags(user.name || user.nickname)
+ user = User.sanitize_html(user, FastSanitize.Sanitizer.StripTags)
%{
"id" => user.id,
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 348fdedf1..635e7cd38 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -331,7 +331,7 @@ def format_input(text, "text/html", options) do
def format_input(text, "text/markdown", options) do
text
|> Formatter.mentions_escape(options)
- |> Earmark.as_html!()
+ |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|> Formatter.linkify(options)
|> Formatter.html_escape("text/html")
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 118c3ac6f..72cb3ee27 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Pleroma.Plugs.HTTPSecurityPlug)
plug(Pleroma.Plugs.UploadedMedia)
- @static_cache_control "public max-age=86400 must-revalidate"
+ @static_cache_control "public, no-cache"
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index 59aabb549..9ba602d9f 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -25,7 +25,12 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
- ActivityPubController.call(conn, :user)
+ with %{halted: false} = conn <-
+ Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn,
+ unless_func: &Pleroma.Web.FederatingPlug.federating?/0
+ ) do
+ ActivityPubController.call(conn, :user)
+ end
end
def feed_redirect(conn, %{"nickname" => nickname}) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
index f165c9965..37b389382 100644
--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -86,6 +86,6 @@ defp local_mastodon_root_path(conn) do
@spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
defp get_or_make_app do
%{client_name: @local_mastodon_name, redirect_uris: "."}
- |> App.get_or_make(["read", "write", "follow", "push"])
+ |> App.get_or_make(["read", "write", "follow", "push", "admin"])
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 6dc191250..341dc2c91 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view
- alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
@@ -67,6 +66,7 @@ def render("relationships.json", %{user: user, targets: targets}) do
end
defp do_render("show.json", %{user: user} = opts) do
+ user = User.sanitize_html(user, User.html_filter_policy(opts[:for]))
display_name = user.name || user.nickname
image = User.avatar_url(user) |> MediaProxy.url()
@@ -100,17 +100,6 @@ defp do_render("show.json", %{user: user} = opts) do
}
end)
- fields =
- user
- |> User.fields()
- |> Enum.map(fn %{"name" => name, "value" => value} ->
- %{
- "name" => name,
- "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
- }
- end)
-
- bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
relationship = render("relationship.json", %{user: opts[:for], target: user})
%{
@@ -123,17 +112,17 @@ defp do_render("show.json", %{user: user} = opts) do
followers_count: followers_count,
following_count: following_count,
statuses_count: user.note_count,
- note: bio || "",
+ note: user.bio || "",
url: User.profile_url(user),
avatar: image,
avatar_static: image,
header: header,
header_static: header,
emojis: emojis,
- fields: fields,
+ fields: user.fields,
bot: bot,
source: %{
- note: HTML.strip_tags((user.bio || "") |> String.replace(" ", "\n")),
+ note: Pleroma.HTML.strip_tags((user.bio || "") |> String.replace(" ", "\n")),
sensitive: false,
fields: user.raw_fields,
pleroma: %{
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index c443c888c..6fd3cfce5 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -16,6 +16,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.Router
+ plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
+ unless_func: &Pleroma.Web.FederatingPlug.federating?/0
+ )
+
plug(
RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
@@ -135,13 +139,13 @@ def notice_player(conn, %{"id" => id}) do
end
end
- def errors(conn, {:error, :not_found}) do
+ defp errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found")
end
- def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+ defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
- def errors(conn, _) do
+ defp errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index 0e160bbfc..dae7f0f2f 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -101,6 +101,11 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id})
conn
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
+ else
+ _error ->
+ conn
+ |> put_status(404)
+ |> json(%{"error" => "Unknown conversation id"})
end
end
@@ -108,9 +113,9 @@ def conversation_statuses(
%{assigns: %{user: user}} = conn,
%{"id" => participation_id} = params
) do
- participation = Participation.get(participation_id, preload: [:conversation])
-
- if user.id == participation.user_id do
+ with %Participation{} = participation <-
+ Participation.get(participation_id, preload: [:conversation]),
+ true <- user.id == participation.user_id do
params =
params
|> Map.put("blocking_user", user)
@@ -126,6 +131,11 @@ def conversation_statuses(
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
+ else
+ _error ->
+ conn
+ |> put_status(404)
+ |> json(%{"error" => "Unknown conversation id"})
end
end
@@ -133,15 +143,22 @@ def update_conversation(
%{assigns: %{user: user}} = conn,
%{"id" => participation_id, "recipients" => recipients}
) do
- participation =
- participation_id
- |> Participation.get()
-
- with true <- user.id == participation.user_id,
+ with %Participation{} = participation <- Participation.get(participation_id),
+ true <- user.id == participation.user_id,
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
conn
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
+ else
+ {:error, message} ->
+ conn
+ |> put_status(:bad_request)
+ |> json(%{"error" => message})
+
+ _error ->
+ conn
+ |> put_status(404)
+ |> json(%{"error" => "Unknown conversation id"})
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 980242c68..e4e3ee704 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -541,6 +541,7 @@ defmodule Pleroma.Web.Router do
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end
+ # Server to Server (S2S) AP interactions
pipeline :activitypub do
plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
@@ -554,6 +555,7 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/outbox", ActivityPubController, :outbox)
end
+ # Client to Server (C2S) AP interactions
pipeline :activitypub_client do
plug(:accepts, ["activity+json", "json"])
plug(:fetch_session)
@@ -597,8 +599,8 @@ defmodule Pleroma.Web.Router do
post("/inbox", ActivityPubController, :inbox)
end
- get("/following", ActivityPubController, :following, assigns: %{relay: true})
- get("/followers", ActivityPubController, :followers, assigns: %{relay: true})
+ get("/following", ActivityPubController, :relay_following)
+ get("/followers", ActivityPubController, :relay_followers)
end
scope "/internal/fetch", Pleroma.Web.ActivityPub do
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index 5ac75f1c4..7f9464268 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -17,6 +17,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id)
+ plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
+ unless_func: &Pleroma.Web.FederatingPlug.federating?/0
+ )
+
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
@@ -33,7 +37,7 @@ defp not_found(conn, message) do
|> render("error.html", %{message: message, meta: ""})
end
- def get_counts(%Activity{} = activity) do
+ defp get_counts(%Activity{} = activity) do
%Object{data: data} = Object.normalize(activity)
%{
@@ -43,9 +47,9 @@ def get_counts(%Activity{} = activity) do
}
end
- def represent(%Activity{} = activity), do: represent(activity, false)
+ defp represent(%Activity{} = activity), do: represent(activity, false)
- def represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
{:ok, user} = User.get_or_fetch(activity.object.data["actor"])
link =
@@ -54,10 +58,17 @@ def represent(%Activity{object: %Object{data: data}} = activity, selected) do
_ -> data["url"] || data["external_url"] || data["id"]
end
+ content =
+ if data["content"] do
+ Pleroma.HTML.filter_tags(data["content"])
+ else
+ nil
+ end
+
%{
- user: user,
+ user: User.sanitize_html(user),
title: get_title(activity.object),
- content: data["content"] || nil,
+ content: content,
attachment: data["attachment"],
link: link,
published: data["published"],
@@ -109,7 +120,7 @@ def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
next_page_id = List.last(timeline) && List.last(timeline).id
render(conn, "profile.html", %{
- user: user,
+ user: User.sanitize_html(user),
timeline: timeline,
prev_page_id: prev_page_id,
next_page_id: next_page_id,
@@ -147,17 +158,17 @@ def show(%{assigns: %{activity_id: _}} = conn, _params) do
end
end
- def assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
+ defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
- def assign_id(%{path_info: ["users", user_id]} = conn, _opts),
+ defp assign_id(%{path_info: ["users", user_id]} = conn, _opts),
do: assign(conn, :username_or_id, user_id)
- def assign_id(%{path_info: ["objects", object_id]} = conn, _opts),
+ defp assign_id(%{path_info: ["objects", object_id]} = conn, _opts),
do: assign(conn, :object_id, object_id)
- def assign_id(%{path_info: ["activities", activity_id]} = conn, _opts),
+ defp assign_id(%{path_info: ["activities", activity_id]} = conn, _opts),
do: assign(conn, :activity_id, activity_id)
- def assign_id(conn, _opts), do: conn
+ defp assign_id(conn, _opts), do: conn
end
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
index fbf31c7eb..89da760da 100644
--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -16,6 +16,8 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
+ plug(Pleroma.Web.FederatingPlug)
+
# Note: follower can submit the form (with password auth) not being signed in (having no token)
plug(
OAuthScopesPlug,
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index bca0e26eb..537f9f778 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -17,6 +17,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.WebFinger
+ plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
+
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]}
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
index 598df6580..0f8ece2c4 100644
--- a/lib/pleroma/workers/background_worker.ex
+++ b/lib/pleroma/workers/background_worker.ex
@@ -10,10 +10,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker
- def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}, _job) do
- user = User.get_cached_by_id(user_id)
- User.perform(:fetch_initial_posts, user)
- end
def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do
user = User.get_cached_by_id(user_id)
diff --git a/mix.exs b/mix.exs
index 4404043d1..f4f459682 100644
--- a/mix.exs
+++ b/mix.exs
@@ -134,7 +134,7 @@ defp deps do
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
{:earmark, "~> 1.3"},
- {:bbcode, "~> 0.1.1"},
+ {:bbcode_pleroma, "~> 0.2.0"},
{:ex_machina, "~> 2.3", only: :test},
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.3", only: :test},
diff --git a/mix.lock b/mix.lock
index af53e5c0f..b791dccc4 100644
--- a/mix.lock
+++ b/mix.lock
@@ -3,7 +3,8 @@
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]},
"base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
- "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"},
+ "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
+ "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"},
diff --git a/priv/repo/migrations/20200314123607_config_remove_fetch_initial_posts.exs b/priv/repo/migrations/20200314123607_config_remove_fetch_initial_posts.exs
new file mode 100644
index 000000000..392f531e8
--- /dev/null
+++ b/priv/repo/migrations/20200314123607_config_remove_fetch_initial_posts.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.ConfigRemoveFetchInitialPosts do
+ use Ecto.Migration
+
+ def change do
+ execute(
+ "delete from config where config.key = ':fetch_initial_posts' and config.group = ':pleroma';",
+ ""
+ )
+ end
+end
diff --git a/priv/repo/migrations/20200315125756_delete_fetch_initial_posts_jobs.exs b/priv/repo/migrations/20200315125756_delete_fetch_initial_posts_jobs.exs
new file mode 100644
index 000000000..5b8e3ab91
--- /dev/null
+++ b/priv/repo/migrations/20200315125756_delete_fetch_initial_posts_jobs.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.DeleteFetchInitialPostsJobs do
+ use Ecto.Migration
+
+ def change do
+ execute(
+ "delete from oban_jobs where worker = 'Pleroma.Workers.BackgroundWorker' and args->>'op' = 'fetch_initial_posts';",
+ ""
+ )
+ end
+end
diff --git a/priv/static/adminfe/chunk-17a5.edcdbe30.css b/priv/static/adminfe/chunk-0d8f.650c8e81.css
similarity index 100%
rename from priv/static/adminfe/chunk-17a5.edcdbe30.css
rename to priv/static/adminfe/chunk-0d8f.650c8e81.css
diff --git a/priv/static/adminfe/chunk-2b8b.0f1ee211.css b/priv/static/adminfe/chunk-136a.3936457d.css
similarity index 100%
rename from priv/static/adminfe/chunk-2b8b.0f1ee211.css
rename to priv/static/adminfe/chunk-136a.3936457d.css
diff --git a/priv/static/adminfe/chunk-15fa.dc3643e6.css b/priv/static/adminfe/chunk-15fa.5a5f973d.css
similarity index 100%
rename from priv/static/adminfe/chunk-15fa.dc3643e6.css
rename to priv/static/adminfe/chunk-15fa.5a5f973d.css
diff --git a/priv/static/adminfe/chunk-46cf.6dd5bbb7.css b/priv/static/adminfe/chunk-46cf.a43e9415.css
similarity index 100%
rename from priv/static/adminfe/chunk-46cf.6dd5bbb7.css
rename to priv/static/adminfe/chunk-46cf.a43e9415.css
diff --git a/priv/static/adminfe/chunk-453a.bbab87da.css b/priv/static/adminfe/chunk-46ef.d45db7be.css
similarity index 100%
rename from priv/static/adminfe/chunk-453a.bbab87da.css
rename to priv/static/adminfe/chunk-46ef.d45db7be.css
diff --git a/priv/static/adminfe/chunk-293a.a8b5ee5b.css b/priv/static/adminfe/chunk-4e7d.7aace723.css
similarity index 57%
rename from priv/static/adminfe/chunk-293a.a8b5ee5b.css
rename to priv/static/adminfe/chunk-4e7d.7aace723.css
index 924633a80..9a35b64a0 100644
--- a/priv/static/adminfe/chunk-293a.a8b5ee5b.css
+++ b/priv/static/adminfe/chunk-4e7d.7aace723.css
@@ -1 +1 @@
-.status-card{margin-bottom:10px}.status-card .account{text-decoration:underline;line-height:26px;font-size:13px}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;height:22px}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.moderate-user-button{text-align:left;width:200px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.avatar-name-container[data-v-7a96af64],header[data-v-7a96af64]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}header[data-v-7a96af64]{margin:22px 0;padding-left:15px}header h1[data-v-7a96af64]{margin:0 0 0 10px}table[data-v-7a96af64]{margin:10px 0 0 15px}table .name-col[data-v-7a96af64]{width:150px}.el-table--border[data-v-7a96af64]:after,.el-table--group[data-v-7a96af64]:after,.el-table[data-v-7a96af64]:before{background-color:transparent}.poll ul[data-v-7a96af64]{list-style-type:none;padding:0;width:30%}.image[data-v-7a96af64]{width:20%}.image img[data-v-7a96af64]{width:100%}.no-statuses[data-v-7a96af64]{margin-left:28px;color:#606266}.recent-statuses-container[data-v-7a96af64]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:67%}.recent-statuses-header[data-v-7a96af64]{margin-top:10px}.statuses[data-v-7a96af64]{padding:0 20px 0 0}.show-private[data-v-7a96af64]{width:200px;text-align:left;line-height:67px;margin-right:20px}.show-private-statuses[data-v-7a96af64]{margin-left:28px;margin-bottom:20px}.recent-statuses[data-v-7a96af64]{margin-left:28px}.user-page-header[data-v-7a96af64]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:0 20px}.user-page-header h1[data-v-7a96af64]{display:inline}.user-profile-card[data-v-7a96af64]{margin:0 20px;width:30%;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.user-profile-container[data-v-7a96af64]{display:-webkit-box;display:-ms-flexbox;display:flex}.user-profile-table[data-v-7a96af64]{margin:0}.user-profile-tag[data-v-7a96af64]{margin:0 4px 4px 0}@media only screen and (max-width:480px){.avatar-name-container[data-v-7a96af64]{margin-bottom:10px}.recent-statuses[data-v-7a96af64]{margin:20px 10px 15px}.recent-statuses-container[data-v-7a96af64]{width:100%;margin:0 10px}.show-private-statuses[data-v-7a96af64]{margin:0 10px 20px}.user-page-header[data-v-7a96af64]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;padding:0;margin:7px 0 15px 10px}.user-profile-card[data-v-7a96af64]{margin:0 10px;width:95%}.user-profile-card td[data-v-7a96af64]{width:80px}.user-profile-container[data-v-7a96af64]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}@media only screen and (max-width:801px) and (min-width:481px){.avatar-name-container[data-v-7a96af64]{margin-bottom:20px}.recent-statuses[data-v-7a96af64]{margin:20px 10px 15px 0}.recent-statuses-container[data-v-7a96af64]{width:97%;margin:0 20px}.show-private-statuses[data-v-7a96af64]{margin:0 10px 20px 0}.user-page-header[data-v-7a96af64]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;padding:0;margin:7px 0 20px 20px}.user-profile-card[data-v-7a96af64]{margin:0 20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.user-profile-container[data-v-7a96af64]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}
\ No newline at end of file
+.status-card{margin-bottom:10px}.status-card .account{text-decoration:underline;line-height:26px;font-size:13px}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;height:22px}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.moderate-user-button{text-align:left;width:200px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.avatar-name-container[data-v-68790c38],header[data-v-68790c38]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}header[data-v-68790c38]{margin:22px 0;padding-left:15px}header h1[data-v-68790c38]{margin:0 0 0 10px}table[data-v-68790c38]{margin:10px 0 0 15px}table .name-col[data-v-68790c38]{width:150px}.el-table--border[data-v-68790c38]:after,.el-table--group[data-v-68790c38]:after,.el-table[data-v-68790c38]:before{background-color:transparent}.poll ul[data-v-68790c38]{list-style-type:none;padding:0;width:30%}.image[data-v-68790c38]{width:20%}.image img[data-v-68790c38]{width:100%}.no-statuses[data-v-68790c38]{margin-left:28px;color:#606266}.recent-statuses-container[data-v-68790c38]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:67%}.recent-statuses-header[data-v-68790c38]{margin-top:10px}.statuses[data-v-68790c38]{padding:0 20px 0 0}.show-private[data-v-68790c38]{width:200px;text-align:left;line-height:67px;margin-right:20px}.show-private-statuses[data-v-68790c38]{margin-left:28px;margin-bottom:20px}.recent-statuses[data-v-68790c38]{margin-left:28px}.user-page-header[data-v-68790c38]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:0 20px}.user-page-header h1[data-v-68790c38]{display:inline}.user-profile-card[data-v-68790c38]{margin:0 20px;width:30%;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.user-profile-container[data-v-68790c38]{display:-webkit-box;display:-ms-flexbox;display:flex}.user-profile-table[data-v-68790c38]{margin:0}.user-profile-tag[data-v-68790c38]{margin:0 4px 4px 0}@media only screen and (max-width:480px){.avatar-name-container[data-v-68790c38]{margin-bottom:10px}.recent-statuses[data-v-68790c38]{margin:20px 10px 15px}.recent-statuses-container[data-v-68790c38]{width:100%;margin:0 10px}.show-private-statuses[data-v-68790c38]{margin:0 10px 20px}.user-page-header[data-v-68790c38]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;padding:0;margin:7px 0 15px 10px}.user-profile-card[data-v-68790c38]{margin:0 10px;width:95%}.user-profile-card td[data-v-68790c38]{width:80px}.user-profile-container[data-v-68790c38]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}@media only screen and (max-width:801px) and (min-width:481px){.avatar-name-container[data-v-68790c38]{margin-bottom:20px}.recent-statuses[data-v-68790c38]{margin:20px 10px 15px 0}.recent-statuses-container[data-v-68790c38]{width:97%;margin:0 20px}.show-private-statuses[data-v-68790c38]{margin:0 10px 20px 0}.user-page-header[data-v-68790c38]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;padding:0;margin:7px 0 20px 20px}.user-profile-card[data-v-68790c38]{margin:0 20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.user-profile-container[data-v-68790c38]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-4e46.ad5e9ff3.css b/priv/static/adminfe/chunk-4ffb.dd09fe2e.css
similarity index 100%
rename from priv/static/adminfe/chunk-4e46.ad5e9ff3.css
rename to priv/static/adminfe/chunk-4ffb.dd09fe2e.css
diff --git a/priv/static/adminfe/chunk-6dd6.85f319f7.css b/priv/static/adminfe/chunk-876c.90dffac4.css
similarity index 100%
rename from priv/static/adminfe/chunk-6dd6.85f319f7.css
rename to priv/static/adminfe/chunk-876c.90dffac4.css
diff --git a/priv/static/adminfe/chunk-03b0.49362218.css b/priv/static/adminfe/chunk-87b3.2affd602.css
similarity index 57%
rename from priv/static/adminfe/chunk-03b0.49362218.css
rename to priv/static/adminfe/chunk-87b3.2affd602.css
index e43c776aa..c4fa46d3e 100644
--- a/priv/static/adminfe/chunk-03b0.49362218.css
+++ b/priv/static/adminfe/chunk-87b3.2affd602.css
@@ -1 +1 @@
-a{text-decoration:underline}.center-label label{text-align:center}.center-label label span{float:left}.code{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button{margin-left:5px}.description>p{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.description>p code{display:inline;padding:2px 3px;font-size:14px}.description-container{overflow-wrap:break-word;margin-bottom:0}.divider{margin:0 0 18px}.divider.thick-line{height:2px}.editable-keyword-container{width:100%}.el-form-item .rate-limit{margin-right:0}.el-input-group__prepend{padding-left:10px;padding-right:10px}.esshd-list{margin:0}.expl,.expl>p{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code,.expl code{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay{width:350px;margin-right:7px}.form-container{margin-bottom:80px}.grouped-settings-header{margin:0 0 14px}.highlight{background-color:#e6e6e6}.icons-button-container{width:100%;margin-bottom:10px}.icons-button-desc{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input{width:30%;margin-right:8px}.icon-minus-button{width:36px;height:36px}.icon-value-input{width:70%;margin-left:8px}.icons-container,.input-container{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item{margin-right:30px;width:100%}.input-container .el-select,.keyword-container{width:100%}label{overflow:hidden;text-overflow:ellipsis}.label-font{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl{margin-left:10px}.limit-input{width:47%;margin:0 0 5px 1%}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot{margin-bottom:15px}.mascot-container{width:100%}.mascot-input{margin-bottom:7px}.mascot-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input{margin-right:10px}.multiple-select-container{width:100%}.name-input{width:30%;margin-right:8px}.pattern-input{width:20%;margin-right:8px}.proxy-url-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input{width:35%;margin-right:8px}.proxy-url-value-input{width:35%;margin-left:8px;margin-right:10px}.prune-options{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio{margin-top:11px}.rate-limit .el-form-item__content{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container,.rate-limit-content{width:100%}.rate-limit-label{float:right}.rate-limit-label-container{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:36px;width:100%;margin-right:10px}.relays-container{margin:0 15px}.replacement-input{width:80%;margin-left:8px;margin-right:10px}.scale-input{width:47%;margin:0 1% 5px 0}.setting-input{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.settings-container{max-width:1824px;margin:auto}.settings-container .el-tabs{margin-top:20px}.settings-delete-button{margin-left:5px}.settings-docs-button{width:163px;text-align:left;padding:10px}.settings-header{margin:0}.settings-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 30px 15px 15px}.settings-reboot-button{width:145px;text-align:left;padding:10px;margin-right:5px}.single-input{margin-right:10px}.socks5-checkbox{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts{margin:36px 0 0}.submit-button{float:right;margin:0 30px 22px 0}.submit-button-container{width:100%;position:fixed;bottom:0;right:0;z-index:10000}.switch-input{height:36px}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.submit-button-container{max-width:1637px;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.delete-setting-button{margin:4px 0 0 5px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 5px 7px 0}.description>p code{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.divider{margin:0 0 10px}.divider .thick-line{height:2px}.follow-relay{width:70%;margin-right:5px}.follow-relay input{width:100%}.follow-relay-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container{width:100%}.input-container .el-form-item:first-child{margin:0;padding:0 15px 10px 0}.input-container .el-form-item:first-child .mascot-form-item,.input-container .el-form-item:first-child .rate-limit{padding:0}.input-container .settings-delete-button{margin-top:4px;float:right}.input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.limit-input{width:45%}.proxy-url-input{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;margin-bottom:0}.proxy-url-host-input{width:100%;margin-bottom:5px}.proxy-url-value-input{width:100%;margin-left:0}.prune-options{height:80px}.prune-options,.rate-limit .el-form-item__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-label{float:left}.scale-input{width:45%}.setting-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.settings-header{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:0}.settings-header-container{margin:15px}.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.settings-menu{width:163px}.socks5-checkbox-container{width:100%}.submit-button{margin:0 15px 22px 0}.el-input__inner{padding:0 5px}.el-form-item__label:not(.no-top-margin){padding-left:3px;padding-right:10px;line-height:22px;margin-top:7px}.el-message{min-width:80%}.el-select__tags{overflow:hidden}.expl,.expl>p{line-height:16px}.icon-key-input{width:40%;margin-right:4px}.icon-minus-button{width:28px;height:28px;margin-top:4px}.icon-values-container{margin:0 7px 7px 0}.icon-value-input{width:60%;margin-left:4px}.icons-button-container{line-height:24px}.line{margin-bottom:10px}.mascot-container{margin-bottom:5px}.name-input{width:40%;margin-right:5px}p.expl{line-height:20px}.pattern-input{width:40%;margin-right:4px}.relays-container{margin:0 10px}.replacement-input{width:60%;margin-left:4px;margin-right:5px}.value-input{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:801px) and (min-width:481px){.delete-setting-button{margin:4px 0 0 10px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 15px 10px 0}.icon-minus-button{width:28px;height:28px;margin-top:4px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span{margin-left:10px}.input-row,.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-label-container{width:250px}.settings-delete-button{float:right}}
\ No newline at end of file
+a{text-decoration:underline}.center-label label{text-align:center}.center-label label span{float:left}.code{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button{margin-left:5px}.description>p{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.description>p code{display:inline;padding:2px 3px;font-size:14px}.description-container{overflow-wrap:break-word;margin-bottom:0}.divider{margin:0 0 18px}.divider.thick-line{height:2px}.editable-keyword-container{width:100%}.el-form-item .rate-limit{margin-right:0}.el-input-group__prepend{padding-left:10px;padding-right:10px}.esshd-list{margin:0}.expl,.expl>p{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code,.expl code{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay{width:350px;margin-right:7px}.form-container{margin-bottom:80px}.grouped-settings-header{margin:0 0 14px}.highlight{background-color:#e6e6e6}.icons-button-container{width:100%;margin-bottom:10px}.icons-button-desc{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input{width:30%;margin-right:8px}.icon-minus-button{width:36px;height:36px}.icon-value-input{width:70%;margin-left:8px}.icons-container,.input-container{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item{margin-right:30px;width:100%}.input-container .el-select,.keyword-container{width:100%}label{overflow:hidden;text-overflow:ellipsis}.label-font{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl{margin-left:10px}.limit-input{width:47%;margin:0 0 5px 1%}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot{margin-bottom:15px}.mascot-container{width:100%}.mascot-input{margin-bottom:7px}.mascot-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input{margin-right:10px}.multiple-select-container{width:100%}.name-input{width:30%;margin-right:8px}.pattern-input{width:20%;margin-right:8px}.proxy-url-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input{width:35%;margin-right:8px}.proxy-url-value-input{width:35%;margin-left:8px;margin-right:10px}.prune-options{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio{margin-top:11px}.rate-limit .el-form-item__content{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container,.rate-limit-content{width:100%}.rate-limit-label{float:right}.rate-limit-label-container{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:36px;width:100%;margin-right:10px}.relays-container{margin:0 15px}.replacement-input{width:80%;margin-left:8px;margin-right:10px}.scale-input{width:47%;margin:0 1% 5px 0}.setting-input{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.settings-container{max-width:1824px;margin:auto}.settings-container .el-tabs{margin-top:20px}.settings-delete-button{margin-left:5px}.settings-docs-button{width:163px;text-align:left;padding:10px}.settings-header{margin:0}.settings-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 30px 15px 15px}.settings-reboot-button{width:145px;text-align:left;padding:10px;margin-right:5px}.single-input{margin-right:10px}.socks5-checkbox{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts{margin:36px 0 0}.submit-button{float:right;margin:0 30px 22px 0}.submit-button-container{width:100%;position:fixed;bottom:0;right:0;z-index:10000}.switch-input{height:36px}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.submit-button-container{max-width:1637px;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab,.crontab label{width:100%}.delete-setting-button{margin:4px 0 0 5px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 5px 7px 0}.description>p code{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.divider{margin:0 0 10px}.divider .thick-line{height:2px}.follow-relay{width:70%;margin-right:5px}.follow-relay input{width:100%}.follow-relay-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container{width:100%}.input-container .el-form-item:first-child{margin:0;padding:0 15px 10px 0}.input-container .el-form-item.crontab-container:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item,.input-container .el-form-item:first-child .rate-limit{padding:0}.input-container .settings-delete-button{margin-top:4px;float:right}.input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.limit-input{width:45%}.proxy-url-input{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;margin-bottom:0}.proxy-url-host-input{width:100%;margin-bottom:5px}.proxy-url-value-input{width:100%;margin-left:0}.prune-options{height:80px}.prune-options,.rate-limit .el-form-item__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-label{float:left}.scale-input{width:45%}.setting-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.settings-header{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:0}.settings-header-container{margin:15px}.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.settings-menu{width:163px}.socks5-checkbox-container{width:100%}.submit-button{margin:0 15px 22px 0}.el-input__inner{padding:0 5px}.el-form-item__label:not(.no-top-margin){padding-left:3px;padding-right:10px;line-height:22px;margin-top:7px}.el-message{min-width:80%}.el-select__tags{overflow:hidden}.expl,.expl>p{line-height:16px}.icon-key-input{width:40%;margin-right:4px}.icon-minus-button{width:28px;height:28px;margin-top:4px}.icon-values-container{margin:0 7px 7px 0}.icon-value-input{width:60%;margin-left:4px}.icons-button-container{line-height:24px}.line{margin-bottom:10px}.mascot-container{margin-bottom:5px}.name-input{width:40%;margin-right:5px}p.expl{line-height:20px}.pattern-input{width:40%;margin-right:4px}.relays-container{margin:0 10px}.replacement-input{width:60%;margin-left:4px;margin-right:5px}.value-input{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:801px) and (min-width:481px){.delete-setting-button{margin:4px 0 0 10px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 15px 10px 0}.icon-minus-button{width:28px;height:28px;margin-top:4px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span{margin-left:10px}.input-row,.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-label-container{width:250px}.settings-delete-button{float:right}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-cf58.80435fa1.css b/priv/static/adminfe/chunk-cf57.4d39576f.css
similarity index 75%
rename from priv/static/adminfe/chunk-cf58.80435fa1.css
rename to priv/static/adminfe/chunk-cf57.4d39576f.css
index 8b0f21153..1190aca24 100644
--- a/priv/static/adminfe/chunk-cf58.80435fa1.css
+++ b/priv/static/adminfe/chunk-cf57.4d39576f.css
@@ -1 +1 @@
-.actions-button[data-v-3850612b]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-3850612b]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-3850612b]{float:right}.el-icon-edit[data-v-3850612b]{margin-right:5px}.tag-container[data-v-3850612b]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-3850612b]{padding-right:20px}.no-hover[data-v-3850612b]:hover{color:#606266;background-color:#fff;cursor:auto}.status-card{margin-bottom:10px}.status-card .account{text-decoration:underline;line-height:26px;font-size:13px}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;height:22px}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.statuses-container{padding:0 15px}.statuses-container .status-container{margin:0 0 10px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 0 15px}.select-instance{width:350px}.statuses-pagination{padding:15px 0;text-align:center}h1{margin:22px 0 0}@media only screen and (max-width:480px){.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:10px 0}.select-field{width:100%;margin-bottom:5px}.select-instance{width:100%}}
\ No newline at end of file
+.actions-button[data-v-3850612b]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-3850612b]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-3850612b]{float:right}.el-icon-edit[data-v-3850612b]{margin-right:5px}.tag-container[data-v-3850612b]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-3850612b]{padding-right:20px}.no-hover[data-v-3850612b]:hover{color:#606266;background-color:#fff;cursor:auto}.status-card{margin-bottom:10px}.status-card .account{text-decoration:underline;line-height:26px;font-size:13px}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;height:22px}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.statuses-container{padding:0 15px}.statuses-container .status-container{margin:0 0 10px}.checkbox-container{margin-bottom:15px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 0 15px}.select-instance{width:350px}.statuses-pagination{padding:15px 0;text-align:center}h1{margin:22px 0 0}@media only screen and (max-width:480px){.checkbox-container{margin-bottom:10px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:10px 0}.select-field{width:100%;margin-bottom:5px}.select-instance{width:100%}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-560d.802cfba1.css b/priv/static/adminfe/chunk-e5cf.cba3ae06.css
similarity index 100%
rename from priv/static/adminfe/chunk-560d.802cfba1.css
rename to priv/static/adminfe/chunk-e5cf.cba3ae06.css
diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html
index e2db408c3..717b0f32d 100644
--- a/priv/static/adminfe/index.html
+++ b/priv/static/adminfe/index.html
@@ -1 +1 @@
-Admin FE
\ No newline at end of file
+Admin FE
\ No newline at end of file
diff --git a/priv/static/adminfe/static/js/app.55df3157.js b/priv/static/adminfe/static/js/app.55df3157.js
deleted file mode 100644
index d1a37af1c..000000000
Binary files a/priv/static/adminfe/static/js/app.55df3157.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/app.55df3157.js.map b/priv/static/adminfe/static/js/app.55df3157.js.map
deleted file mode 100644
index 740783b80..000000000
Binary files a/priv/static/adminfe/static/js/app.55df3157.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/app.d2c3c6b3.js b/priv/static/adminfe/static/js/app.d2c3c6b3.js
new file mode 100644
index 000000000..c527207dd
Binary files /dev/null and b/priv/static/adminfe/static/js/app.d2c3c6b3.js differ
diff --git a/priv/static/adminfe/static/js/app.d2c3c6b3.js.map b/priv/static/adminfe/static/js/app.d2c3c6b3.js.map
new file mode 100644
index 000000000..7b2d4dc05
Binary files /dev/null and b/priv/static/adminfe/static/js/app.d2c3c6b3.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js b/priv/static/adminfe/static/js/chunk-03b0.7a203856.js
deleted file mode 100644
index 43ca0e4e6..000000000
Binary files a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js.map b/priv/static/adminfe/static/js/chunk-03b0.7a203856.js.map
deleted file mode 100644
index 697a106ac..000000000
Binary files a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-17a5.13b13757.js
rename to priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js
index 80e7a8ac7..e3b0ae986 100644
Binary files a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js and b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js differ
diff --git a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js.map b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-17a5.13b13757.js.map
rename to priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js.map
index 7da1a0077..cf75f3243 100644
Binary files a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js.map and b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js
rename to priv/static/adminfe/static/js/chunk-136a.142aa42a.js
index 4b100db60..812089b5f 100644
Binary files a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js and b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js differ
diff --git a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js.map b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js.map
rename to priv/static/adminfe/static/js/chunk-136a.142aa42a.js.map
index a7282eaf4..f6b4c84aa 100644
Binary files a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js.map and b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js b/priv/static/adminfe/static/js/chunk-15fa.34070731.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-15fa.15303f3a.js
rename to priv/static/adminfe/static/js/chunk-15fa.34070731.js
index 7d3e0c56e..937908d00 100644
Binary files a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js and b/priv/static/adminfe/static/js/chunk-15fa.34070731.js differ
diff --git a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js.map b/priv/static/adminfe/static/js/chunk-15fa.34070731.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-15fa.15303f3a.js.map
rename to priv/static/adminfe/static/js/chunk-15fa.34070731.js.map
index f08d1dbf9..d3830be7c 100644
Binary files a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js.map and b/priv/static/adminfe/static/js/chunk-15fa.34070731.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-293a.a728de01.js b/priv/static/adminfe/static/js/chunk-293a.a728de01.js
deleted file mode 100644
index c856e21eb..000000000
Binary files a/priv/static/adminfe/static/js/chunk-293a.a728de01.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-293a.a728de01.js.map b/priv/static/adminfe/static/js/chunk-293a.a728de01.js.map
deleted file mode 100644
index 03f61abcb..000000000
Binary files a/priv/static/adminfe/static/js/chunk-293a.a728de01.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-46cf.104380a9.js
rename to priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js
index 9e1e1520b..0795a46b6 100644
Binary files a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js and b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js differ
diff --git a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js.map b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-46cf.104380a9.js.map
rename to priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js.map
index b9357ca8f..9993be4aa 100644
Binary files a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js.map and b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js b/priv/static/adminfe/static/js/chunk-46ef.215af110.js
similarity index 98%
rename from priv/static/adminfe/static/js/chunk-453a.2fcd7192.js
rename to priv/static/adminfe/static/js/chunk-46ef.215af110.js
index b0ee1b6b0..db11c7488 100644
Binary files a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js and b/priv/static/adminfe/static/js/chunk-46ef.215af110.js differ
diff --git a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js.map b/priv/static/adminfe/static/js/chunk-46ef.215af110.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-453a.2fcd7192.js.map
rename to priv/static/adminfe/static/js/chunk-46ef.215af110.js.map
index b43d2f571..2da3dbec6 100644
Binary files a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js.map and b/priv/static/adminfe/static/js/chunk-46ef.215af110.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js
new file mode 100644
index 000000000..ef2379ed9
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js differ
diff --git a/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js.map b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js.map
new file mode 100644
index 000000000..b349f12eb
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js
similarity index 85%
rename from priv/static/adminfe/static/js/chunk-4e46.d257e435.js
rename to priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js
index 39c5dcc4e..5a7aa9f59 100644
Binary files a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js and b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js differ
diff --git a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js.map b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js.map
similarity index 98%
rename from priv/static/adminfe/static/js/chunk-4e46.d257e435.js.map
rename to priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js.map
index 75d3554ac..7c020768c 100644
Binary files a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js.map and b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js
similarity index 97%
rename from priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js
rename to priv/static/adminfe/static/js/chunk-876c.e4ceccca.js
index 670016168..841ceb9dc 100644
Binary files a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js and b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js differ
diff --git a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js.map b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js.map
rename to priv/static/adminfe/static/js/chunk-876c.e4ceccca.js.map
index b1438722c..88976a4fe 100644
Binary files a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js.map and b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js
new file mode 100644
index 000000000..9766fd7d2
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js differ
diff --git a/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js.map b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js.map
new file mode 100644
index 000000000..7472fcd92
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-cf57.42b96339.js b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js
new file mode 100644
index 000000000..81122f992
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js differ
diff --git a/priv/static/adminfe/static/js/chunk-cf57.42b96339.js.map b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js.map
new file mode 100644
index 000000000..7471835b9
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js b/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js
deleted file mode 100644
index b74c20373..000000000
Binary files a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js.map b/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js.map
deleted file mode 100644
index 0f3f15299..000000000
Binary files a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-560d.a8bb8682.js
rename to priv/static/adminfe/static/js/chunk-e5cf.501d7902.js
index 0b03305e9..fe5552943 100644
Binary files a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js and b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js differ
diff --git a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js.map b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-560d.a8bb8682.js.map
rename to priv/static/adminfe/static/js/chunk-e5cf.501d7902.js.map
index bfab1ade9..60676bfe7 100644
Binary files a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js.map and b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js.map differ
diff --git a/priv/static/adminfe/static/js/runtime.ae93ea9f.js b/priv/static/adminfe/static/js/runtime.ae93ea9f.js
deleted file mode 100644
index ebda2acde..000000000
Binary files a/priv/static/adminfe/static/js/runtime.ae93ea9f.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/runtime.fa19e5d1.js b/priv/static/adminfe/static/js/runtime.fa19e5d1.js
new file mode 100644
index 000000000..b905e42e1
Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.fa19e5d1.js differ
diff --git a/priv/static/adminfe/static/js/runtime.ae93ea9f.js.map b/priv/static/adminfe/static/js/runtime.fa19e5d1.js.map
similarity index 90%
rename from priv/static/adminfe/static/js/runtime.ae93ea9f.js.map
rename to priv/static/adminfe/static/js/runtime.fa19e5d1.js.map
index 6392c981a..6a2565556 100644
Binary files a/priv/static/adminfe/static/js/runtime.ae93ea9f.js.map and b/priv/static/adminfe/static/js/runtime.fa19e5d1.js.map differ
diff --git a/test/activity/ir/topics_test.exs b/test/activity/ir/topics_test.exs
index 8729e5746..14a6e6b71 100644
--- a/test/activity/ir/topics_test.exs
+++ b/test/activity/ir/topics_test.exs
@@ -59,8 +59,8 @@ test "non-local action does not produce public:local topic", %{activity: activit
describe "public visibility create events" do
setup do
activity = %Activity{
- object: %Object{data: %{"type" => "Create", "attachment" => []}},
- data: %{"to" => [Pleroma.Constants.as_public()]}
+ object: %Object{data: %{"attachment" => []}},
+ data: %{"type" => "Create", "to" => [Pleroma.Constants.as_public()]}
}
{:ok, activity: activity}
@@ -98,8 +98,8 @@ test "only converts strings to hash tags", %{
describe "public visibility create events with attachments" do
setup do
activity = %Activity{
- object: %Object{data: %{"type" => "Create", "attachment" => ["foo"]}},
- data: %{"to" => [Pleroma.Constants.as_public()]}
+ object: %Object{data: %{"attachment" => ["foo"]}},
+ data: %{"type" => "Create", "to" => [Pleroma.Constants.as_public()]}
}
{:ok, activity: activity}
diff --git a/test/earmark_renderer_test.exs b/test/earmark_renderer_test.exs
new file mode 100644
index 000000000..220d97d16
--- /dev/null
+++ b/test/earmark_renderer_test.exs
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.EarmarkRendererTest do
+ use ExUnit.Case
+
+ test "Paragraph" do
+ code = ~s[Hello\n\nWorld!]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == "Hello
World!
"
+ end
+
+ test "raw HTML" do
+ code = ~s[OwO ]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == "#{code}
"
+ end
+
+ test "rulers" do
+ code = ~s[before\n\n-----\n\nafter]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == "before
after
"
+ end
+
+ test "headings" do
+ code = ~s[# h1\n## h2\n### h3\n]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[h1 h2 h3 ]
+ end
+
+ test "blockquote" do
+ code = ~s[> whoms't are you quoting?]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == "whoms’t are you quoting?
"
+ end
+
+ test "code" do
+ code = ~s[`mix`]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[mix
]
+
+ code = ~s[``mix``]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[mix
]
+
+ code = ~s[```\nputs "Hello World"\n```]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[puts "Hello World"
]
+ end
+
+ test "lists" do
+ code = ~s[- one\n- two\n- three\n- four]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ""
+
+ code = ~s[1. one\n2. two\n3. three\n4. four\n]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == "one two three four "
+ end
+
+ test "delegated renderers" do
+ code = ~s[a b]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == "#{code}
"
+
+ code = ~s[*aaaa~*]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[aaaa~
]
+
+ code = ~s[**aaaa~**]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[aaaa~
]
+
+ # strikethrought
+ code = ~s[aaaa~]
+ result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+ assert result == ~s[aaaa~
]
+ end
+end
diff --git a/test/plugs/cache_control_test.exs b/test/plugs/cache_control_test.exs
index 005912ffb..6b567e81d 100644
--- a/test/plugs/cache_control_test.exs
+++ b/test/plugs/cache_control_test.exs
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.CacheControlTest do
test "Verify Cache-Control header on static assets", %{conn: conn} do
conn = get(conn, "/index.html")
- assert Conn.get_resp_header(conn, "cache-control") == ["public max-age=86400 must-revalidate"]
+ assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"]
end
test "Verify Cache-Control header on the API", %{conn: conn} do
diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs
index 18be5edd0..7f3559b83 100644
--- a/test/plugs/ensure_authenticated_plug_test.exs
+++ b/test/plugs/ensure_authenticated_plug_test.exs
@@ -8,24 +8,62 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User
- test "it halts if no user is assigned", %{conn: conn} do
- conn =
- conn
- |> EnsureAuthenticatedPlug.call(%{})
+ describe "without :if_func / :unless_func options" do
+ test "it halts if user is NOT assigned", %{conn: conn} do
+ conn = EnsureAuthenticatedPlug.call(conn, %{})
- assert conn.status == 403
- assert conn.halted == true
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+
+ test "it continues if a user is assigned", %{conn: conn} do
+ conn = assign(conn, :user, %User{})
+ ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
+
+ assert ret_conn == conn
+ end
end
- test "it continues if a user is assigned", %{conn: conn} do
- conn =
- conn
- |> assign(:user, %User{})
+ describe "with :if_func / :unless_func options" do
+ setup do
+ %{
+ true_fn: fn -> true end,
+ false_fn: fn -> false end
+ }
+ end
- ret_conn =
- conn
- |> EnsureAuthenticatedPlug.call(%{})
+ test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do
+ conn = assign(conn, :user, %User{})
+ assert EnsureAuthenticatedPlug.call(conn, if_func: true_fn) == conn
+ assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
+ assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
+ assert EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) == conn
+ end
- assert ret_conn == conn
+ test "it continues if a user is NOT assigned but :if_func evaluates to `false`",
+ %{conn: conn, false_fn: false_fn} do
+ assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
+ end
+
+ test "it continues if a user is NOT assigned but :unless_func evaluates to `true`",
+ %{conn: conn, true_fn: true_fn} do
+ assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
+ end
+
+ test "it halts if a user is NOT assigned and :if_func evaluates to `true`",
+ %{conn: conn, true_fn: true_fn} do
+ conn = EnsureAuthenticatedPlug.call(conn, if_func: true_fn)
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+
+ test "it halts if a user is NOT assigned and :unless_func evaluates to `false`",
+ %{conn: conn, false_fn: false_fn} do
+ conn = EnsureAuthenticatedPlug.call(conn, unless_func: false_fn)
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
end
end
diff --git a/test/plugs/oauth_plug_test.exs b/test/plugs/oauth_plug_test.exs
index 8534a5c13..f74c068cd 100644
--- a/test/plugs/oauth_plug_test.exs
+++ b/test/plugs/oauth_plug_test.exs
@@ -38,7 +38,7 @@ test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
assert conn.assigns[:user] == opts[:user]
end
- test "with valid token(downcase) in url parameters, it assings the user", opts do
+ test "with valid token(downcase) in url parameters, it assigns the user", opts do
conn =
:get
|> build_conn("/?access_token=#{opts[:token]}")
diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs
index 8023271e4..81e2009c8 100644
--- a/test/plugs/rate_limiter_test.exs
+++ b/test/plugs/rate_limiter_test.exs
@@ -3,8 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.RateLimiterTest do
- use ExUnit.Case, async: true
- use Plug.Test
+ use Pleroma.Web.ConnCase
alias Pleroma.Config
alias Pleroma.Plugs.RateLimiter
@@ -36,63 +35,44 @@ test "config is required for plug to work" do
|> RateLimiter.init()
|> RateLimiter.action_settings()
end
+ end
- test "it is disabled for localhost" do
- Config.put([:rate_limit, @limiter_name], {1, 1})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1})
- Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
+ test "it is disabled if it remote ip plug is enabled but no remote ip is found" do
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1})
+ assert RateLimiter.disabled?(Plug.Conn.assign(build_conn(), :remote_ip_found, false))
+ end
- assert RateLimiter.disabled?() == true
- end
+ test "it restricts based on config values" do
+ limiter_name = :test_plug_opts
+ scale = 80
+ limit = 5
- test "it is disabled for socket" do
- Config.put([:rate_limit, @limiter_name], {1, 1})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
- Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+ Config.put([:rate_limit, limiter_name], {scale, limit})
- assert RateLimiter.disabled?() == true
- end
-
- test "it is enabled for socket when remote ip is enabled" do
- Config.put([:rate_limit, @limiter_name], {1, 1})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
- Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
-
- assert RateLimiter.disabled?() == false
- end
-
- test "it restricts based on config values" do
- limiter_name = :test_plug_opts
- scale = 80
- limit = 5
-
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
- Config.put([:rate_limit, limiter_name], {scale, limit})
-
- plug_opts = RateLimiter.init(name: limiter_name)
- conn = conn(:get, "/")
-
- for i <- 1..5 do
- conn = RateLimiter.call(conn, plug_opts)
- assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- Process.sleep(10)
- end
+ plug_opts = RateLimiter.init(name: limiter_name)
+ conn = conn(:get, "/")
+ for i <- 1..5 do
conn = RateLimiter.call(conn, plug_opts)
- assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
- assert conn.halted
-
- Process.sleep(50)
-
- conn = conn(:get, "/")
-
- conn = RateLimiter.call(conn, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
-
- refute conn.status == Plug.Conn.Status.code(:too_many_requests)
- refute conn.resp_body
- refute conn.halted
+ assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ Process.sleep(10)
end
+
+ conn = RateLimiter.call(conn, plug_opts)
+ assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+
+ Process.sleep(50)
+
+ conn = conn(:get, "/")
+
+ conn = RateLimiter.call(conn, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+
+ refute conn.status == Plug.Conn.Status.code(:too_many_requests)
+ refute conn.resp_body
+ refute conn.halted
end
describe "options" do
diff --git a/test/reverse_proxy/reverse_proxy_test.exs b/test/reverse_proxy/reverse_proxy_test.exs
index abdfddcb7..c677066b3 100644
--- a/test/reverse_proxy/reverse_proxy_test.exs
+++ b/test/reverse_proxy/reverse_proxy_test.exs
@@ -278,17 +278,6 @@ test "returns 400 on non GET, HEAD requests", %{conn: conn} do
end
describe "cache resp headers" do
- test "returns headers", %{conn: conn} do
- ClientMock
- |> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ ->
- {:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}}
- end)
- |> expect(:stream_body, fn _ -> :done end)
-
- conn = ReverseProxy.call(conn, "/cache/10")
- assert {"cache-control", "public, max-age=10"} in conn.resp_headers
- end
-
test "add cache-control", %{conn: conn} do
ClientMock
|> expect(:request, fn :get, "/cache", _, _, _ ->
@@ -297,7 +286,7 @@ test "add cache-control", %{conn: conn} do
|> expect(:stream_body, fn _ -> :done end)
conn = ReverseProxy.call(conn, "/cache")
- assert {"cache-control", "public"} in conn.resp_headers
+ assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers
end
end
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 0f2e81f9e..064874201 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -26,6 +26,8 @@ defmodule Pleroma.Web.ConnCase do
use Pleroma.Tests.Helpers
import Pleroma.Web.Router.Helpers
+ alias Pleroma.Config
+
# The default endpoint for testing
@endpoint Pleroma.Web.Endpoint
@@ -48,6 +50,28 @@ defp oauth_access(scopes, opts \\ []) do
%{user: user, token: token, conn: conn}
end
+
+ defp ensure_federating_or_authenticated(conn, url, user) do
+ initial_setting = Config.get([:instance, :federating])
+ on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
+
+ Config.put([:instance, :federating], false)
+
+ conn
+ |> get(url)
+ |> response(403)
+
+ conn
+ |> assign(:user, user)
+ |> get(url)
+ |> response(200)
+
+ Config.put([:instance, :federating], true)
+
+ conn
+ |> get(url)
+ |> response(200)
+ end
end
end
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index b2352538a..bd8e0b5cc 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
import Pleroma.Factory
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Delivery
alias Pleroma.Instances
alias Pleroma.Object
@@ -25,9 +26,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
:ok
end
- clear_config_all([:instance, :federating],
- do: Pleroma.Config.put([:instance, :federating], true)
- )
+ clear_config([:instance, :federating]) do
+ Config.put([:instance, :federating], true)
+ end
describe "/relay" do
clear_config([:instance, :allow_relay])
@@ -42,12 +43,21 @@ test "with the relay active, it returns the relay user", %{conn: conn} do
end
test "with the relay disabled, it returns 404", %{conn: conn} do
- Pleroma.Config.put([:instance, :allow_relay], false)
+ Config.put([:instance, :allow_relay], false)
conn
|> get(activity_pub_path(conn, :relay))
|> json_response(404)
- |> assert
+ end
+
+ test "on non-federating instance, it returns 404", %{conn: conn} do
+ Config.put([:instance, :federating], false)
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> get(activity_pub_path(conn, :relay))
+ |> json_response(404)
end
end
@@ -60,6 +70,16 @@ test "it returns the internal fetch user", %{conn: conn} do
assert res["id"] =~ "/fetch"
end
+
+ test "on non-federating instance, it returns 404", %{conn: conn} do
+ Config.put([:instance, :federating], false)
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> get(activity_pub_path(conn, :internal_fetch))
+ |> json_response(404)
+ end
end
describe "/users/:nickname" do
@@ -123,9 +143,34 @@ test "it returns 404 for remote users", %{
assert json_response(conn, 404)
end
+
+ test "it returns error when user is not found", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/users/jimm")
+ |> json_response(404)
+
+ assert response == "Not found"
+ end
+
+ test "it requires authentication if instance is NOT federating", %{
+ conn: conn
+ } do
+ user = insert(:user)
+
+ conn =
+ put_req_header(
+ conn,
+ "accept",
+ "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+ )
+
+ ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
+ end
end
- describe "/object/:uuid" do
+ describe "/objects/:uuid" do
test "it returns a json representation of the object with accept application/json", %{
conn: conn
} do
@@ -236,6 +281,18 @@ test "cached purged after object deletion", %{conn: conn} do
assert "Not found" == json_response(conn2, :not_found)
end
+
+ test "it requires authentication if instance is NOT federating", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ note = insert(:note)
+ uuid = String.split(note.data["id"], "/") |> List.last()
+
+ conn = put_req_header(conn, "accept", "application/activity+json")
+
+ ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
+ end
end
describe "/activities/:uuid" do
@@ -307,6 +364,18 @@ test "cached purged after activity deletion", %{conn: conn} do
assert "Not found" == json_response(conn2, :not_found)
end
+
+ test "it requires authentication if instance is NOT federating", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ activity = insert(:note_activity)
+ uuid = String.split(activity.data["id"], "/") |> List.last()
+
+ conn = put_req_header(conn, "accept", "application/activity+json")
+
+ ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
+ end
end
describe "/inbox" do
@@ -379,6 +448,34 @@ test "accept follow activity", %{conn: conn} do
:ok = Mix.Tasks.Pleroma.Relay.run(["list"])
assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
end
+
+ 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") |> Poison.decode!()
+ non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
+
+ conn = put_req_header(conn, "content-type", "application/activity+json")
+
+ Config.put([:instance, :federating], false)
+
+ conn
+ |> post("/inbox", data)
+ |> json_response(403)
+
+ conn
+ |> post("/inbox", non_create_data)
+ |> json_response(403)
+
+ Config.put([: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
end
describe "/users/:nickname/inbox" do
@@ -517,22 +614,11 @@ test "it accepts messages from actors that are followed by the user", %{
test "it rejects reads from other users", %{conn: conn} do
user = insert(:user)
- otheruser = insert(:user)
-
- conn =
- conn
- |> assign(:user, otheruser)
- |> put_req_header("accept", "application/activity+json")
- |> get("/users/#{user.nickname}/inbox")
-
- assert json_response(conn, 403)
- end
-
- test "it doesn't crash without an authenticated user", %{conn: conn} do
- user = insert(:user)
+ other_user = insert(:user)
conn =
conn
+ |> assign(:user, other_user)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/inbox")
@@ -613,14 +699,30 @@ test "it removes all follower collections but actor's", %{conn: conn} do
refute recipient.follower_address in activity.data["cc"]
refute recipient.follower_address in activity.data["to"]
end
+
+ test "it requires authentication", %{conn: conn} do
+ user = insert(:user)
+ conn = put_req_header(conn, "accept", "application/activity+json")
+
+ ret_conn = get(conn, "/users/#{user.nickname}/inbox")
+ assert json_response(ret_conn, 403)
+
+ ret_conn =
+ conn
+ |> assign(:user, user)
+ |> get("/users/#{user.nickname}/inbox")
+
+ assert json_response(ret_conn, 200)
+ end
end
- describe "/users/:nickname/outbox" do
- test "it will not bomb when there is no activity", %{conn: conn} do
+ describe "GET /users/:nickname/outbox" do
+ test "it returns 200 even if there're no activities", %{conn: conn} do
user = insert(:user)
conn =
conn
+ |> assign(:user, user)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox")
@@ -635,6 +737,7 @@ test "it returns a note activity in a collection", %{conn: conn} do
conn =
conn
+ |> assign(:user, user)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox?page=true")
@@ -647,24 +750,38 @@ test "it returns an announce activity in a collection", %{conn: conn} do
conn =
conn
+ |> assign(:user, user)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox?page=true")
assert response(conn, 200) =~ announce_activity.data["object"]
end
- test "it rejects posts from other users", %{conn: conn} do
+ test "it requires authentication if instance is NOT federating", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ conn = put_req_header(conn, "accept", "application/activity+json")
+
+ ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
+ end
+ end
+
+ describe "POST /users/:nickname/outbox" do
+ test "it rejects posts from other users / unauuthenticated users", %{conn: conn} do
data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
user = insert(:user)
- otheruser = insert(:user)
+ other_user = insert(:user)
+ conn = put_req_header(conn, "content-type", "application/activity+json")
- conn =
- conn
- |> assign(:user, otheruser)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", data)
+ conn
+ |> post("/users/#{user.nickname}/outbox", data)
+ |> json_response(403)
- assert json_response(conn, 403)
+ conn
+ |> assign(:user, other_user)
+ |> post("/users/#{user.nickname}/outbox", data)
+ |> json_response(403)
end
test "it inserts an incoming create activity into the database", %{conn: conn} do
@@ -779,24 +896,42 @@ test "it returns relay followers", %{conn: conn} do
result =
conn
- |> assign(:relay, true)
|> get("/relay/followers")
|> json_response(200)
assert result["first"]["orderedItems"] == [user.ap_id]
end
+
+ test "on non-federating instance, it returns 404", %{conn: conn} do
+ Config.put([:instance, :federating], false)
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> get("/relay/followers")
+ |> json_response(404)
+ end
end
describe "/relay/following" do
test "it returns relay following", %{conn: conn} do
result =
conn
- |> assign(:relay, true)
|> get("/relay/following")
|> json_response(200)
assert result["first"]["orderedItems"] == []
end
+
+ test "on non-federating instance, it returns 404", %{conn: conn} do
+ Config.put([:instance, :federating], false)
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> get("/relay/following")
+ |> json_response(404)
+ end
end
describe "/users/:nickname/followers" do
@@ -807,32 +942,36 @@ test "it returns the followers in a collection", %{conn: conn} do
result =
conn
+ |> assign(:user, user_two)
|> get("/users/#{user_two.nickname}/followers")
|> json_response(200)
assert result["first"]["orderedItems"] == [user.ap_id]
end
- test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do
+ test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, hide_followers: true)
User.follow(user, user_two)
result =
conn
+ |> assign(:user, user)
|> get("/users/#{user_two.nickname}/followers")
|> json_response(200)
assert is_binary(result["first"])
end
- test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",
+ test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
%{conn: conn} do
- user = insert(:user, hide_followers: true)
+ user = insert(:user)
+ other_user = insert(:user, hide_followers: true)
result =
conn
- |> get("/users/#{user.nickname}/followers?page=1")
+ |> assign(:user, user)
+ |> get("/users/#{other_user.nickname}/followers?page=1")
assert result.status == 403
assert result.resp_body == ""
@@ -864,6 +1003,7 @@ test "it works for more than 10 users", %{conn: conn} do
result =
conn
+ |> assign(:user, user)
|> get("/users/#{user.nickname}/followers")
|> json_response(200)
@@ -873,12 +1013,21 @@ test "it works for more than 10 users", %{conn: conn} do
result =
conn
+ |> assign(:user, user)
|> get("/users/#{user.nickname}/followers?page=2")
|> json_response(200)
assert length(result["orderedItems"]) == 5
assert result["totalItems"] == 15
end
+
+ test "returns 403 if requester is not logged in", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> get("/users/#{user.nickname}/followers")
+ |> json_response(403)
+ end
end
describe "/users/:nickname/following" do
@@ -889,6 +1038,7 @@ test "it returns the following in a collection", %{conn: conn} do
result =
conn
+ |> assign(:user, user)
|> get("/users/#{user.nickname}/following")
|> json_response(200)
@@ -896,25 +1046,28 @@ test "it returns the following in a collection", %{conn: conn} do
end
test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
- user = insert(:user, hide_follows: true)
- user_two = insert(:user)
+ user = insert(:user)
+ user_two = insert(:user, hide_follows: true)
User.follow(user, user_two)
result =
conn
- |> get("/users/#{user.nickname}/following")
+ |> assign(:user, user)
+ |> get("/users/#{user_two.nickname}/following")
|> json_response(200)
assert is_binary(result["first"])
end
- test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",
+ test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
%{conn: conn} do
- user = insert(:user, hide_follows: true)
+ user = insert(:user)
+ user_two = insert(:user, hide_follows: true)
result =
conn
- |> get("/users/#{user.nickname}/following?page=1")
+ |> assign(:user, user)
+ |> get("/users/#{user_two.nickname}/following?page=1")
assert result.status == 403
assert result.resp_body == ""
@@ -947,6 +1100,7 @@ test "it works for more than 10 users", %{conn: conn} do
result =
conn
+ |> assign(:user, user)
|> get("/users/#{user.nickname}/following")
|> json_response(200)
@@ -956,12 +1110,21 @@ test "it works for more than 10 users", %{conn: conn} do
result =
conn
+ |> assign(:user, user)
|> get("/users/#{user.nickname}/following?page=2")
|> json_response(200)
assert length(result["orderedItems"]) == 5
assert result["totalItems"] == 15
end
+
+ test "returns 403 if requester is not logged in", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> get("/users/#{user.nickname}/following")
+ |> json_response(403)
+ end
end
describe "delivery tracking" do
@@ -1046,8 +1209,8 @@ test "it tracks a signed activity fetch when the json is cached", %{conn: conn}
end
end
- describe "Additionnal ActivityPub C2S endpoints" do
- test "/api/ap/whoami", %{conn: conn} do
+ describe "Additional ActivityPub C2S endpoints" do
+ test "GET /api/ap/whoami", %{conn: conn} do
user = insert(:user)
conn =
@@ -1058,12 +1221,16 @@ test "/api/ap/whoami", %{conn: conn} do
user = User.get_cached_by_id(user.id)
assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
+
+ conn
+ |> get("/api/ap/whoami")
+ |> json_response(403)
end
clear_config([:media_proxy])
clear_config([Pleroma.Upload])
- test "uploadMedia", %{conn: conn} do
+ test "POST /api/ap/upload_media", %{conn: conn} do
user = insert(:user)
desc = "Description of the image"
@@ -1083,6 +1250,10 @@ test "uploadMedia", %{conn: conn} do
assert object["name"] == desc
assert object["type"] == "Document"
assert object["actor"] == user.ap_id
+
+ conn
+ |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
+ |> json_response(403)
end
end
end
diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs
index 3404848d4..da26b13f7 100644
--- a/test/web/activity_pub/publisher_test.exs
+++ b/test/web/activity_pub/publisher_test.exs
@@ -23,6 +23,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
:ok
end
+ clear_config_all([:instance, :federating]) do
+ Pleroma.Config.put([:instance, :federating], true)
+ end
+
describe "gather_webfinger_links/1" do
test "it returns links" do
user = insert(:user)
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index e5ab54dd4..e913a5148 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -177,71 +177,6 @@ test "does not adress actor's follower address if the activity is not public", %
end
end
- describe "fetch_ordered_collection" do
- import Tesla.Mock
-
- test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do
- mock(fn
- %{method: :get, url: "http://mastodon.com/outbox"} ->
- json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"})
-
- %{method: :get, url: "http://mastodon.com/outbox?page=true"} ->
- json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]})
- end)
-
- assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"]
- end
-
- test "fetches several pages in the right order one after another, but only the specified amount" do
- mock(fn
- %{method: :get, url: "http://example.com/outbox"} ->
- json(%{
- "type" => "OrderedCollectionPage",
- "orderedItems" => [0],
- "next" => "http://example.com/outbox?page=1"
- })
-
- %{method: :get, url: "http://example.com/outbox?page=1"} ->
- json(%{
- "type" => "OrderedCollectionPage",
- "orderedItems" => [1],
- "next" => "http://example.com/outbox?page=2"
- })
-
- %{method: :get, url: "http://example.com/outbox?page=2"} ->
- json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]})
- end)
-
- assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0]
- assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1]
- end
-
- test "returns an error if the url doesn't have an OrderedCollection/Page" do
- mock(fn
- %{method: :get, url: "http://example.com/not-an-outbox"} ->
- json(%{"type" => "NotAnOutbox"})
- end)
-
- assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1)
- end
-
- test "returns the what was collected if there are less pages than specified" do
- mock(fn
- %{method: :get, url: "http://example.com/outbox"} ->
- json(%{
- "type" => "OrderedCollectionPage",
- "orderedItems" => [0],
- "next" => "http://example.com/outbox?page=1"
- })
-
- %{method: :get, url: "http://example.com/outbox?page=1"} ->
- json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]})
- end)
-
- assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
- end
- end
-
test "make_json_ld_header/0" do
assert Utils.make_json_ld_header() == %{
"@context" => [
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 76240e5bc..eb29deaed 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -3048,7 +3048,7 @@ test "pleroma restarts", %{conn: conn} do
end
describe "GET /api/pleroma/admin/statuses" do
- test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: admin} do
+ test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do
blocked = insert(:user)
user = insert(:user)
User.block(admin, blocked)
@@ -3067,7 +3067,7 @@ test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: a
|> json_response(200)
refute "private" in Enum.map(response, & &1["visibility"])
- assert length(response) == 4
+ assert length(response) == 3
end
test "returns only local statuses with local_only on", %{conn: conn} do
@@ -3084,12 +3084,16 @@ test "returns only local statuses with local_only on", %{conn: conn} do
assert length(response) == 1
end
- test "returns private statuses with godmode on", %{conn: conn} do
+ test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do
user = insert(:user)
+
+ {:ok, _} =
+ CommonAPI.post(user, %{"status" => "@#{admin.nickname}", "visibility" => "direct"})
+
{:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
{:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
conn = get(conn, "/api/pleroma/admin/statuses?godmode=true")
- assert json_response(conn, 200) |> length() == 2
+ assert json_response(conn, 200) |> length() == 3
end
end
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index 92ba2b983..d383d1714 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -89,8 +89,8 @@ test "works for bare text/html" do
assert output == expected
- text = "hello world!
\n\nsecond paragraph
"
- expected = "hello world!
\n\nsecond paragraph
"
+ text = "hello world!
\nsecond paragraph
"
+ expected = "hello world!
\nsecond paragraph
"
{output, [], []} = Utils.format_input(text, "text/html")
@@ -99,14 +99,14 @@ test "works for bare text/html" do
test "works for bare text/markdown" do
text = "**hello world**"
- expected = "hello world
\n"
+ expected = "hello world
"
{output, [], []} = Utils.format_input(text, "text/markdown")
assert output == expected
text = "**hello world**\n\n*another paragraph*"
- expected = "hello world
\nanother paragraph
\n"
+ expected = "hello world
another paragraph
"
{output, [], []} = Utils.format_input(text, "text/markdown")
@@ -118,7 +118,7 @@ test "works for bare text/markdown" do
by someone
"""
- expected = "cool quote
\n \nby someone
\n"
+ expected = "cool quote
by someone
"
{output, [], []} = Utils.format_input(text, "text/markdown")
@@ -134,7 +134,7 @@ test "works for bare text/bbcode" do
assert output == expected
text = "[b]hello world![/b]\n\nsecond paragraph!"
- expected = "hello world! \n \nsecond paragraph!"
+ expected = "hello world! second paragraph!"
{output, [], []} = Utils.format_input(text, "text/bbcode")
@@ -143,7 +143,7 @@ test "works for bare text/bbcode" do
text = "[b]hello world![/b]\n\nsecond paragraph! "
expected =
- "hello world! \n \n<strong>second paragraph!</strong>"
+ "hello world! <strong>second paragraph!</strong>"
{output, [], []} = Utils.format_input(text, "text/bbcode")
@@ -156,16 +156,14 @@ test "works for text/markdown with mentions" do
text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"
- expected =
- ~s(hello world
\nanother @user__test and @user__test google.com paragraph
\n)
-
{output, _, _} = Utils.format_input(text, "text/markdown")
- assert output == expected
+ assert output ==
+ ~s(hello world
another @user__test and @user__test google.com paragraph
)
end
end
diff --git a/test/web/feed/user_controller_test.exs b/test/web/feed/user_controller_test.exs
index 19a019060..00c50f003 100644
--- a/test/web/feed/user_controller_test.exs
+++ b/test/web/feed/user_controller_test.exs
@@ -8,222 +8,83 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
import Pleroma.Factory
import SweetXml
+ alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.User
- clear_config([:feed])
-
- test "gets a feed", %{conn: conn} do
- Pleroma.Config.put(
- [:feed, :post_title],
- %{max_length: 10, omission: "..."}
- )
-
- activity = insert(:note_activity)
-
- note =
- insert(:note,
- data: %{
- "content" => "This is :moominmamma: note ",
- "attachment" => [
- %{
- "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}]
- }
- ],
- "inReplyTo" => activity.data["id"]
- }
- )
-
- note_activity = insert(:note_activity, note: note)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- note2 =
- insert(:note,
- user: user,
- data: %{"content" => "42 This is :moominmamma: note ", "inReplyTo" => activity.data["id"]}
- )
-
- _note_activity2 = insert(:note_activity, note: note2)
- object = Object.normalize(note_activity)
-
- resp =
- conn
- |> put_req_header("content-type", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, user.nickname))
- |> response(200)
-
- activity_titles =
- resp
- |> SweetXml.parse()
- |> SweetXml.xpath(~x"//entry/title/text()"l)
-
- assert activity_titles == ['42 This...', 'This is...']
- assert resp =~ object.data["content"]
+ clear_config([:instance, :federating]) do
+ Config.put([:instance, :federating], true)
end
- test "returns 404 for a missing feed", %{conn: conn} do
- conn =
- conn
- |> put_req_header("content-type", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, "nonexisting"))
+ describe "feed" do
+ clear_config([:feed])
- assert response(conn, 404)
+ test "gets a feed", %{conn: conn} do
+ Config.put(
+ [:feed, :post_title],
+ %{max_length: 10, omission: "..."}
+ )
+
+ activity = insert(:note_activity)
+
+ note =
+ insert(:note,
+ data: %{
+ "content" => "This is :moominmamma: note ",
+ "attachment" => [
+ %{
+ "url" => [
+ %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
+ ]
+ }
+ ],
+ "inReplyTo" => activity.data["id"]
+ }
+ )
+
+ note_activity = insert(:note_activity, note: note)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ note2 =
+ insert(:note,
+ user: user,
+ data: %{
+ "content" => "42 This is :moominmamma: note ",
+ "inReplyTo" => activity.data["id"]
+ }
+ )
+
+ _note_activity2 = insert(:note_activity, note: note2)
+ object = Object.normalize(note_activity)
+
+ resp =
+ conn
+ |> put_req_header("content-type", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, user.nickname))
+ |> response(200)
+
+ activity_titles =
+ resp
+ |> SweetXml.parse()
+ |> SweetXml.xpath(~x"//entry/title/text()"l)
+
+ assert activity_titles == ['42 This...', 'This is...']
+ assert resp =~ object.data["content"]
+ end
+
+ test "returns 404 for a missing feed", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("content-type", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, "nonexisting"))
+
+ assert response(conn, 404)
+ end
end
+ # Note: see ActivityPubControllerTest for JSON format tests
describe "feed_redirect" do
- test "undefined format. it redirects to feed", %{conn: conn} do
- note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- response =
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/users/#{user.nickname}")
- |> response(302)
-
- assert response ==
- "You are being redirected ."
- end
-
- test "undefined format. it returns error when user not found", %{conn: conn} do
- response =
- conn
- |> put_req_header("accept", "application/xml")
- |> get(user_feed_path(conn, :feed, "jimm"))
- |> response(404)
-
- assert response == ~S({"error":"Not found"})
- end
-
- test "activity+json format. it redirects on actual feed of user", %{conn: conn} do
- note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- response =
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get("/users/#{user.nickname}")
- |> json_response(200)
-
- assert response["endpoints"] == %{
- "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
- "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
- "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
- "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox",
- "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media"
- }
-
- assert response["@context"] == [
- "https://www.w3.org/ns/activitystreams",
- "http://localhost:4001/schemas/litepub-0.1.jsonld",
- %{"@language" => "und"}
- ]
-
- assert Map.take(response, [
- "followers",
- "following",
- "id",
- "inbox",
- "manuallyApprovesFollowers",
- "name",
- "outbox",
- "preferredUsername",
- "summary",
- "tag",
- "type",
- "url"
- ]) == %{
- "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
- "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
- "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
- "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
- "manuallyApprovesFollowers" => false,
- "name" => user.name,
- "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
- "preferredUsername" => user.nickname,
- "summary" => user.bio,
- "tag" => [],
- "type" => "Person",
- "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
- }
- end
-
- test "activity+json format. it returns error whe use not found", %{conn: conn} do
- response =
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get("/users/jimm")
- |> json_response(404)
-
- assert response == "Not found"
- end
-
- test "json format. it redirects on actual feed of user", %{conn: conn} do
- note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- response =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/users/#{user.nickname}")
- |> json_response(200)
-
- assert response["endpoints"] == %{
- "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
- "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
- "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
- "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox",
- "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media"
- }
-
- assert response["@context"] == [
- "https://www.w3.org/ns/activitystreams",
- "http://localhost:4001/schemas/litepub-0.1.jsonld",
- %{"@language" => "und"}
- ]
-
- assert Map.take(response, [
- "followers",
- "following",
- "id",
- "inbox",
- "manuallyApprovesFollowers",
- "name",
- "outbox",
- "preferredUsername",
- "summary",
- "tag",
- "type",
- "url"
- ]) == %{
- "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
- "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
- "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
- "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
- "manuallyApprovesFollowers" => false,
- "name" => user.name,
- "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
- "preferredUsername" => user.nickname,
- "summary" => user.bio,
- "tag" => [],
- "type" => "Person",
- "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
- }
- end
-
- test "json format. it returns error whe use not found", %{conn: conn} do
- response =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/users/jimm")
- |> json_response(404)
-
- assert response == "Not found"
- end
-
- test "html format. it redirects on actual feed of user", %{conn: conn} do
+ test "with html format, it redirects to user feed", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
@@ -239,7 +100,7 @@ test "html format. it redirects on actual feed of user", %{conn: conn} do
).resp_body
end
- test "html format. it returns error when user not found", %{conn: conn} do
+ test "with html format, it returns error when user is not found", %{conn: conn} do
response =
conn
|> get("/users/jimm")
@@ -247,5 +108,30 @@ test "html format. it returns error when user not found", %{conn: conn} do
assert response == %{"error" => "Not found"}
end
+
+ test "with non-html / non-json format, it redirects to user feed in atom format", %{
+ conn: conn
+ } do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/users/#{user.nickname}")
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "#{Pleroma.Web.base_url()}/users/#{user.nickname}/feed.atom"
+ end
+
+ test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get(user_feed_path(conn, :feed, "jimm"))
+ |> response(404)
+
+ assert response == ~S({"error":"Not found"})
+ end
end
end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 7f7d8cea3..7efccd9c4 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -756,10 +756,6 @@ test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_
end
describe "create account by app / rate limit" do
- clear_config([Pleroma.Plugs.RemoteIp, :enabled]) do
- Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
- end
-
clear_config([:rate_limit, :app_account_creation]) do
Pleroma.Config.put([:rate_limit, :app_account_creation], {10_000, 2})
end
diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs
index f035dfeee..7ac7e4af1 100644
--- a/test/web/media_proxy/media_proxy_controller_test.exs
+++ b/test/web/media_proxy/media_proxy_controller_test.exs
@@ -52,9 +52,8 @@ test "redirects on valid url when filename invalidated", %{conn: conn} do
url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png")
invalid_url = String.replace(url, "test.png", "test-file.png")
response = get(conn, invalid_url)
- html = "You are being redirected ."
assert response.status == 302
- assert response.resp_body == html
+ assert redirected_to(response) == url
end
test "it performs ReverseProxy.call when signature valid", %{conn: conn} do
diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs
index 2051841c2..3b84358e4 100644
--- a/test/web/ostatus/ostatus_controller_test.exs
+++ b/test/web/ostatus/ostatus_controller_test.exs
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
import Pleroma.Factory
+ alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@@ -16,22 +17,24 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
:ok
end
- clear_config_all([:instance, :federating]) do
- Pleroma.Config.put([:instance, :federating], true)
+ clear_config([:instance, :federating]) do
+ Config.put([:instance, :federating], true)
end
- describe "GET object/2" do
+ # Note: see ActivityPubControllerTest for JSON format tests
+ describe "GET /objects/:uuid (text/html)" do
+ setup %{conn: conn} do
+ conn = put_req_header(conn, "accept", "text/html")
+ %{conn: conn}
+ end
+
test "redirects to /notice/id for html format", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get(url)
-
+ conn = get(conn, url)
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
@@ -45,23 +48,25 @@ test "404s on private objects", %{conn: conn} do
|> response(404)
end
- test "404s on nonexisting objects", %{conn: conn} do
+ test "404s on non-existing objects", %{conn: conn} do
conn
|> get("/objects/123")
|> response(404)
end
end
- describe "GET activity/2" do
+ # Note: see ActivityPubControllerTest for JSON format tests
+ describe "GET /activities/:uuid (text/html)" do
+ setup %{conn: conn} do
+ conn = put_req_header(conn, "accept", "text/html")
+ %{conn: conn}
+ end
+
test "redirects to /notice/id for html format", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/activities/#{uuid}")
-
+ conn = get(conn, "/activities/#{uuid}")
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
@@ -79,19 +84,6 @@ test "404s on nonexistent activities", %{conn: conn} do
|> get("/activities/123")
|> response(404)
end
-
- test "gets an activity in AS2 format", %{conn: conn} do
- note_activity = insert(:note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- url = "/activities/#{uuid}"
-
- conn =
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get(url)
-
- assert json_response(conn, 200)
- end
end
describe "GET notice/2" do
@@ -170,7 +162,7 @@ test "404s a private notice", %{conn: conn} do
assert response(conn, 404)
end
- test "404s a nonexisting notice", %{conn: conn} do
+ test "404s a non-existing notice", %{conn: conn} do
url = "/notice/123"
conn =
@@ -179,10 +171,21 @@ test "404s a nonexisting notice", %{conn: conn} do
assert response(conn, 404)
end
+
+ test "it requires authentication if instance is NOT federating", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ note_activity = insert(:note_activity)
+
+ conn = put_req_header(conn, "accept", "text/html")
+
+ ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}", user)
+ end
end
describe "GET /notice/:id/embed_player" do
- test "render embed player", %{conn: conn} do
+ setup do
note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
@@ -204,9 +207,11 @@ test "render embed player", %{conn: conn} do
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
- conn =
- conn
- |> get("/notice/#{note_activity.id}/embed_player")
+ %{note_activity: note_activity}
+ end
+
+ test "renders embed player", %{conn: conn, note_activity: note_activity} do
+ conn = get(conn, "/notice/#{note_activity.id}/embed_player")
assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"]
@@ -272,9 +277,19 @@ test "404s when attachment isn't audio or video", %{conn: conn} do
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
- assert conn
- |> get("/notice/#{note_activity.id}/embed_player")
- |> response(404)
+ conn
+ |> get("/notice/#{note_activity.id}/embed_player")
+ |> response(404)
+ end
+
+ test "it requires authentication if instance is NOT federating", %{
+ conn: conn,
+ note_activity: note_activity
+ } do
+ user = insert(:user)
+ conn = put_req_header(conn, "accept", "text/html")
+
+ ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}/embed_player", user)
end
end
end
diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/web/static_fe/static_fe_controller_test.exs
index 2ce8f9fa3..c3d2ae3b4 100644
--- a/test/web/static_fe/static_fe_controller_test.exs
+++ b/test/web/static_fe/static_fe_controller_test.exs
@@ -1,56 +1,46 @@
defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
use Pleroma.Web.ConnCase
+
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
clear_config_all([:static_fe, :enabled]) do
- Pleroma.Config.put([:static_fe, :enabled], true)
+ Config.put([:static_fe, :enabled], true)
end
- describe "user profile page" do
- test "just the profile as HTML", %{conn: conn} do
- user = insert(:user)
+ clear_config([:instance, :federating]) do
+ Config.put([:instance, :federating], true)
+ end
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/users/#{user.nickname}")
+ setup %{conn: conn} do
+ conn = put_req_header(conn, "accept", "text/html")
+ user = insert(:user)
+
+ %{conn: conn, user: user}
+ end
+
+ describe "user profile html" do
+ test "just the profile as HTML", %{conn: conn, user: user} do
+ conn = get(conn, "/users/#{user.nickname}")
assert html_response(conn, 200) =~ user.nickname
end
- test "renders json unless there's an html accept header", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/users/#{user.nickname}")
-
- assert json_response(conn, 200)
- end
-
test "404 when user not found", %{conn: conn} do
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/users/limpopo")
+ conn = get(conn, "/users/limpopo")
assert html_response(conn, 404) =~ "not found"
end
- test "profile does not include private messages", %{conn: conn} do
- user = insert(:user)
+ test "profile does not include private messages", %{conn: conn, user: user} do
CommonAPI.post(user, %{"status" => "public"})
CommonAPI.post(user, %{"status" => "private", "visibility" => "private"})
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/users/#{user.nickname}")
+ conn = get(conn, "/users/#{user.nickname}")
html = html_response(conn, 200)
@@ -58,14 +48,10 @@ test "profile does not include private messages", %{conn: conn} do
refute html =~ ">private<"
end
- test "pagination", %{conn: conn} do
- user = insert(:user)
+ test "pagination", %{conn: conn, user: user} do
Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end)
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/users/#{user.nickname}")
+ conn = get(conn, "/users/#{user.nickname}")
html = html_response(conn, 200)
@@ -75,15 +61,11 @@ test "pagination", %{conn: conn} do
refute html =~ ">test1<"
end
- test "pagination, page 2", %{conn: conn} do
- user = insert(:user)
+ test "pagination, page 2", %{conn: conn, user: user} do
activities = Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end)
{:ok, a11} = Enum.at(activities, 11)
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/users/#{user.nickname}?max_id=#{a11.id}")
+ conn = get(conn, "/users/#{user.nickname}?max_id=#{a11.id}")
html = html_response(conn, 200)
@@ -92,17 +74,17 @@ test "pagination, page 2", %{conn: conn} do
refute html =~ ">test20<"
refute html =~ ">test29<"
end
+
+ test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
+ ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user)
+ end
end
- describe "notice rendering" do
- test "single notice page", %{conn: conn} do
- user = insert(:user)
+ describe "notice html" do
+ test "single notice page", %{conn: conn, user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "testing a thing!"})
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/notice/#{activity.id}")
+ conn = get(conn, "/notice/#{activity.id}")
html = html_response(conn, 200)
assert html =~ ""
@@ -110,79 +92,68 @@ test "single notice page", %{conn: conn} do
assert html =~ "testing a thing!"
end
- test "shows the whole thread", %{conn: conn} do
+ test "filters HTML tags", %{conn: conn} do
user = insert(:user)
- {:ok, activity} = CommonAPI.post(user, %{"status" => "space: the final frontier"})
-
- CommonAPI.post(user, %{
- "status" => "these are the voyages or something",
- "in_reply_to_status_id" => activity.id
- })
+ {:ok, activity} = CommonAPI.post(user, %{"status" => ""})
conn =
conn
|> put_req_header("accept", "text/html")
|> get("/notice/#{activity.id}")
+ html = html_response(conn, 200)
+ assert html =~ ~s[<script>alert('xss')</script>]
+ end
+
+ test "shows the whole thread", %{conn: conn, user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "space: the final frontier"})
+
+ CommonAPI.post(user, %{
+ "status" => "these are the voyages or something",
+ "in_reply_to_status_id" => activity.id
+ })
+
+ conn = get(conn, "/notice/#{activity.id}")
+
html = html_response(conn, 200)
assert html =~ "the final frontier"
assert html =~ "voyages"
end
- test "redirect by AP object ID", %{conn: conn} do
- user = insert(:user)
-
+ test "redirect by AP object ID", %{conn: conn, user: user} do
{:ok, %Activity{data: %{"object" => object_url}}} =
CommonAPI.post(user, %{"status" => "beam me up"})
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get(URI.parse(object_url).path)
+ conn = get(conn, URI.parse(object_url).path)
assert html_response(conn, 302) =~ "redirected"
end
- test "redirect by activity ID", %{conn: conn} do
- user = insert(:user)
-
+ test "redirect by activity ID", %{conn: conn, user: user} do
{:ok, %Activity{data: %{"id" => id}}} =
CommonAPI.post(user, %{"status" => "I'm a doctor, not a devops!"})
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get(URI.parse(id).path)
+ conn = get(conn, URI.parse(id).path)
assert html_response(conn, 302) =~ "redirected"
end
test "404 when notice not found", %{conn: conn} do
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/notice/88c9c317")
+ conn = get(conn, "/notice/88c9c317")
assert html_response(conn, 404) =~ "not found"
end
- test "404 for private status", %{conn: conn} do
- user = insert(:user)
-
+ test "404 for private status", %{conn: conn, user: user} do
{:ok, activity} =
CommonAPI.post(user, %{"status" => "don't show me!", "visibility" => "private"})
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/notice/#{activity.id}")
+ conn = get(conn, "/notice/#{activity.id}")
assert html_response(conn, 404) =~ "not found"
end
- test "302 for remote cached status", %{conn: conn} do
- user = insert(:user)
-
+ test "302 for remote cached status", %{conn: conn, user: user} do
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => user.follower_address,
@@ -199,12 +170,15 @@ test "302 for remote cached status", %{conn: conn} do
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
- conn =
- conn
- |> put_req_header("accept", "text/html")
- |> get("/notice/#{activity.id}")
+ conn = get(conn, "/notice/#{activity.id}")
assert html_response(conn, 302) =~ "redirected"
end
+
+ test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "testing a thing!"})
+
+ ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user)
+ end
end
end
diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs
index 80a42989d..73062f18f 100644
--- a/test/web/twitter_api/remote_follow_controller_test.exs
+++ b/test/web/twitter_api/remote_follow_controller_test.exs
@@ -5,8 +5,10 @@
defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
use Pleroma.Web.ConnCase
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.CommonAPI
+
import ExUnit.CaptureLog
import Pleroma.Factory
@@ -15,6 +17,10 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
:ok
end
+ clear_config_all([:instance, :federating]) do
+ Config.put([:instance, :federating], true)
+ end
+
clear_config([:instance])
clear_config([:frontend_configurations, :pleroma_fe])
clear_config([:user, :deny_follow_blocked])
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index d464ce215..9d757b5ef 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo
+ alias Pleroma.Config
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
@@ -178,7 +179,7 @@ test "it updates notification privacy option", %{user: user, conn: conn} do
describe "GET /api/statusnet/config" do
test "it returns config in xml format", %{conn: conn} do
- instance = Pleroma.Config.get(:instance)
+ instance = Config.get(:instance)
response =
conn
@@ -195,12 +196,12 @@ test "it returns config in xml format", %{conn: conn} do
end
test "it returns config in json format", %{conn: conn} do
- instance = Pleroma.Config.get(:instance)
- Pleroma.Config.put([:instance, :managed_config], true)
- Pleroma.Config.put([:instance, :registrations_open], false)
- Pleroma.Config.put([:instance, :invites_enabled], true)
- Pleroma.Config.put([:instance, :public], false)
- Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
+ instance = Config.get(:instance)
+ Config.put([:instance, :managed_config], true)
+ Config.put([:instance, :registrations_open], false)
+ Config.put([:instance, :invites_enabled], true)
+ Config.put([:instance, :public], false)
+ Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
response =
conn
@@ -234,7 +235,7 @@ test "it returns config in json format", %{conn: conn} do
end
test "returns the state of safe_dm_mentions flag", %{conn: conn} do
- Pleroma.Config.put([:instance, :safe_dm_mentions], true)
+ Config.put([:instance, :safe_dm_mentions], true)
response =
conn
@@ -243,7 +244,7 @@ test "returns the state of safe_dm_mentions flag", %{conn: conn} do
assert response["site"]["safeDMMentionsEnabled"] == "1"
- Pleroma.Config.put([:instance, :safe_dm_mentions], false)
+ Config.put([:instance, :safe_dm_mentions], false)
response =
conn
@@ -254,8 +255,8 @@ test "returns the state of safe_dm_mentions flag", %{conn: conn} do
end
test "it returns the managed config", %{conn: conn} do
- Pleroma.Config.put([:instance, :managed_config], false)
- Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
+ Config.put([:instance, :managed_config], false)
+ Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
response =
conn
@@ -264,7 +265,7 @@ test "it returns the managed config", %{conn: conn} do
refute response["site"]["pleromafe"]
- Pleroma.Config.put([:instance, :managed_config], true)
+ Config.put([:instance, :managed_config], true)
response =
conn
@@ -287,7 +288,7 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d
}
]
- Pleroma.Config.put(:frontend_configurations, config)
+ Config.put(:frontend_configurations, config)
response =
conn
@@ -320,7 +321,7 @@ test "returns json with custom emoji with tags", %{conn: conn} do
clear_config([:instance, :healthcheck])
test "returns 503 when healthcheck disabled", %{conn: conn} do
- Pleroma.Config.put([:instance, :healthcheck], false)
+ Config.put([:instance, :healthcheck], false)
response =
conn
@@ -331,7 +332,7 @@ test "returns 503 when healthcheck disabled", %{conn: conn} do
end
test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do
- Pleroma.Config.put([:instance, :healthcheck], true)
+ Config.put([:instance, :healthcheck], true)
with_mock Pleroma.Healthcheck,
system_info: fn -> %Pleroma.Healthcheck{healthy: true} end do
@@ -351,7 +352,7 @@ test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do
end
test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do
- Pleroma.Config.put([:instance, :healthcheck], true)
+ Config.put([:instance, :healthcheck], true)
with_mock Pleroma.Healthcheck,
system_info: fn -> %Pleroma.Healthcheck{healthy: false} end do
@@ -426,6 +427,10 @@ test "it returns version in json format", %{conn: conn} do
end
describe "POST /main/ostatus - remote_subscribe/2" do
+ clear_config([:instance, :federating]) do
+ Config.put([:instance, :federating], true)
+ end
+
test "renders subscribe form", %{conn: conn} do
user = insert(:user)