From d0eb43b58b0a191b727360cf4523329d2dc60adc Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 16 Jul 2020 22:19:17 -0500
Subject: [PATCH 01/14] Add account aliases

---
 docs/API/pleroma_api.md                       | 20 ++++++++
 lib/pleroma/user.ex                           | 24 ++++++++++
 .../operations/pleroma_account_operation.ex   | 46 +++++++++++++++++++
 lib/pleroma/web/api_spec/schemas/account.ex   |  2 +
 .../web/mastodon_api/views/account_view.ex    |  1 +
 .../controllers/account_controller.ex         | 25 ++++++++++
 lib/pleroma/web/router.ex                     |  3 ++
 lib/pleroma/web/web_finger/web_finger.ex      |  9 +++-
 .../20200717025041_add_aliases_to_users.exs   |  9 ++++
 test/user_test.exs                            | 37 +++++++++++++++
 .../mastodon_api/views/account_view_test.exs  |  5 +-
 .../controllers/account_controller_test.exs   | 29 ++++++++++++
 .../web_finger/web_finger_controller_test.exs | 14 +++++-
 13 files changed, 220 insertions(+), 4 deletions(-)
 create mode 100644 priv/repo/migrations/20200717025041_add_aliases_to_users.exs

diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index 5bd38ad36..8a937fdfd 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -570,3 +570,23 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
   {"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}
 ]
 ```
+
+# Account aliases
+
+Set and delete ActivityPub aliases for follower move.
+
+## `POST /api/v1/pleroma/accounts/ap_aliases`
+### Add account aliases
+* Method: `POST`
+* Authentication: required
+* Params:
+  * `aliases`: array of ActivityPub IDs to add
+* Response: JSON, the user's account
+
+## `DELETE /api/v1/pleroma/accounts/ap_aliases`
+### Delete account aliases
+* Method: `DELETE`
+* Authentication: required
+* Params:
+  * `aliases`: array of ActivityPub IDs to delete
+* Response: JSON, the user's account
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9240e912d..9b756c9a0 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -89,6 +89,7 @@ defmodule Pleroma.User do
     field(:keys, :string)
     field(:public_key, :string)
     field(:ap_id, :string)
+    field(:ap_aliases, {:array, :string}, default: [])
     field(:avatar, :map, default: %{})
     field(:local, :boolean, default: true)
     field(:follower_address, :string)
@@ -2268,4 +2269,27 @@ defmodule Pleroma.User do
     |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
     |> Map.put(:fields, fields)
   end
+
+  def add_aliases(%User{} = user, aliases) when is_list(aliases) do
+    alias_set =
+      (user.ap_aliases ++ aliases)
+      |> MapSet.new()
+      |> MapSet.to_list()
+
+    user
+    |> change(%{ap_aliases: alias_set})
+    |> Repo.update()
+  end
+
+  def delete_aliases(%User{} = user, aliases) when is_list(aliases) do
+    alias_set =
+      user.ap_aliases
+      |> MapSet.new()
+      |> MapSet.difference(MapSet.new(aliases))
+      |> MapSet.to_list()
+
+    user
+    |> change(%{ap_aliases: alias_set})
+    |> Repo.update()
+  end
 end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
index 97836b2eb..1040f6e20 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
@@ -4,6 +4,8 @@
 
 defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
   alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.ApiError
   alias Pleroma.Web.ApiSpec.Schemas.FlakeID
@@ -87,10 +89,54 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
     }
   end
 
+  def add_aliases_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Add ActivityPub aliases",
+      operationId: "PleromaAPI.AccountController.add_aliases",
+      requestBody: request_body("Parameters", alias_request(), required: true),
+      security: [%{"oAuth" => ["write:accounts"]}],
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def delete_aliases_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Delete ActivityPub aliases",
+      operationId: "PleromaAPI.AccountController.delete_aliases",
+      requestBody: request_body("Parameters", alias_request(), required: true),
+      security: [%{"oAuth" => ["write:accounts"]}],
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account)
+      }
+    }
+  end
+
   defp id_param do
     Operation.parameter(:id, :path, FlakeID, "Account ID",
       example: "9umDrYheeY451cQnEe",
       required: true
     )
   end
+
+  defp alias_request do
+    %Schema{
+      title: "AccountAliasRequest",
+      description: "POST body for adding/deleting AP aliases",
+      type: :object,
+      properties: %{
+        aliases: %Schema{
+          type: :array,
+          items: %Schema{type: :string}
+        }
+      },
+      example: %{
+        "aliases" => ["https://beepboop.social/users/beep", "https://mushroom.kingdom/users/toad"]
+      }
+    }
+  end
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index ca79f0747..4fd27edf5 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -40,6 +40,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
       pleroma: %Schema{
         type: :object,
         properties: %{
+          ap_id: %Schema{type: :string},
+          ap_aliases: %Schema{type: :array, items: %Schema{type: :string}},
           allow_following_move: %Schema{
             type: :boolean,
             description: "whether the user allows automatically follow moved following accounts"
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index bc9745044..e2912031a 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -248,6 +248,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       # Pleroma extension
       pleroma: %{
         ap_id: user.ap_id,
+        ap_aliases: user.ap_aliases,
         confirmation_pending: user.confirmation_pending,
         tags: user.tags,
         hide_followers_count: user.hide_followers_count,
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 563edded7..03e5781a3 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -39,6 +39,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
     %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
   )
 
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write:accounts"]} when action in [:add_aliases, :delete_aliases]
+  )
+
   plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
 
   plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
@@ -107,4 +112,24 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
     end
   end
+
+  @doc "POST /api/v1/pleroma/accounts/ap_aliases"
+  def add_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params)
+      when is_list(aliases) do
+    with {:ok, user} <- User.add_aliases(user, aliases) do
+      render(conn, "show.json", user: user)
+    else
+      {:error, message} -> json_response(conn, :forbidden, %{error: message})
+    end
+  end
+
+  @doc "DELETE /api/v1/pleroma/accounts/ap_aliases"
+  def delete_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params)
+      when is_list(aliases) do
+    with {:ok, user} <- User.delete_aliases(user, aliases) do
+      render(conn, "show.json", user: user)
+    else
+      {:error, message} -> json_response(conn, :forbidden, %{error: message})
+    end
+  end
 end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 386308362..dea95cd77 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -344,6 +344,9 @@ defmodule Pleroma.Web.Router do
 
       post("/accounts/:id/subscribe", AccountController, :subscribe)
       post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
+
+      post("/accounts/ap_aliases", AccountController, :add_aliases)
+      delete("/accounts/ap_aliases", AccountController, :delete_aliases)
     end
 
     post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index 71ccf251a..fb142ce8d 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -58,12 +58,19 @@ defmodule Pleroma.Web.WebFinger do
     ] ++ Publisher.gather_webfinger_links(user)
   end
 
+  defp gather_aliases(%User{} = user) do
+    user.ap_aliases
+    |> MapSet.new()
+    |> MapSet.put(user.ap_id)
+    |> MapSet.to_list()
+  end
+
   def represent_user(user, "JSON") do
     {:ok, user} = User.ensure_keys_present(user)
 
     %{
       "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
-      "aliases" => [user.ap_id],
+      "aliases" => gather_aliases(user),
       "links" => gather_links(user)
     }
   end
diff --git a/priv/repo/migrations/20200717025041_add_aliases_to_users.exs b/priv/repo/migrations/20200717025041_add_aliases_to_users.exs
new file mode 100644
index 000000000..a6ace6e0f
--- /dev/null
+++ b/priv/repo/migrations/20200717025041_add_aliases_to_users.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddAliasesToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add(:ap_aliases, {:array, :string}, default: [])
+    end
+  end
+end
diff --git a/test/user_test.exs b/test/user_test.exs
index 9788e09d9..db6e4872e 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1858,4 +1858,41 @@ defmodule Pleroma.UserTest do
 
     assert User.avatar_url(user, no_default: true) == nil
   end
+
+  test "add_aliases/2" do
+    user = insert(:user)
+
+    aliases = [
+      "https://gleasonator.com/users/alex",
+      "https://gleasonator.com/users/alex",
+      "https://animalliberation.social/users/alex"
+    ]
+
+    {:ok, user} = User.add_aliases(user, aliases)
+
+    assert user.ap_aliases == [
+             "https://animalliberation.social/users/alex",
+             "https://gleasonator.com/users/alex"
+           ]
+  end
+
+  test "delete_aliases/2" do
+    user =
+      insert(:user,
+        ap_aliases: [
+          "https://animalliberation.social/users/alex",
+          "https://benis.social/users/benis",
+          "https://gleasonator.com/users/alex"
+        ]
+      )
+
+    aliases = ["https://benis.social/users/benis"]
+
+    {:ok, user} = User.delete_aliases(user, aliases)
+
+    assert user.ap_aliases == [
+             "https://animalliberation.social/users/alex",
+             "https://gleasonator.com/users/alex"
+           ]
+  end
 end
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index a83bf90a3..4a0512e68 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -37,7 +37,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
           "<script src=\"invalid-html\"></script><span>valid html</span>. a<br>b<br/>c<br >d<br />f '&<>\"",
         inserted_at: ~N[2017-08-15 15:47:06.597036],
         emoji: %{"karjalanpiirakka" => "/file.png"},
-        raw_bio: "valid html. a\nb\nc\nd\nf '&<>\""
+        raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"",
+        ap_aliases: ["https://shitposter.zone/users/shp"]
       })
 
     expected = %{
@@ -77,6 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       },
       pleroma: %{
         ap_id: user.ap_id,
+        ap_aliases: ["https://shitposter.zone/users/shp"],
         background_image: "https://example.com/images/asuka_hospital.png",
         favicon:
           "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
@@ -171,6 +173,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       },
       pleroma: %{
         ap_id: user.ap_id,
+        ap_aliases: [],
         background_image: nil,
         favicon:
           "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs
index 07909d48b..da01a8218 100644
--- a/test/web/pleroma_api/controllers/account_controller_test.exs
+++ b/test/web/pleroma_api/controllers/account_controller_test.exs
@@ -281,4 +281,33 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
     end
   end
+
+  describe "aliases controllers" do
+    setup do: oauth_access(["write:accounts"])
+
+    test "adds aliases", %{conn: conn} do
+      aliases = ["https://gleasonator.com/users/alex"]
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases})
+
+      assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200)
+      assert Enum.count(res) == 1
+    end
+
+    test "deletes aliases", %{conn: conn, user: user} do
+      aliases = ["https://gleasonator.com/users/alex"]
+      User.add_aliases(user, aliases)
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> delete("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases})
+
+      assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200)
+      assert Enum.count(res) == 0
+    end
+  end
 end
diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs
index 0023f1e81..50b6c4b3e 100644
--- a/test/web/web_finger/web_finger_controller_test.exs
+++ b/test/web/web_finger/web_finger_controller_test.exs
@@ -30,14 +30,24 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
   end
 
   test "Webfinger JRD" do
-    user = insert(:user)
+    user =
+      insert(:user,
+        ap_id: "https://hyrule.world/users/zelda",
+        ap_aliases: ["https://mushroom.kingdom/users/toad"]
+      )
 
     response =
       build_conn()
       |> put_req_header("accept", "application/jrd+json")
       |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
+      |> json_response(200)
 
-    assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost"
+    assert response["subject"] == "acct:#{user.nickname}@localhost"
+
+    assert response["aliases"] == [
+             "https://hyrule.world/users/zelda",
+             "https://mushroom.kingdom/users/toad"
+           ]
   end
 
   test "it returns 404 when user isn't found (JSON)" do

From bd1e2e3a58ebd702306e7a6e2df985ac07e5f7d8 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 17 Jul 2020 19:11:28 -0500
Subject: [PATCH 02/14] Validate alias IDs

---
 CHANGELOG.md        |  1 +
 lib/pleroma/user.ex | 13 +++++++++++++
 test/user_test.exs  |  7 +++++++
 3 files changed, 21 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a02f28241..ef3235804 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -63,6 +63,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Support pagination in emoji packs API (for packs and for files in pack)
 - Support for viewing instances favicons next to posts and accounts
 - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
+- Ability to set ActivityPub aliases for follower migration.
 
 <details>
   <summary>API Changes</summary>
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9b756c9a0..66664235b 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -47,6 +47,8 @@ defmodule Pleroma.User do
 
   # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
   @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
+  # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
+  @url_regex ~r/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/
 
   @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
   @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
@@ -2278,6 +2280,7 @@ defmodule Pleroma.User do
 
     user
     |> change(%{ap_aliases: alias_set})
+    |> validate_ap_aliases()
     |> Repo.update()
   end
 
@@ -2290,6 +2293,16 @@ defmodule Pleroma.User do
 
     user
     |> change(%{ap_aliases: alias_set})
+    |> validate_ap_aliases()
     |> Repo.update()
   end
+
+  defp validate_ap_aliases(changeset) do
+    validate_change(changeset, :ap_aliases, fn :ap_aliases, ap_aliases ->
+      case Enum.all?(ap_aliases, fn a -> Regex.match?(@url_regex, a) end) do
+        true -> []
+        false -> [ap_aliases: "Invalid ap_id format. Must be a URL."]
+      end
+    end)
+  end
 end
diff --git a/test/user_test.exs b/test/user_test.exs
index db6e4872e..29855b9cd 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1876,6 +1876,13 @@ defmodule Pleroma.UserTest do
            ]
   end
 
+  test "add_aliases/2 with invalid alias" do
+    user = insert(:user)
+    {:error, _} = User.add_aliases(user, ["invalid_alias"])
+    {:error, _} = User.add_aliases(user, ["http://still_invalid"])
+    {:error, _} = User.add_aliases(user, ["http://validalias.com/users/dude", "invalid_alias"])
+  end
+
   test "delete_aliases/2" do
     user =
       insert(:user,

From 4af1b803811cbb59d41f0149706d6dda340b4755 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 7 Aug 2020 16:48:03 -0500
Subject: [PATCH 03/14] Clean up account aliases

---
 docs/API/differences_in_mastoapi_responses.md |  1 +
 docs/API/pleroma_api.md                       | 20 --------
 lib/pleroma/user.ex                           | 37 +++------------
 .../api_spec/operations/account_operation.ex  |  7 +++
 .../operations/pleroma_account_operation.ex   | 46 -------------------
 lib/pleroma/web/api_spec/schemas/account.ex   |  2 +-
 .../controllers/account_controller.ex         |  2 +
 .../web/mastodon_api/views/account_view.ex    |  2 +-
 .../controllers/account_controller.ex         | 25 ----------
 lib/pleroma/web/router.ex                     |  3 --
 lib/pleroma/web/web_finger/web_finger.ex      | 14 +++---
 .../20200717025041_add_aliases_to_users.exs   |  9 ----
 test/user_test.exs                            | 44 ------------------
 .../update_credentials_test.exs               | 10 ++++
 .../mastodon_api/views/account_view_test.exs  |  6 +--
 .../controllers/account_controller_test.exs   | 29 ------------
 .../web_finger/web_finger_controller_test.exs | 12 +++--
 17 files changed, 47 insertions(+), 222 deletions(-)
 delete mode 100644 priv/repo/migrations/20200717025041_add_aliases_to_users.exs

diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 38865dc68..3cb2183bd 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -184,6 +184,7 @@ Additional parameters can be added to the JSON body/Form data:
 - `pleroma_settings_store` - Opaque user settings to be saved on the backend.
 - `skip_thread_containment` - if true, skip filtering out broken threads
 - `allow_following_move` - if true, allows automatically follow moved following accounts
+- `also_known_as` - array of ActivityPub IDs, needed for following move
 - `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
 - `discoverable` - if true, discovery of this account in search results and other services is allowed.
 - `actor_type` - the type of this account.
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index c1aa4d204..4e97d26c0 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -570,23 +570,3 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
   {"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}
 ]
 ```
-
-# Account aliases
-
-Set and delete ActivityPub aliases for follower move.
-
-## `POST /api/v1/pleroma/accounts/ap_aliases`
-### Add account aliases
-* Method: `POST`
-* Authentication: required
-* Params:
-  * `aliases`: array of ActivityPub IDs to add
-* Response: JSON, the user's account
-
-## `DELETE /api/v1/pleroma/accounts/ap_aliases`
-### Delete account aliases
-* Method: `DELETE`
-* Authentication: required
-* Params:
-  * `aliases`: array of ActivityPub IDs to delete
-* Response: JSON, the user's account
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index ad7a04f62..57e06bd5a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -96,7 +96,6 @@ defmodule Pleroma.User do
     field(:keys, :string)
     field(:public_key, :string)
     field(:ap_id, :string)
-    field(:ap_aliases, {:array, :string}, default: [])
     field(:avatar, :map, default: %{})
     field(:local, :boolean, default: true)
     field(:follower_address, :string)
@@ -486,6 +485,7 @@ defmodule Pleroma.User do
         :hide_follows_count,
         :hide_favorites,
         :allow_following_move,
+        :also_known_as,
         :background,
         :show_role,
         :skip_thread_containment,
@@ -494,12 +494,12 @@ defmodule Pleroma.User do
         :pleroma_settings_store,
         :discoverable,
         :actor_type,
-        :also_known_as,
         :accepts_chat_messages
       ]
     )
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
+    |> validate_also_known_as()
     |> validate_length(:bio, max: bio_limit)
     |> validate_length(:name, min: 1, max: name_limit)
     |> validate_inclusion(:actor_type, ["Person", "Service"])
@@ -2387,36 +2387,11 @@ defmodule Pleroma.User do
     |> Map.put(:fields, fields)
   end
 
-  def add_aliases(%User{} = user, aliases) when is_list(aliases) do
-    alias_set =
-      (user.ap_aliases ++ aliases)
-      |> MapSet.new()
-      |> MapSet.to_list()
-
-    user
-    |> change(%{ap_aliases: alias_set})
-    |> validate_ap_aliases()
-    |> Repo.update()
-  end
-
-  def delete_aliases(%User{} = user, aliases) when is_list(aliases) do
-    alias_set =
-      user.ap_aliases
-      |> MapSet.new()
-      |> MapSet.difference(MapSet.new(aliases))
-      |> MapSet.to_list()
-
-    user
-    |> change(%{ap_aliases: alias_set})
-    |> validate_ap_aliases()
-    |> Repo.update()
-  end
-
-  defp validate_ap_aliases(changeset) do
-    validate_change(changeset, :ap_aliases, fn :ap_aliases, ap_aliases ->
-      case Enum.all?(ap_aliases, fn a -> Regex.match?(@url_regex, a) end) do
+  defp validate_also_known_as(changeset) do
+    validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as ->
+      case Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do
         true -> []
-        false -> [ap_aliases: "Invalid ap_id format. Must be a URL."]
+        false -> [also_known_as: "Invalid ap_id format. Must be a URL."]
       end
     end)
   end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index aaebc9b5c..91b4d0c80 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -597,6 +597,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           nullable: true,
           description: "Allows automatically follow moved following accounts"
         },
+        also_known_as: %Schema{
+          type: :array,
+          items: %Schema{type: :string},
+          nullable: true,
+          description: "List of alternate ActivityPub IDs"
+        },
         pleroma_background_image: %Schema{
           type: :string,
           nullable: true,
@@ -627,6 +633,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
         skip_thread_containment: false,
         allow_following_move: false,
+        also_known_as: ["https://foo.bar/users/foo"],
         discoverable: false,
         actor_type: "Person"
       }
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
index 1040f6e20..97836b2eb 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
@@ -4,8 +4,6 @@
 
 defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
   alias OpenApiSpex.Operation
-  alias OpenApiSpex.Schema
-  alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.ApiError
   alias Pleroma.Web.ApiSpec.Schemas.FlakeID
@@ -89,54 +87,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
     }
   end
 
-  def add_aliases_operation do
-    %Operation{
-      tags: ["Accounts"],
-      summary: "Add ActivityPub aliases",
-      operationId: "PleromaAPI.AccountController.add_aliases",
-      requestBody: request_body("Parameters", alias_request(), required: true),
-      security: [%{"oAuth" => ["write:accounts"]}],
-      responses: %{
-        200 => Operation.response("Account", "application/json", Account),
-        403 => Operation.response("Forbidden", "application/json", ApiError)
-      }
-    }
-  end
-
-  def delete_aliases_operation do
-    %Operation{
-      tags: ["Accounts"],
-      summary: "Delete ActivityPub aliases",
-      operationId: "PleromaAPI.AccountController.delete_aliases",
-      requestBody: request_body("Parameters", alias_request(), required: true),
-      security: [%{"oAuth" => ["write:accounts"]}],
-      responses: %{
-        200 => Operation.response("Account", "application/json", Account)
-      }
-    }
-  end
-
   defp id_param do
     Operation.parameter(:id, :path, FlakeID, "Account ID",
       example: "9umDrYheeY451cQnEe",
       required: true
     )
   end
-
-  defp alias_request do
-    %Schema{
-      title: "AccountAliasRequest",
-      description: "POST body for adding/deleting AP aliases",
-      type: :object,
-      properties: %{
-        aliases: %Schema{
-          type: :array,
-          items: %Schema{type: :string}
-        }
-      },
-      example: %{
-        "aliases" => ["https://beepboop.social/users/beep", "https://mushroom.kingdom/users/toad"]
-      }
-    }
-  end
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 4fd27edf5..cf743932c 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
         type: :object,
         properties: %{
           ap_id: %Schema{type: :string},
-          ap_aliases: %Schema{type: :array, items: %Schema{type: :string}},
+          also_known_as: %Schema{type: :array, items: %Schema{type: :string}},
           allow_following_move: %Schema{
             type: :boolean,
             description: "whether the user allows automatically follow moved following accounts"
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index f45678184..b0ec97d87 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -186,6 +186,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         :show_role,
         :skip_thread_containment,
         :allow_following_move,
+        :also_known_as,
         :discoverable,
         :accepts_chat_messages
       ]
@@ -210,6 +211,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
       end)
       |> Maps.put_if_present(:actor_type, params[:actor_type])
+      |> Maps.put_if_present(:also_known_as, params[:also_known_as])
 
     # What happens here:
     #
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 4f29a80fb..f78b04565 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -267,7 +267,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       # Pleroma extension
       pleroma: %{
         ap_id: user.ap_id,
-        ap_aliases: user.ap_aliases,
+        also_known_as: user.also_known_as,
         confirmation_pending: user.confirmation_pending,
         tags: user.tags,
         hide_followers_count: user.hide_followers_count,
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 03e5781a3..563edded7 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -39,11 +39,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
     %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
   )
 
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:accounts"]} when action in [:add_aliases, :delete_aliases]
-  )
-
   plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
 
   plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
@@ -112,24 +107,4 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
     end
   end
-
-  @doc "POST /api/v1/pleroma/accounts/ap_aliases"
-  def add_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params)
-      when is_list(aliases) do
-    with {:ok, user} <- User.add_aliases(user, aliases) do
-      render(conn, "show.json", user: user)
-    else
-      {:error, message} -> json_response(conn, :forbidden, %{error: message})
-    end
-  end
-
-  @doc "DELETE /api/v1/pleroma/accounts/ap_aliases"
-  def delete_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params)
-      when is_list(aliases) do
-    with {:ok, user} <- User.delete_aliases(user, aliases) do
-      render(conn, "show.json", user: user)
-    else
-      {:error, message} -> json_response(conn, :forbidden, %{error: message})
-    end
-  end
 end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index fbab0fc27..c6433cc53 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -345,9 +345,6 @@ defmodule Pleroma.Web.Router do
 
       post("/accounts/:id/subscribe", AccountController, :subscribe)
       post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
-
-      post("/accounts/ap_aliases", AccountController, :add_aliases)
-      delete("/accounts/ap_aliases", AccountController, :delete_aliases)
     end
 
     post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index fb142ce8d..b0df356a3 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -59,10 +59,7 @@ defmodule Pleroma.Web.WebFinger do
   end
 
   defp gather_aliases(%User{} = user) do
-    user.ap_aliases
-    |> MapSet.new()
-    |> MapSet.put(user.ap_id)
-    |> MapSet.to_list()
+    [user.ap_id] ++ user.also_known_as
   end
 
   def represent_user(user, "JSON") do
@@ -78,6 +75,10 @@ defmodule Pleroma.Web.WebFinger do
   def represent_user(user, "XML") do
     {:ok, user} = User.ensure_keys_present(user)
 
+    aliases =
+      gather_aliases(user)
+      |> Enum.map(fn the_alias -> {:Alias, the_alias} end)
+
     links =
       gather_links(user)
       |> Enum.map(fn link -> {:Link, link} end)
@@ -86,9 +87,8 @@ defmodule Pleroma.Web.WebFinger do
       :XRD,
       %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
       [
-        {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
-        {:Alias, user.ap_id}
-      ] ++ links
+        {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"}
+      ] ++ aliases ++ links
     }
     |> XmlBuilder.to_doc()
   end
diff --git a/priv/repo/migrations/20200717025041_add_aliases_to_users.exs b/priv/repo/migrations/20200717025041_add_aliases_to_users.exs
deleted file mode 100644
index a6ace6e0f..000000000
--- a/priv/repo/migrations/20200717025041_add_aliases_to_users.exs
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Pleroma.Repo.Migrations.AddAliasesToUsers do
-  use Ecto.Migration
-
-  def change do
-    alter table(:users) do
-      add(:ap_aliases, {:array, :string}, default: [])
-    end
-  end
-end
diff --git a/test/user_test.exs b/test/user_test.exs
index 941e48408..b47405895 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -2024,48 +2024,4 @@ defmodule Pleroma.UserTest do
 
     assert User.avatar_url(user, no_default: true) == nil
   end
-
-  test "add_aliases/2" do
-    user = insert(:user)
-
-    aliases = [
-      "https://gleasonator.com/users/alex",
-      "https://gleasonator.com/users/alex",
-      "https://animalliberation.social/users/alex"
-    ]
-
-    {:ok, user} = User.add_aliases(user, aliases)
-
-    assert user.ap_aliases == [
-             "https://animalliberation.social/users/alex",
-             "https://gleasonator.com/users/alex"
-           ]
-  end
-
-  test "add_aliases/2 with invalid alias" do
-    user = insert(:user)
-    {:error, _} = User.add_aliases(user, ["invalid_alias"])
-    {:error, _} = User.add_aliases(user, ["http://still_invalid"])
-    {:error, _} = User.add_aliases(user, ["http://validalias.com/users/dude", "invalid_alias"])
-  end
-
-  test "delete_aliases/2" do
-    user =
-      insert(:user,
-        ap_aliases: [
-          "https://animalliberation.social/users/alex",
-          "https://benis.social/users/benis",
-          "https://gleasonator.com/users/alex"
-        ]
-      )
-
-    aliases = ["https://benis.social/users/benis"]
-
-    {:ok, user} = User.delete_aliases(user, aliases)
-
-    assert user.ap_aliases == [
-             "https://animalliberation.social/users/alex",
-             "https://gleasonator.com/users/alex"
-           ]
-  end
 end
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index b888e4c71..467110f3b 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -216,6 +216,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert user_data["display_name"] == "markorepairs"
     end
 
+    test "updates the user's AKAs", %{conn: conn} do
+      conn =
+        patch(conn, "/api/v1/accounts/update_credentials", %{
+          "also_known_as" => ["https://mushroom.kingdom/users/mario"]
+        })
+
+      assert user_data = json_response_and_validate_schema(conn, 200)
+      assert user_data["pleroma"]["also_known_as"] == ["https://mushroom.kingdom/users/mario"]
+    end
+
     test "updates the user's avatar", %{user: user, conn: conn} do
       new_avatar = %Plug.Upload{
         content_type: "image/jpg",
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index a55b5a06e..bbf7b33a8 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
         inserted_at: ~N[2017-08-15 15:47:06.597036],
         emoji: %{"karjalanpiirakka" => "/file.png"},
         raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"",
-        ap_aliases: ["https://shitposter.zone/users/shp"]
+        also_known_as: ["https://shitposter.zone/users/shp"]
       })
 
     expected = %{
@@ -78,7 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       },
       pleroma: %{
         ap_id: user.ap_id,
-        ap_aliases: ["https://shitposter.zone/users/shp"],
+        also_known_as: ["https://shitposter.zone/users/shp"],
         background_image: "https://example.com/images/asuka_hospital.png",
         favicon:
           "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
@@ -174,7 +174,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       },
       pleroma: %{
         ap_id: user.ap_id,
-        ap_aliases: [],
+        also_known_as: [],
         background_image: nil,
         favicon:
           "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs
index da01a8218..07909d48b 100644
--- a/test/web/pleroma_api/controllers/account_controller_test.exs
+++ b/test/web/pleroma_api/controllers/account_controller_test.exs
@@ -281,33 +281,4 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
     end
   end
-
-  describe "aliases controllers" do
-    setup do: oauth_access(["write:accounts"])
-
-    test "adds aliases", %{conn: conn} do
-      aliases = ["https://gleasonator.com/users/alex"]
-
-      conn =
-        conn
-        |> put_req_header("content-type", "application/json")
-        |> post("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases})
-
-      assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200)
-      assert Enum.count(res) == 1
-    end
-
-    test "deletes aliases", %{conn: conn, user: user} do
-      aliases = ["https://gleasonator.com/users/alex"]
-      User.add_aliases(user, aliases)
-
-      conn =
-        conn
-        |> put_req_header("content-type", "application/json")
-        |> delete("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases})
-
-      assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200)
-      assert Enum.count(res) == 0
-    end
-  end
 end
diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs
index 50b6c4b3e..ce9eb0650 100644
--- a/test/web/web_finger/web_finger_controller_test.exs
+++ b/test/web/web_finger/web_finger_controller_test.exs
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
     user =
       insert(:user,
         ap_id: "https://hyrule.world/users/zelda",
-        ap_aliases: ["https://mushroom.kingdom/users/toad"]
+        also_known_as: ["https://mushroom.kingdom/users/toad"]
       )
 
     response =
@@ -61,14 +61,20 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
   end
 
   test "Webfinger XML" do
-    user = insert(:user)
+    user =
+      insert(:user,
+        ap_id: "https://hyrule.world/users/zelda",
+        also_known_as: ["https://mushroom.kingdom/users/toad"]
+      )
 
     response =
       build_conn()
       |> put_req_header("accept", "application/xrd+xml")
       |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
+      |> response(200)
 
-    assert response(response, 200)
+    assert response =~ "<Alias>https://hyrule.world/users/zelda</Alias>"
+    assert response =~ "<Alias>https://mushroom.kingdom/users/toad</Alias>"
   end
 
   test "it returns 404 when user isn't found (XML)" do

From a3964b373e696301dae0e432983f55d22f055c5f Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 8 Oct 2020 15:46:03 -0500
Subject: [PATCH 04/14] Aliases: move changelog entry

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97908caf7..6107ac07e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
 - Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
 - Mix task option for force-unfollowing relays
+- Ability to set ActivityPub aliases for follower migration.
 
 ### Changed
 
@@ -182,7 +183,6 @@ switched to a new configuration mechanism, however it was not officially removed
 - Support pagination in emoji packs API (for packs and for files in pack)
 - Support for viewing instances favicons next to posts and accounts
 - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
-- Ability to set ActivityPub aliases for follower migration.
 - "By approval" registrations mode.
 - Configuration: Added `:welcome` settings for the welcome message to newly registered users. You can send a welcome message as a direct message, chat or email.
 - Ability to hide favourites and emoji reactions in the API with `[:instance, :show_reactions]` config.

From 5ec7d88b77360ed78f75be6b1f94895c3f602972 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 8 Oct 2020 16:33:47 -0500
Subject: [PATCH 05/14] Aliases: fix URL regex

---
 lib/pleroma/user.ex | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index e0252c8ee..d66c92b14 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -51,8 +51,7 @@ defmodule Pleroma.User do
 
   # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
   @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
-  # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
-  @url_regex ~r/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/
+  @url_regex ~r/^https?:\/\/[^\s]{1,256}$/
 
   @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
   @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/

From 744b34709db9c11767a9bc57fe0bb21c96e826c7 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 30 Dec 2020 14:22:48 -0600
Subject: [PATCH 06/14] Do not reverse order of reports. We want newest ones
 sorted to the top.

---
 lib/pleroma/web/admin_api/views/report_view.ex | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index 535556370..da949e306 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -19,8 +19,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
       reports:
         reports[:items]
         |> Enum.map(&Report.extract_report_info/1)
-        |> Enum.map(&render(__MODULE__, "show.json", &1))
-        |> Enum.reverse(),
+        |> Enum.map(&render(__MODULE__, "show.json", &1)),
       total: reports[:total]
     }
   end

From 11d40e92b7ee4d855c02d24a485035fb622a138a Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 30 Dec 2020 18:53:27 -0600
Subject: [PATCH 07/14] Render AKAs in Actor endpoints

---
 lib/pleroma/web/activity_pub/views/user_view.ex        | 3 ++-
 test/pleroma/web/activity_pub/views/user_view_test.exs | 6 ++++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 93c9f436c..241224b57 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -112,7 +112,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "tag" => emoji_tags,
       # Note: key name is indeed "discoverable" (not an error)
       "discoverable" => user.is_discoverable,
-      "capabilities" => capabilities
+      "capabilities" => capabilities,
+      "alsoKnownAs" => user.also_known_as
     }
     |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
     |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs
index fe6ddf0d6..5702c1b6f 100644
--- a/test/pleroma/web/activity_pub/views/user_view_test.exs
+++ b/test/pleroma/web/activity_pub/views/user_view_test.exs
@@ -80,6 +80,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
     assert %{"invisible" => true} = UserView.render("service.json", %{user: user})
   end
 
+  test "renders AKAs" do
+    akas = ["https://i.tusooa.xyz/users/test-pleroma"]
+    user = insert(:user, also_known_as: akas)
+    assert %{"alsoKnownAs" => ^akas} = UserView.render("user.json", %{user: user})
+  end
+
   describe "endpoints" do
     test "local users have a usable endpoints structure" do
       user = insert(:user)

From e4791258d4483cd9dad6016ec453e6ca7ea10d73 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 31 Dec 2020 10:53:18 -0600
Subject: [PATCH 08/14] Ensure newest report is returned first in the list

---
 .../web/admin_api/views/report_view_test.exs  | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/test/pleroma/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs
index ff3453208..3914751b5 100644
--- a/test/pleroma/web/admin_api/views/report_view_test.exs
+++ b/test/pleroma/web/admin_api/views/report_view_test.exs
@@ -143,4 +143,29 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
 
     assert %{} = ReportView.render("show.json", Report.extract_report_info(activity))
   end
+
+  test "reports are ordered newest first" do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, report1} =
+      CommonAPI.report(user, %{
+        account_id: other_user.id,
+        comment: "first report"
+      })
+
+    {:ok, report2} =
+      CommonAPI.report(user, %{
+        account_id: other_user.id,
+        comment: "second report"
+      })
+
+    %{reports: rendered} =
+      ReportView.render("index.json",
+        reports: Pleroma.Web.ActivityPub.Utils.get_reports(%{}, 1, 50)
+      )
+
+    assert report2.id == rendered |> Enum.at(0) |> Map.get(:id)
+    assert report1.id == rendered |> Enum.at(1) |> Map.get(:id)
+  end
 end

From 0d6b9ce8ca5afd1ddaa0af4061fd956b29d17826 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 31 Dec 2020 18:51:57 +0000
Subject: [PATCH 09/14] Apply 2 suggestion(s) to 1 file(s)

---
 lib/pleroma/web/web_finger.ex | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex
index 7c009388a..a109e1acc 100644
--- a/lib/pleroma/web/web_finger.ex
+++ b/lib/pleroma/web/web_finger.ex
@@ -59,7 +59,7 @@ defmodule Pleroma.Web.WebFinger do
   end
 
   defp gather_aliases(%User{} = user) do
-    [user.ap_id] ++ user.also_known_as
+    [user.ap_id | user.also_known_as]
   end
 
   def represent_user(user, "JSON") do
@@ -76,8 +76,9 @@ defmodule Pleroma.Web.WebFinger do
     {:ok, user} = User.ensure_keys_present(user)
 
     aliases =
-      gather_aliases(user)
-      |> Enum.map(fn the_alias -> {:Alias, the_alias} end)
+      user
+      |> gather_aliases()
+      |> Enum.map(&{:Alias, &1})
 
     links =
       gather_links(user)

From 4200a063408966b1e14e79b18c96ce59af23ec35 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 31 Dec 2020 12:53:28 -0600
Subject: [PATCH 10/14] Aliases: refactor validate_also_known_as/1

---
 lib/pleroma/user.ex | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 7b26ac7a3..230845662 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2458,9 +2458,10 @@ defmodule Pleroma.User do
 
   defp validate_also_known_as(changeset) do
     validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as ->
-      case Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do
-        true -> []
-        false -> [also_known_as: "Invalid ap_id format. Must be a URL."]
+      if Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do
+        []
+      else
+        [also_known_as: "Invalid ap_id format. Must be a URL."]
       end
     end)
   end

From 83d97ab98e99a16d0ba25a57df1be182dfb9b938 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 31 Dec 2020 13:15:44 -0600
Subject: [PATCH 11/14] Document reports ordering change

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1604ab3a..77959c415 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
 - Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries.
 - Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators.
+- Admin API: Reports now ordered by newest
 
 ### Added
 

From 0ec7e9b8e98f6310f19d0b0a6ad82fc9c70a9d47 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 1 Jan 2021 11:58:42 -0600
Subject: [PATCH 12/14] AdminAPI: return id for moderation log entries

---
 docs/API/admin_api.md                                        | 1 +
 lib/pleroma/web/admin_api/views/moderation_log_view.ex       | 1 +
 .../pleroma/web/admin_api/views/moderation_log_view_test.exs | 5 +++++
 3 files changed, 7 insertions(+)

diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index 266f8cef8..5253dc668 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -1123,6 +1123,7 @@ Loads json generated from `config/descriptions.exs`.
 ```json
 [
   {
+    "id": 1234,
     "data": {
       "actor": {
         "id": 1,
diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
index 112f9e0e1..3fa778b0a 100644
--- a/lib/pleroma/web/admin_api/views/moderation_log_view.ex
+++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
@@ -21,6 +21,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogView do
       |> DateTime.to_unix()
 
     %{
+      id: log_entry.id,
       data: log_entry.data,
       time: time,
       message: ModerationLog.get_log_entry_message(log_entry)
diff --git a/test/pleroma/web/admin_api/views/moderation_log_view_test.exs b/test/pleroma/web/admin_api/views/moderation_log_view_test.exs
index 390d7bbeb..a4748990e 100644
--- a/test/pleroma/web/admin_api/views/moderation_log_view_test.exs
+++ b/test/pleroma/web/admin_api/views/moderation_log_view_test.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
   describe "renders `report_note_delete` log messages" do
     setup do
       log1 = %Pleroma.ModerationLog{
+        id: 1,
         data: %{
           "action" => "report_note_delete",
           "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@@ -21,6 +22,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
       }
 
       log2 = %Pleroma.ModerationLog{
+        id: 2,
         data: %{
           "action" => "report_note_delete",
           "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@@ -42,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
              ) == %{
                items: [
                  %{
+                   id: 1,
                    data: %{
                      "action" => "report_note_delete",
                      "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@@ -59,6 +62,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
                    time: 1_605_622_400
                  },
                  %{
+                   id: 2,
                    data: %{
                      "action" => "report_note_delete",
                      "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@@ -82,6 +86,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
 
     test "renders `report_note_delete` log message", %{log1: log} do
       assert ModerationLogView.render("show.json", %{log_entry: log}) == %{
+               id: 1,
                data: %{
                  "action" => "report_note_delete",
                  "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},

From 8e5904daa59f9e7c85e1431605067b86506bcfc9 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 4 Jan 2021 18:40:59 +0100
Subject: [PATCH 13/14] SideEffects.DeleteTest: asyncify.

Replace Mock with Mox, mock out Logger.
---
 config/test.exs                               |   4 +
 lib/pleroma/logging.ex                        |   7 +
 lib/pleroma/web/activity_pub/activity_pub.ex  |   5 +
 .../activity_pub/activity_pub/streaming.ex    |  12 ++
 lib/pleroma/web/activity_pub/side_effects.ex  |   8 +-
 .../activity_pub/side_effects/delete_test.exs | 147 ++++++++++++++++++
 .../web/activity_pub/side_effects_test.exs    | 110 -------------
 test/support/conn_case.ex                     |   2 +
 test/support/data_case.ex                     |   2 +
 test/support/mocks.ex                         |   7 +-
 10 files changed, 190 insertions(+), 114 deletions(-)
 create mode 100644 lib/pleroma/logging.ex
 create mode 100644 lib/pleroma/web/activity_pub/activity_pub/streaming.ex
 create mode 100644 test/pleroma/web/activity_pub/side_effects/delete_test.exs

diff --git a/config/test.exs b/config/test.exs
index a85881592..7fc457463 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -134,6 +134,10 @@ config :pleroma, :pipeline,
 
 config :pleroma, :cachex, provider: Pleroma.CachexMock
 
+config :pleroma, :side_effects,
+  ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
+  logger: Pleroma.LoggerMock
+
 if File.exists?("./config/test.secret.exs") do
   import_config "test.secret.exs"
 else
diff --git a/lib/pleroma/logging.ex b/lib/pleroma/logging.ex
new file mode 100644
index 000000000..37b201c29
--- /dev/null
+++ b/lib/pleroma/logging.ex
@@ -0,0 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Logging do
+  @callback error(String.t()) :: any()
+end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 15f298bb8..3e346d49a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -33,6 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   require Pleroma.Constants
 
   @behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting
+  @behaviour Pleroma.Web.ActivityPub.ActivityPub.Streaming
 
   defp get_recipients(%{"type" => "Create"} = data) do
     to = Map.get(data, "to", [])
@@ -224,6 +225,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     Streamer.stream("participation", participations)
   end
 
+  @impl true
   def stream_out_participations(%Object{data: %{"context" => context}}, user) do
     with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do
       conversation = Repo.preload(conversation, :participations)
@@ -240,8 +242,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
+  @impl true
   def stream_out_participations(_, _), do: :noop
 
+  @impl true
   def stream_out(%Activity{data: %{"type" => data_type}} = activity)
       when data_type in ["Create", "Announce", "Delete"] do
     activity
@@ -249,6 +253,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> Streamer.stream(activity)
   end
 
+  @impl true
   def stream_out(_activity) do
     :noop
   end
diff --git a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
new file mode 100644
index 000000000..30009f2fb
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
@@ -0,0 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
+  alias Pleroma.Activity
+  alias Pleroma.Object
+  alias Pleroma.User
+
+  @callback stream_out(Activity.t()) :: any()
+  @callback stream_out_participations(Object.t(), User.t()) :: any()
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 55c99ad0c..e37caf6a0 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -28,6 +28,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   require Logger
 
   @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+  @ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
+  @logger Pleroma.Config.get([:side_effects, :logger], Logger)
 
   @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
 
@@ -287,12 +289,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
 
             MessageReference.delete_for_object(deleted_object)
 
-            ActivityPub.stream_out(object)
-            ActivityPub.stream_out_participations(deleted_object, user)
+            @ap_streamer.stream_out(object)
+            @ap_streamer.stream_out_participations(deleted_object, user)
             :ok
           else
             {:actor, _} ->
-              Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
+              @logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
               :no_object_actor
           end
 
diff --git a/test/pleroma/web/activity_pub/side_effects/delete_test.exs b/test/pleroma/web/activity_pub/side_effects/delete_test.exs
new file mode 100644
index 000000000..e4ad606a9
--- /dev/null
+++ b/test/pleroma/web/activity_pub/side_effects/delete_test.exs
@@ -0,0 +1,147 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.SideEffects.DeleteTest do
+  use Oban.Testing, repo: Pleroma.Repo
+  use Pleroma.DataCase, async: true
+
+  alias Pleroma.Activity
+  alias Pleroma.Object
+  alias Pleroma.Repo
+  alias Pleroma.Tests.ObanHelpers
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Builder
+  alias Pleroma.Web.ActivityPub.SideEffects
+  alias Pleroma.Web.CommonAPI
+
+  alias Pleroma.LoggerMock
+  alias Pleroma.Web.ActivityPub.ActivityPubMock
+
+  import Mox
+  import Pleroma.Factory
+
+  describe "user deletion" do
+    setup do
+      user = insert(:user)
+
+      {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
+      {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
+
+      %{
+        user: user,
+        delete_user: delete_user
+      }
+    end
+
+    test "it handles user deletions", %{delete_user: delete, user: user} do
+      {:ok, _delete, _} = SideEffects.handle(delete)
+      ObanHelpers.perform_all()
+
+      assert User.get_cached_by_ap_id(user.ap_id).deactivated
+    end
+  end
+
+  describe "object deletion" do
+    setup do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
+      {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
+      {:ok, favorite} = CommonAPI.favorite(user, post.id)
+      object = Object.normalize(post)
+      {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
+      {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
+
+      %{
+        user: user,
+        delete: delete,
+        post: post,
+        object: object,
+        op: op,
+        favorite: favorite
+      }
+    end
+
+    test "it handles object deletions", %{
+      delete: delete,
+      post: post,
+      object: object,
+      user: user,
+      op: op,
+      favorite: favorite
+    } do
+      object_id = object.id
+      user_id = user.id
+
+      ActivityPubMock
+      |> expect(:stream_out, fn ^delete -> nil end)
+      |> expect(:stream_out_participations, fn %Object{id: ^object_id}, %User{id: ^user_id} ->
+        nil
+      end)
+
+      {:ok, _delete, _} = SideEffects.handle(delete)
+      user = User.get_cached_by_ap_id(object.data["actor"])
+
+      object = Object.get_by_id(object.id)
+      assert object.data["type"] == "Tombstone"
+      refute Activity.get_by_id(post.id)
+      refute Activity.get_by_id(favorite.id)
+
+      user = User.get_by_id(user.id)
+      assert user.note_count == 0
+
+      object = Object.normalize(op.data["object"], false)
+
+      assert object.data["repliesCount"] == 0
+    end
+
+    test "it handles object deletions when the object itself has been pruned", %{
+      delete: delete,
+      post: post,
+      object: object,
+      user: user,
+      op: op
+    } do
+      object_id = object.id
+      user_id = user.id
+
+      ActivityPubMock
+      |> expect(:stream_out, fn ^delete -> nil end)
+      |> expect(:stream_out_participations, fn %Object{id: ^object_id}, %User{id: ^user_id} ->
+        nil
+      end)
+
+      {:ok, _delete, _} = SideEffects.handle(delete)
+      user = User.get_cached_by_ap_id(object.data["actor"])
+
+      object = Object.get_by_id(object.id)
+      assert object.data["type"] == "Tombstone"
+      refute Activity.get_by_id(post.id)
+
+      user = User.get_by_id(user.id)
+      assert user.note_count == 0
+
+      object = Object.normalize(op.data["object"], false)
+
+      assert object.data["repliesCount"] == 0
+    end
+
+    test "it logs issues with objects deletion", %{
+      delete: delete,
+      object: object
+    } do
+      {:ok, _object} =
+        object
+        |> Object.change(%{data: Map.delete(object.data, "actor")})
+        |> Repo.update()
+
+      LoggerMock
+      |> expect(:error, fn str -> assert str =~ "The object doesn't have an actor" end)
+
+      {:error, :no_object_actor} = SideEffects.handle(delete)
+    end
+  end
+end
diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs
index 297fc0b84..50af7a507 100644
--- a/test/pleroma/web/activity_pub/side_effects_test.exs
+++ b/test/pleroma/web/activity_pub/side_effects_test.exs
@@ -19,7 +19,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
   alias Pleroma.Web.ActivityPub.SideEffects
   alias Pleroma.Web.CommonAPI
 
-  import ExUnit.CaptureLog
   import Mock
   import Pleroma.Factory
 
@@ -131,115 +130,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
     end
   end
 
-  describe "delete objects" do
-    setup do
-      user = insert(:user)
-      other_user = insert(:user)
-
-      {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
-      {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
-      {:ok, favorite} = CommonAPI.favorite(user, post.id)
-      object = Object.normalize(post)
-      {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
-      {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
-      {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
-      {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
-
-      %{
-        user: user,
-        delete: delete,
-        post: post,
-        object: object,
-        delete_user: delete_user,
-        op: op,
-        favorite: favorite
-      }
-    end
-
-    test "it handles object deletions", %{
-      delete: delete,
-      post: post,
-      object: object,
-      user: user,
-      op: op,
-      favorite: favorite
-    } do
-      with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
-        stream_out: fn _ -> nil end,
-        stream_out_participations: fn _, _ -> nil end do
-        {:ok, delete, _} = SideEffects.handle(delete)
-        user = User.get_cached_by_ap_id(object.data["actor"])
-
-        assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
-        assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
-      end
-
-      object = Object.get_by_id(object.id)
-      assert object.data["type"] == "Tombstone"
-      refute Activity.get_by_id(post.id)
-      refute Activity.get_by_id(favorite.id)
-
-      user = User.get_by_id(user.id)
-      assert user.note_count == 0
-
-      object = Object.normalize(op.data["object"], false)
-
-      assert object.data["repliesCount"] == 0
-    end
-
-    test "it handles object deletions when the object itself has been pruned", %{
-      delete: delete,
-      post: post,
-      object: object,
-      user: user,
-      op: op
-    } do
-      with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
-        stream_out: fn _ -> nil end,
-        stream_out_participations: fn _, _ -> nil end do
-        {:ok, delete, _} = SideEffects.handle(delete)
-        user = User.get_cached_by_ap_id(object.data["actor"])
-
-        assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
-        assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
-      end
-
-      object = Object.get_by_id(object.id)
-      assert object.data["type"] == "Tombstone"
-      refute Activity.get_by_id(post.id)
-
-      user = User.get_by_id(user.id)
-      assert user.note_count == 0
-
-      object = Object.normalize(op.data["object"], false)
-
-      assert object.data["repliesCount"] == 0
-    end
-
-    test "it handles user deletions", %{delete_user: delete, user: user} do
-      {:ok, _delete, _} = SideEffects.handle(delete)
-      ObanHelpers.perform_all()
-
-      assert User.get_cached_by_ap_id(user.ap_id).deactivated
-    end
-
-    test "it logs issues with objects deletion", %{
-      delete: delete,
-      object: object
-    } do
-      {:ok, object} =
-        object
-        |> Object.change(%{data: Map.delete(object.data, "actor")})
-        |> Repo.update()
-
-      Object.invalid_object_cache(object)
-
-      assert capture_log(fn ->
-               {:error, :no_object_actor} = SideEffects.handle(delete)
-             end) =~ "object doesn't have an actor"
-    end
-  end
-
   describe "EmojiReact objects" do
     setup do
       poster = insert(:user)
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 02f49c590..f20e3d955 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -138,6 +138,8 @@ defmodule Pleroma.Web.ConnCase do
 
     Pleroma.DataCase.stub_pipeline()
 
+    Mox.verify_on_exit!()
+
     {:ok, conn: Phoenix.ConnTest.build_conn()}
   end
 end
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
index 5c657c1d9..0b41f0f63 100644
--- a/test/support/data_case.ex
+++ b/test/support/data_case.ex
@@ -85,6 +85,8 @@ defmodule Pleroma.DataCase do
 
     stub_pipeline()
 
+    Mox.verify_on_exit!()
+
     :ok
   end
 
diff --git a/test/support/mocks.ex b/test/support/mocks.ex
index a600a6458..442ff5b71 100644
--- a/test/support/mocks.ex
+++ b/test/support/mocks.ex
@@ -13,7 +13,10 @@ Mox.defmock(Pleroma.Web.ActivityPub.MRFMock,
 )
 
 Mox.defmock(Pleroma.Web.ActivityPub.ActivityPubMock,
-  for: Pleroma.Web.ActivityPub.ActivityPub.Persisting
+  for: [
+    Pleroma.Web.ActivityPub.ActivityPub.Persisting,
+    Pleroma.Web.ActivityPub.ActivityPub.Streaming
+  ]
 )
 
 Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock,
@@ -23,3 +26,5 @@ Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock,
 Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing)
 
 Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)
+
+Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)

From e802b48d558ccd4a65a6da2bcc6dacb057b7fd09 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Tue, 5 Jan 2021 13:10:14 +0100
Subject: [PATCH 14/14] User: Use ObjectID type to validate also-known-as field

---
 lib/pleroma/user.ex                                | 14 +-------------
 .../web/mastodon_api/update_credentials_test.exs   |  9 +++++++++
 2 files changed, 10 insertions(+), 13 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 230845662..52730fd8d 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -51,7 +51,6 @@ defmodule Pleroma.User do
 
   # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
   @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
-  @url_regex ~r/^https?:\/\/[^\s]{1,256}$/
 
   @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
   @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
@@ -143,7 +142,7 @@ defmodule Pleroma.User do
     field(:allow_following_move, :boolean, default: true)
     field(:skip_thread_containment, :boolean, default: false)
     field(:actor_type, :string, default: "Person")
-    field(:also_known_as, {:array, :string}, default: [])
+    field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
     field(:inbox, :string)
     field(:shared_inbox, :string)
     field(:accepts_chat_messages, :boolean, default: nil)
@@ -530,7 +529,6 @@ defmodule Pleroma.User do
     )
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
-    |> validate_also_known_as()
     |> validate_length(:bio, max: bio_limit)
     |> validate_length(:name, min: 1, max: name_limit)
     |> validate_inclusion(:actor_type, ["Person", "Service"])
@@ -2456,16 +2454,6 @@ defmodule Pleroma.User do
     |> Map.put(:fields, fields)
   end
 
-  defp validate_also_known_as(changeset) do
-    validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as ->
-      if Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do
-        []
-      else
-        [also_known_as: "Invalid ap_id format. Must be a URL."]
-      end
-    end)
-  end
-
   def get_host(%User{ap_id: ap_id} = _user) do
     URI.parse(ap_id).host
   end
diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
index ff0147244..e3e437a19 100644
--- a/test/pleroma/web/mastodon_api/update_credentials_test.exs
+++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs
@@ -228,6 +228,15 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
       assert user_data["pleroma"]["also_known_as"] == ["https://mushroom.kingdom/users/mario"]
     end
 
+    test "doesn't update non-url akas", %{conn: conn} do
+      conn =
+        patch(conn, "/api/v1/accounts/update_credentials", %{
+          "also_known_as" => ["aReallyCoolGuy"]
+        })
+
+      assert json_response_and_validate_schema(conn, 403)
+    end
+
     test "updates the user's avatar", %{user: user, conn: conn} do
       new_avatar = %Plug.Upload{
         content_type: "image/jpeg",