Merge remote-tracking branch 'upstream/develop' into registration-workflow
authorAlex Gleason <alex@alexgleason.me>
Thu, 17 Dec 2020 15:04:43 +0000 (09:04 -0600)
committerAlex Gleason <alex@alexgleason.me>
Thu, 17 Dec 2020 15:05:36 +0000 (09:05 -0600)
1  2 
CHANGELOG.md
lib/mix/tasks/pleroma/user.ex
lib/pleroma/emails/user_email.ex
lib/pleroma/user.ex
test/mix/tasks/pleroma/user_test.exs
test/pleroma/user_test.exs

diff --combined CHANGELOG.md
index cedba088c3eb43c4c525bb67d9a7a6e03ad7848d,230888bbf6568c920a2166b26d4cebee85125acf..c6bf38ee02fdb2e3a1f75f16cb4577ea7e302c11
@@@ -10,20 -10,23 +10,25 @@@ The format is based on [Keep a Changelo
  
  - Polls now always return a `voters_count`, even if they are single-choice.
  - Admin Emails: The ap id is used as the user link in emails now.
 +- Improved registration workflow for email confirmation and account approval modes.
 +- **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.
  
  ### Added
  
  - Reports now generate notifications for admins and mods.
  - Experimental websocket-based federation between Pleroma instances.
- - Support for local-only statuses
+ - Support for local-only statuses.
  - Support pagination of blocks and mutes.
  - Account backup.
  - Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
  - Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
  - The site title is now injected as a `title` tag like preloads or metadata.
  - Password reset tokens now are not accepted after a certain age.
+ - Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config`.
+ - OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved.
+ - OAuth improvements and fixes: more secure session-based authentication (by token that could be revoked anytime), ability to revoke belonging OAuth token from any client etc.
  
  <details>
    <summary>API Changes</summary>
  - Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
  - Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
  - Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
- - Admin API: An endpoint to manage frontends
+ - Admin API: An endpoint to manage frontends.
+ - Streaming API: Add follow relationships updates.
  </details>
  
  ### Fixed
  
  - Users with `is_discoverable` field set to false (default value) will appear in in-service search results but be hidden from external services (search bots etc.).
+ - Streaming API: Posts and notifications are not dropped, when CLI task is executing.
  
  <details>
    <summary>API Changes</summary>
@@@ -58,6 -62,7 +64,7 @@@
  - Mix task pleroma.user delete_activities for source installations.
  - Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention
  - Forwarded reports duplication from Pleroma instances.
+ - Rich Media Previews sometimes showed the wrong preview due to a bug following redirects.
  
  <details>
    <summary>API</summary>
@@@ -101,7 -106,7 +108,7 @@@ switched to a new configuration mechani
  
  - Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
  - 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 for sending confirmation emails to all unconfirmed users (`mix pleroma.email resend_confirmation_emails`)
  - Mix task option for force-unfollowing relays
  - App metrics: ability to restrict access to specified IP whitelist.
  
index 544ba355045cedcbc273b282c207ee3336e7f3b7,ca9c8579f932d26406213abf3ed25d0a449563db..20fe6c6e408bc85275a5cb88a73774a13b937554
@@@ -60,7 -60,7 +60,7 @@@ defmodule Mix.Tasks.Pleroma.User d
        - admin: #{if(admin?, do: "true", else: "false")}
      """)
  
-     proceed? = assume_yes? or shell_yes?("Continue?")
+     proceed? = assume_yes? or shell_prompt("Continue?", "n") in ~w(Yn Y y)
  
      if proceed? do
        start_pleroma()
      end
    end
  
 -  def run(["toggle_confirmed", nickname]) do
 +  def run(["confirm", nickname]) do
      start_pleroma()
  
      with %User{} = user <- User.get_cached_by_nickname(nickname) do
 -      {:ok, user} = User.toggle_confirmation(user)
 +      {:ok, user} = User.confirm(user)
  
        message = if user.confirmation_pending, do: "needs", else: "doesn't need"
  
index 3b9275bc3f46fd695499170296b197ac20914c14,e6829b8621a15f85675451a7854237caeb2795ae..d3625dbf2bb7bc54f3f27a901261460570eff0a8
@@@ -93,19 -93,6 +93,19 @@@ defmodule Pleroma.Emails.UserEmail d
      |> html_body(html_body)
    end
  
 +  def approval_pending_email(user) do
 +    html_body = """
 +    <h3>Awaiting Approval</h3>
 +    <p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
 +    """
 +
 +    new()
 +    |> to(recipient(user))
 +    |> from(sender())
 +    |> subject("Your account is awaiting approval")
 +    |> html_body(html_body)
 +  end
 +
    @doc """
    Email used in digest email notifications
    Includes Mentions and New Followers data
  
        logo_path =
          if is_nil(logo) do
-           Path.join(:code.priv_dir(:pleroma), "static/static/logo.png")
+           Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
          else
            Path.join(Config.get([:instance, :static_dir]), logo)
          end
        |> subject("Your digest from #{instance_name()}")
        |> put_layout(false)
        |> render_body("digest.html", html_data)
-       |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline))
+       |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
      end
    end
  
diff --combined lib/pleroma/user.ex
index 62171078c16481d2a853443468db97cae4f12d4b,1836643a6c4540bcf704cc7f2b43fe490b2885da..5a7704ddb7ec9b479b3355d65eff49d1ba052b37
@@@ -473,6 -473,18 +473,18 @@@ defmodule Pleroma.User d
      |> validate_length(:bio, max: bio_limit)
      |> validate_length(:name, max: name_limit)
      |> validate_fields(true)
+     |> validate_non_local()
+   end
+   defp validate_non_local(cng) do
+     local? = get_field(cng, :local)
+     if local? do
+       cng
+       |> add_error(:local, "User is local, can't update with this changeset.")
+     else
+       cng
+     end
    end
  
    def update_changeset(struct, params \\ %{}) do
      end
    end
  
 -  def post_register_action(%User{} = user) do
 +  def post_register_action(%User{confirmation_pending: true} = user) do
 +    with {:ok, _} <- try_send_confirmation_email(user) do
 +      {:ok, user}
 +    end
 +  end
 +
 +  def post_register_action(%User{approval_pending: true} = user) do
 +    with {:ok, _} <- send_user_approval_email(user),
 +         {:ok, _} <- send_admin_approval_emails(user) do
 +      {:ok, user}
 +    end
 +  end
 +
 +  def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
      with {:ok, user} <- autofollow_users(user),
           {:ok, _} <- autofollowing_users(user),
           {:ok, user} <- set_cache(user),
           {:ok, _} <- send_welcome_email(user),
           {:ok, _} <- send_welcome_message(user),
 -         {:ok, _} <- send_welcome_chat_message(user),
 -         {:ok, _} <- try_send_confirmation_email(user) do
 +         {:ok, _} <- send_welcome_chat_message(user) do
        {:ok, user}
      end
    end
  
 +  defp send_user_approval_email(user) do
 +    user
 +    |> Pleroma.Emails.UserEmail.approval_pending_email()
 +    |> Pleroma.Emails.Mailer.deliver_async()
 +
 +    {:ok, :enqueued}
 +  end
 +
 +  defp send_admin_approval_emails(user) do
 +    all_superusers()
 +    |> Enum.filter(fn user -> not is_nil(user.email) end)
 +    |> Enum.each(fn superuser ->
 +      superuser
 +      |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
 +      |> Pleroma.Emails.Mailer.deliver_async()
 +    end)
 +
 +    {:ok, :enqueued}
 +  end
 +
    def send_welcome_message(user) do
      if User.WelcomeMessage.enabled?() do
        User.WelcomeMessage.post_message(user)
      if not ap_enabled?(followed) do
        follow(follower, followed)
      else
-       {:ok, follower}
+       {:ok, follower, followed}
      end
    end
  
  
        true ->
          FollowingRelationship.follow(follower, followed, state)
-         {:ok, _} = update_follower_count(followed)
-         follower
-         |> update_following_count()
      end
    end
  
      case get_follow_state(follower, followed) do
        state when state in [:follow_pending, :follow_accept] ->
          FollowingRelationship.unfollow(follower, followed)
-         {:ok, followed} = update_follower_count(followed)
-         {:ok, follower} = update_following_count(follower)
-         {:ok, follower, followed}
  
        nil ->
          {:error, "Not subscribed!"}
      end)
    end
  
 -  def approve(%User{} = user) do
 -    change(user, approval_pending: false)
 -    |> update_and_set_cache()
 +  def approve(%User{approval_pending: true} = user) do
 +    with chg <- change(user, approval_pending: false),
 +         {:ok, user} <- update_and_set_cache(chg) do
 +      post_register_action(user)
 +      {:ok, user}
 +    end
 +  end
 +
 +  def approve(%User{} = user), do: {:ok, user}
 +
 +  def confirm(users) when is_list(users) do
 +    Repo.transaction(fn ->
 +      Enum.map(users, fn user ->
 +        with {:ok, user} <- confirm(user), do: user
 +      end)
 +    end)
 +  end
 +
 +  def confirm(%User{confirmation_pending: true} = user) do
 +    with chg <- confirmation_changeset(user, need_confirmation: false),
 +         {:ok, user} <- update_and_set_cache(chg) do
 +      post_register_action(user)
 +      {:ok, user}
 +    end
    end
  
 +  def confirm(%User{} = user), do: {:ok, user}
 +
    def update_notification_settings(%User{} = user, settings) do
      user
      |> cast(%{notification_settings: settings}, [])
      updated_user
    end
  
 -  @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
 -  def toggle_confirmation(%User{} = user) do
 -    user
 -    |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
 -    |> update_and_set_cache()
 -  end
 -
 -  @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
 -  def toggle_confirmation(users) do
 -    Enum.map(users, &toggle_confirmation/1)
 -  end
 -
    @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
    def need_confirmation(%User{} = user, bool) do
      user
      |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
      |> Map.put(:fields, fields)
    end
+   def get_host(%User{ap_id: ap_id} = _user) do
+     URI.parse(ap_id).host
+   end
  end
index 848e0e2599d3a45f11926faff41239e84cd73ec8,1277712127825dbf6796eb6eb4d5972d66d558c8..de8ab27e5451826f610802317262432fc280dfb5
@@@ -36,7 -36,7 +36,7 @@@ defmodule Mix.Tasks.Pleroma.UserTest d
        unsaved = build(:user)
  
        # prepare to answer yes
-       send(self(), {:mix_shell_input, :yes?, true})
+       send(self(), {:mix_shell_input, :prompt, "Y"})
  
        Mix.Tasks.Pleroma.User.run([
          "new",
@@@ -55,7 -55,7 +55,7 @@@
        assert_received {:mix_shell, :info, [message]}
        assert message =~ "user will be created"
  
-       assert_received {:mix_shell, :yes?, [message]}
+       assert_received {:mix_shell, :prompt, [message]}
        assert message =~ "Continue"
  
        assert_received {:mix_shell, :info, [message]}
        unsaved = build(:user)
  
        # prepare to answer no
-       send(self(), {:mix_shell_input, :yes?, false})
+       send(self(), {:mix_shell_input, :prompt, "N"})
  
        Mix.Tasks.Pleroma.User.run(["new", unsaved.nickname, unsaved.email])
  
        assert_received {:mix_shell, :info, [message]}
        assert message =~ "user will be created"
  
-       assert_received {:mix_shell, :yes?, [message]}
+       assert_received {:mix_shell, :prompt, [message]}
        assert message =~ "Continue"
  
        assert_received {:mix_shell, :info, [message]}
      end
    end
  
 -  describe "running toggle_confirmed" do
 +  describe "running confirm" do
      test "user is confirmed" do
        %{id: id, nickname: nickname} = insert(:user, confirmation_pending: false)
  
 -      assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
 +      assert :ok = Mix.Tasks.Pleroma.User.run(["confirm", nickname])
        assert_received {:mix_shell, :info, [message]}
 -      assert message == "#{nickname} needs confirmation."
 +      assert message == "#{nickname} doesn't need confirmation."
  
        user = Repo.get(User, id)
 -      assert user.confirmation_pending
 -      assert user.confirmation_token
 +      refute user.confirmation_pending
 +      refute user.confirmation_token
      end
  
      test "user is not confirmed" do
        %{id: id, nickname: nickname} =
          insert(:user, confirmation_pending: true, confirmation_token: "some token")
  
 -      assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
 +      assert :ok = Mix.Tasks.Pleroma.User.run(["confirm", nickname])
        assert_received {:mix_shell, :info, [message]}
        assert message == "#{nickname} doesn't need confirmation."
  
      end
  
      test "it prints an error message when user is not exist" do
 -      Mix.Tasks.Pleroma.User.run(["toggle_confirmed", "foo"])
 +      Mix.Tasks.Pleroma.User.run(["confirm", "foo"])
  
        assert_received {:mix_shell, :error, [message]}
        assert message =~ "No local user"
        moot = insert(:user, nickname: "moot")
        kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon")
  
-       {:ok, user} = User.follow(user, moon)
+       {:ok, user, moon} = User.follow(user, moon)
  
        assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id)
  
index 674066a24efe072df2eb6eb4f6458e4131a2ab81,5d544fe7d5cb88b80c98bd4ddb8943204221944d..40bbcad0bfb88ad1090da955d098851dced1ebfb
@@@ -233,7 -233,7 +233,7 @@@ defmodule Pleroma.UserTest d
      {:ok, _user_relationship} = User.block(user, blocked)
      {:ok, _user_relationship} = User.block(reverse_blocked, user)
  
-     {:ok, user} = User.follow(user, followed_zero)
+     {:ok, user, followed_zero} = User.follow(user, followed_zero)
  
      {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked])
  
      user = insert(:user)
      followed = insert(:user)
  
-     {:ok, user} = User.follow(user, followed)
+     {:ok, user, followed} = User.follow(user, followed)
  
      user = User.get_cached_by_id(user.id)
      followed = User.get_cached_by_ap_id(followed.ap_id)
      follower = insert(:user, is_locked: true)
      followed = insert(:user, is_locked: true)
  
-     {:ok, follower} = User.maybe_direct_follow(follower, followed)
+     {:ok, follower, followed} = User.maybe_direct_follow(follower, followed)
  
      refute User.following?(follower, followed)
    end
            following_address: "http://localhost:4001/users/fuser2/following"
          })
  
-       {:ok, user} = User.follow(user, followed, :follow_accept)
+       {:ok, user, followed} = User.follow(user, followed, :follow_accept)
  
        {:ok, user, _activity} = User.unfollow(user, followed)
  
        followed = insert(:user)
        user = insert(:user)
  
-       {:ok, user} = User.follow(user, followed, :follow_accept)
+       {:ok, user, followed} = User.follow(user, followed, :follow_accept)
  
        assert User.following(user) == [user.follower_address, followed.follower_address]
  
        |> assert_email_sent()
      end
  
 +    test "sends a pending approval email" do
 +      clear_config([:instance, :account_approval_required], true)
 +
 +      {:ok, user} =
 +        User.register_changeset(%User{}, @full_user_data)
 +        |> User.register()
 +
 +      ObanHelpers.perform_all()
 +
 +      assert_email_sent(
 +        from: Pleroma.Config.Helpers.sender(),
 +        to: {user.name, user.email},
 +        subject: "Your account is awaiting approval"
 +      )
 +    end
 +
      test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
        Pleroma.Config.put([:instance, :account_activation_required], true)
  
          refute cs.valid?
        end)
      end
+     test "it is invalid given a local user" do
+       user = insert(:user)
+       cs = User.remote_user_changeset(user, %{name: "tom from myspace"})
+       refute cs.valid?
+     end
    end
  
    describe "followers and friends" do
        follower_two = insert(:user)
        not_follower = insert(:user)
  
-       {:ok, follower_one} = User.follow(follower_one, user)
-       {:ok, follower_two} = User.follow(follower_two, user)
+       {:ok, follower_one, user} = User.follow(follower_one, user)
+       {:ok, follower_two, user} = User.follow(follower_two, user)
  
        res = User.get_followers(user)
  
        followed_two = insert(:user)
        not_followed = insert(:user)
  
-       {:ok, user} = User.follow(user, followed_one)
-       {:ok, user} = User.follow(user, followed_two)
+       {:ok, user, followed_one} = User.follow(user, followed_one)
+       {:ok, user, followed_two} = User.follow(user, followed_two)
  
        res = User.get_friends(user)
  
        blocker = insert(:user)
        blocked = insert(:user)
  
-       {:ok, blocker} = User.follow(blocker, blocked)
-       {:ok, blocked} = User.follow(blocked, blocker)
+       {:ok, blocker, blocked} = User.follow(blocker, blocked)
+       {:ok, blocked, blocker} = User.follow(blocked, blocker)
  
        assert User.following?(blocker, blocked)
        assert User.following?(blocked, blocker)
        blocker = insert(:user)
        blocked = insert(:user)
  
-       {:ok, blocker} = User.follow(blocker, blocked)
+       {:ok, blocker, blocked} = User.follow(blocker, blocked)
  
        assert User.following?(blocker, blocked)
        refute User.following?(blocked, blocker)
        blocker = insert(:user)
        blocked = insert(:user)
  
-       {:ok, blocked} = User.follow(blocked, blocker)
+       {:ok, blocked, blocker} = User.follow(blocked, blocker)
  
        refute User.following?(blocker, blocked)
        assert User.following?(blocked, blocker)
        good_eggo = insert(:user, %{ap_id: "https://meanies.social/user/cuteposter"})
  
        {:ok, user} = User.block_domain(user, "meanies.social")
-       {:ok, user} = User.follow(user, good_eggo)
+       {:ok, user, good_eggo} = User.follow(user, good_eggo)
  
        refute User.blocks?(user, good_eggo)
      end
        assert Enum.map([actor, addressed], & &1.ap_id) --
                 Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == []
  
-       {:ok, user} = User.follow(user, actor)
-       {:ok, _user_two} = User.follow(user_two, actor)
+       {:ok, user, actor} = User.follow(user, actor)
+       {:ok, _user_two, _actor} = User.follow(user_two, actor)
        recipients = User.get_recipients_from_activity(activity)
        assert length(recipients) == 3
        assert user in recipients
        assert Enum.map([actor, addressed], & &1.ap_id) --
                 Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == []
  
-       {:ok, _actor} = User.follow(actor, user)
-       {:ok, _actor} = User.follow(actor, user_two)
+       {:ok, _actor, _user} = User.follow(actor, user)
+       {:ok, _actor, _user_two} = User.follow(actor, user_two)
        recipients = User.get_recipients_from_activity(activity)
        assert length(recipients) == 2
        assert addressed in recipients
        user = insert(:user)
        user2 = insert(:user)
  
-       {:ok, user} = User.follow(user, user2)
+       {:ok, user, user2} = User.follow(user, user2)
        {:ok, _user} = User.deactivate(user)
  
        user2 = User.get_cached_by_id(user2.id)
        user = insert(:user)
        user2 = insert(:user)
  
-       {:ok, user2} = User.follow(user2, user)
+       {:ok, user2, user} = User.follow(user2, user)
        assert user2.following_count == 1
        assert User.following_count(user2) == 1
  
        user = insert(:user)
        user2 = insert(:user)
  
-       {:ok, user2} = User.follow(user2, user)
+       {:ok, user2, user} = User.follow(user2, user)
  
        {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{user2.nickname}"})
  
          assert false == user.approval_pending
        end)
      end
 +
 +    test "it sends welcome email if it is set" do
 +      clear_config([:welcome, :email, :enabled], true)
 +      clear_config([:welcome, :email, :sender], "tester@test.me")
 +
 +      user = insert(:user, approval_pending: true)
 +      welcome_user = insert(:user, email: "tester@test.me")
 +      instance_name = Pleroma.Config.get([:instance, :name])
 +
 +      User.approve(user)
 +
 +      ObanHelpers.perform_all()
 +
 +      assert_email_sent(
 +        from: {instance_name, welcome_user.email},
 +        to: {user.name, user.email},
 +        html_body: "Welcome to #{instance_name}"
 +      )
 +    end
 +
 +    test "approving an approved user does not trigger post-register actions" do
 +      clear_config([:welcome, :email, :enabled], true)
 +
 +      user = insert(:user, approval_pending: false)
 +      User.approve(user)
 +
 +      ObanHelpers.perform_all()
 +
 +      assert_no_email_sent()
 +    end
 +  end
 +
 +  describe "confirm" do
 +    test "confirms a user" do
 +      user = insert(:user, confirmation_pending: true)
 +      assert true == user.confirmation_pending
 +      {:ok, user} = User.confirm(user)
 +      assert false == user.confirmation_pending
 +    end
 +
 +    test "confirms a list of users" do
 +      unconfirmed_users = [
 +        insert(:user, confirmation_pending: true),
 +        insert(:user, confirmation_pending: true),
 +        insert(:user, confirmation_pending: true)
 +      ]
 +
 +      {:ok, users} = User.confirm(unconfirmed_users)
 +
 +      assert Enum.count(users) == 3
 +
 +      Enum.each(users, fn user ->
 +        assert false == user.confirmation_pending
 +      end)
 +    end
 +
 +    test "sends approval emails when `approval_pending: true`" do
 +      admin = insert(:user, is_admin: true)
 +      user = insert(:user, confirmation_pending: true, approval_pending: true)
 +      User.confirm(user)
 +
 +      ObanHelpers.perform_all()
 +
 +      user_email = Pleroma.Emails.UserEmail.approval_pending_email(user)
 +      admin_email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user)
 +
 +      notify_email = Pleroma.Config.get([:instance, :notify_email])
 +      instance_name = Pleroma.Config.get([:instance, :name])
 +
 +      # User approval email
 +      assert_email_sent(
 +        from: {instance_name, notify_email},
 +        to: {user.name, user.email},
 +        html_body: user_email.html_body
 +      )
 +
 +      # Admin email
 +      assert_email_sent(
 +        from: {instance_name, notify_email},
 +        to: {admin.name, admin.email},
 +        html_body: admin_email.html_body
 +      )
 +    end
 +
 +    test "confirming a confirmed user does not trigger post-register actions" do
 +      user = insert(:user, confirmation_pending: false, approval_pending: true)
 +      User.confirm(user)
 +
 +      ObanHelpers.perform_all()
 +
 +      assert_no_email_sent()
 +    end
    end
  
    describe "delete" do
  
      test "it deactivates a user, all follow relationships and all activities", %{user: user} do
        follower = insert(:user)
-       {:ok, follower} = User.follow(follower, user)
+       {:ok, follower, user} = User.follow(follower, user)
  
        locked_user = insert(:user, name: "locked", is_locked: true)
-       {:ok, _} = User.follow(user, locked_user, :follow_pending)
+       {:ok, _, _} = User.follow(user, locked_user, :follow_pending)
  
        object = insert(:note, user: user)
        activity = insert(:note_activity, user: user, note: object)
      follower2 = insert(:user)
      follower3 = insert(:user)
  
-     {:ok, follower} = User.follow(follower, user)
-     {:ok, _follower2} = User.follow(follower2, user)
-     {:ok, _follower3} = User.follow(follower3, user)
+     {:ok, follower, user} = User.follow(follower, user)
+     {:ok, _follower2, _user} = User.follow(follower2, user)
+     {:ok, _follower3, _user} = User.follow(follower3, user)
  
      {:ok, _user_relationship} = User.block(user, follower)
      user = refresh_record(user)
      end
    end
  
 -  describe "toggle_confirmation/1" do
 -    test "if user is confirmed" do
 -      user = insert(:user, confirmation_pending: false)
 -      {:ok, user} = User.toggle_confirmation(user)
 -
 -      assert user.confirmation_pending
 -      assert user.confirmation_token
 -    end
 -
 -    test "if user is unconfirmed" do
 -      user = insert(:user, confirmation_pending: true, confirmation_token: "some token")
 -      {:ok, user} = User.toggle_confirmation(user)
 -
 -      refute user.confirmation_pending
 -      refute user.confirmation_token
 -    end
 -  end
 -
    describe "ensure_keys_present" do
      test "it creates keys for a user and stores them in info" do
        user = insert(:user)
        assert other_user.following_count == 0
        assert other_user.follower_count == 0
  
-       {:ok, user} = Pleroma.User.follow(user, other_user)
-       other_user = Pleroma.User.get_by_id(other_user.id)
+       {:ok, user, other_user} = Pleroma.User.follow(user, other_user)
  
        assert user.following_count == 1
        assert other_user.follower_count == 1
        assert other_user.follower_count == 0
  
        Pleroma.Config.put([:instance, :external_user_synchronization], true)
-       {:ok, _user} = User.follow(user, other_user)
-       other_user = User.get_by_id(other_user.id)
+       {:ok, _user, other_user} = User.follow(user, other_user)
  
        assert other_user.follower_count == 437
      end
        assert other_user.follower_count == 0
  
        Pleroma.Config.put([:instance, :external_user_synchronization], true)
-       {:ok, other_user} = User.follow(other_user, user)
+       {:ok, other_user, _user} = User.follow(other_user, user)
  
        assert other_user.following_count == 152
      end
  
      assert User.avatar_url(user, no_default: true) == nil
    end
+   test "get_host/1" do
+     user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain")
+     assert User.get_host(user) == "lain.com"
+   end
  end