1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
13 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.EctoType.ActivityPub.ObjectValidators
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
24 alias Pleroma.Notification
26 alias Pleroma.Registration
29 alias Pleroma.UserRelationship
30 alias Pleroma.Web.ActivityPub.ActivityPub
31 alias Pleroma.Web.ActivityPub.Builder
32 alias Pleroma.Web.ActivityPub.Pipeline
33 alias Pleroma.Web.ActivityPub.Utils
34 alias Pleroma.Web.CommonAPI
35 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
36 alias Pleroma.Web.Endpoint
37 alias Pleroma.Web.OAuth
38 alias Pleroma.Web.RelMe
39 alias Pleroma.Workers.BackgroundWorker
43 @type t :: %__MODULE__{}
44 @type account_status ::
47 | :password_reset_pending
48 | :confirmation_pending
50 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
52 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
53 @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])?)*$/
55 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
56 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
58 # AP ID user relationships (blocks, mutes etc.)
59 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
60 @user_relationships_config [
62 blocker_blocks: :blocked_users,
63 blockee_blocks: :blocker_users
66 muter_mutes: :muted_users,
67 mutee_mutes: :muter_users
70 reblog_muter_mutes: :reblog_muted_users,
71 reblog_mutee_mutes: :reblog_muter_users
74 notification_muter_mutes: :notification_muted_users,
75 notification_mutee_mutes: :notification_muter_users
77 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
78 inverse_subscription: [
79 subscribee_subscriptions: :subscriber_users,
80 subscriber_subscriptions: :subscribee_users
84 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
87 field(:bio, :string, default: "")
88 field(:raw_bio, :string)
89 field(:email, :string)
91 field(:nickname, :string)
92 field(:password_hash, :string)
93 field(:password, :string, virtual: true)
94 field(:password_confirmation, :string, virtual: true)
96 field(:public_key, :string)
97 field(:ap_id, :string)
98 field(:avatar, :map, default: %{})
99 field(:local, :boolean, default: true)
100 field(:follower_address, :string)
101 field(:following_address, :string)
102 field(:featured_address, :string)
103 field(:search_rank, :float, virtual: true)
104 field(:search_type, :integer, virtual: true)
105 field(:tags, {:array, :string}, default: [])
106 field(:last_refreshed_at, :naive_datetime_usec)
107 field(:last_digest_emailed_at, :naive_datetime)
108 field(:banner, :map, default: %{})
109 field(:background, :map, default: %{})
110 field(:note_count, :integer, default: 0)
111 field(:follower_count, :integer, default: 0)
112 field(:following_count, :integer, default: 0)
113 field(:is_locked, :boolean, default: false)
114 field(:is_confirmed, :boolean, default: true)
115 field(:password_reset_pending, :boolean, default: false)
116 field(:is_approved, :boolean, default: true)
117 field(:registration_reason, :string, default: nil)
118 field(:confirmation_token, :string, default: nil)
119 field(:default_scope, :string, default: "public")
120 field(:domain_blocks, {:array, :string}, default: [])
121 field(:is_active, :boolean, default: true)
122 field(:no_rich_text, :boolean, default: false)
123 field(:ap_enabled, :boolean, default: false)
124 field(:is_moderator, :boolean, default: false)
125 field(:is_admin, :boolean, default: false)
126 field(:show_role, :boolean, default: true)
127 field(:mastofe_settings, :map, default: nil)
128 field(:uri, ObjectValidators.Uri, default: nil)
129 field(:hide_followers_count, :boolean, default: false)
130 field(:hide_follows_count, :boolean, default: false)
131 field(:hide_followers, :boolean, default: false)
132 field(:hide_follows, :boolean, default: false)
133 field(:hide_favorites, :boolean, default: true)
134 field(:email_notifications, :map, default: %{"digest" => false})
135 field(:mascot, :map, default: nil)
136 field(:emoji, :map, default: %{})
137 field(:pleroma_settings_store, :map, default: %{})
138 field(:fields, {:array, :map}, default: [])
139 field(:raw_fields, {:array, :map}, default: [])
140 field(:is_discoverable, :boolean, default: false)
141 field(:invisible, :boolean, default: false)
142 field(:allow_following_move, :boolean, default: true)
143 field(:skip_thread_containment, :boolean, default: false)
144 field(:actor_type, :string, default: "Person")
145 field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
146 field(:inbox, :string)
147 field(:shared_inbox, :string)
148 field(:accepts_chat_messages, :boolean, default: nil)
149 field(:last_active_at, :naive_datetime)
150 field(:disclose_client, :boolean, default: true)
151 field(:pinned_objects, :map, default: %{})
152 field(:is_suggested, :boolean, default: false)
153 field(:last_status_at, :naive_datetime)
154 field(:language, :string)
157 :notification_settings,
158 Pleroma.User.NotificationSetting,
162 has_many(:notifications, Notification)
163 has_many(:registrations, Registration)
164 has_many(:deliveries, Delivery)
166 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
167 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
169 for {relationship_type,
171 {outgoing_relation, outgoing_relation_target},
172 {incoming_relation, incoming_relation_source}
173 ]} <- @user_relationships_config do
174 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
175 # :notification_muter_mutes, :subscribee_subscriptions
176 has_many(outgoing_relation, UserRelationship,
177 foreign_key: :source_id,
178 where: [relationship_type: relationship_type]
181 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
182 # :notification_mutee_mutes, :subscriber_subscriptions
183 has_many(incoming_relation, UserRelationship,
184 foreign_key: :target_id,
185 where: [relationship_type: relationship_type]
188 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
189 # :notification_muted_users, :subscriber_users
190 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
192 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
193 # :notification_muter_users, :subscribee_users
194 has_many(incoming_relation_source, through: [incoming_relation, :source])
198 :multi_factor_authentication_settings,
206 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
207 @user_relationships_config do
208 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
209 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
210 # `def subscriber_users/2`
211 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
212 target_users_query = assoc(user, unquote(outgoing_relation_target))
214 if restrict_deactivated? do
216 |> User.Query.build(%{deactivated: false})
222 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
223 # `def notification_muted_users/2`, `def subscriber_users/2`
224 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
226 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
228 restrict_deactivated?
233 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
234 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
235 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
237 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
239 restrict_deactivated?
241 |> select([u], u.ap_id)
246 def cached_blocked_users_ap_ids(user) do
247 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
248 blocked_users_ap_ids(user)
252 def cached_muted_users_ap_ids(user) do
253 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
254 muted_users_ap_ids(user)
258 defdelegate following_count(user), to: FollowingRelationship
259 defdelegate following(user), to: FollowingRelationship
260 defdelegate following?(follower, followed), to: FollowingRelationship
261 defdelegate following_ap_ids(user), to: FollowingRelationship
262 defdelegate get_follow_requests(user), to: FollowingRelationship
263 defdelegate search(query, opts \\ []), to: User.Search
266 Dumps Flake Id to SQL-compatible format (16-byte UUID).
267 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
269 def binary_id(source_id) when is_binary(source_id) do
270 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
277 def binary_id(source_ids) when is_list(source_ids) do
278 Enum.map(source_ids, &binary_id/1)
281 def binary_id(%User{} = user), do: binary_id(user.id)
283 @doc "Returns status account"
284 @spec account_status(User.t()) :: account_status()
285 def account_status(%User{is_active: false}), do: :deactivated
286 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
287 def account_status(%User{local: true, is_approved: false}), do: :approval_pending
288 def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
289 def account_status(%User{}), do: :active
291 @spec visible_for(User.t(), User.t() | nil) ::
294 | :restricted_unauthenticated
296 | :confirmation_pending
297 def visible_for(user, for_user \\ nil)
299 def visible_for(%User{invisible: true}, _), do: :invisible
301 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
303 def visible_for(%User{} = user, nil) do
304 if restrict_unauthenticated?(user) do
305 :restrict_unauthenticated
307 visible_account_status(user)
311 def visible_for(%User{} = user, for_user) do
312 if superuser?(for_user) do
315 visible_account_status(user)
319 def visible_for(_, _), do: :invisible
321 defp restrict_unauthenticated?(%User{local: true}) do
322 Config.restrict_unauthenticated_access?(:profiles, :local)
325 defp restrict_unauthenticated?(%User{local: _}) do
326 Config.restrict_unauthenticated_access?(:profiles, :remote)
329 defp visible_account_status(user) do
330 status = account_status(user)
332 if status in [:active, :password_reset_pending] do
339 @spec superuser?(User.t()) :: boolean()
340 def superuser?(%User{local: true, is_admin: true}), do: true
341 def superuser?(%User{local: true, is_moderator: true}), do: true
342 def superuser?(_), do: false
344 @spec invisible?(User.t()) :: boolean()
345 def invisible?(%User{invisible: true}), do: true
346 def invisible?(_), do: false
348 def avatar_url(user, options \\ []) do
350 %{"url" => [%{"href" => href} | _]} ->
354 unless options[:no_default] do
355 Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
360 def banner_url(user, options \\ []) do
362 %{"url" => [%{"href" => href} | _]} -> href
363 _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
367 # Should probably be renamed or removed
368 @spec ap_id(User.t()) :: String.t()
369 def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
371 @spec ap_followers(User.t()) :: String.t()
372 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
373 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
375 @spec ap_following(User.t()) :: String.t()
376 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
377 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
379 @spec ap_featured_collection(User.t()) :: String.t()
380 def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
382 def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
384 defp truncate_fields_param(params) do
385 if Map.has_key?(params, :fields) do
386 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
392 defp truncate_if_exists(params, key, max_length) do
393 if Map.has_key?(params, key) and is_binary(params[key]) do
394 {value, _chopped} = String.split_at(params[key], max_length)
395 Map.put(params, key, value)
401 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
403 defp fix_follower_address(%{nickname: nickname} = params),
404 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
406 defp fix_follower_address(params), do: params
408 def remote_user_changeset(struct \\ %User{local: false}, params) do
409 bio_limit = Config.get([:instance, :user_bio_length], 5000)
410 name_limit = Config.get([:instance, :user_name_length], 100)
413 case params[:name] do
414 name when is_binary(name) and byte_size(name) > 0 -> name
415 _ -> params[:nickname]
420 |> Map.put(:name, name)
421 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
422 |> truncate_if_exists(:name, name_limit)
423 |> truncate_if_exists(:bio, bio_limit)
424 |> truncate_fields_param()
425 |> fix_follower_address()
449 :hide_followers_count,
458 :accepts_chat_messages,
462 |> cast(params, [:name], empty_values: [])
463 |> validate_required([:ap_id])
464 |> validate_required([:name], trim: false)
465 |> unique_constraint(:nickname)
466 |> validate_format(:nickname, @email_regex)
467 |> validate_length(:bio, max: bio_limit)
468 |> validate_length(:name, max: name_limit)
469 |> validate_fields(true)
470 |> validate_non_local()
473 defp validate_non_local(cng) do
474 local? = get_field(cng, :local)
478 |> add_error(:local, "User is local, can't update with this changeset.")
484 def update_changeset(struct, params \\ %{}) do
485 bio_limit = Config.get([:instance, :user_bio_length], 5000)
486 name_limit = Config.get([:instance, :user_name_length], 100)
506 :hide_followers_count,
509 :allow_following_move,
513 :skip_thread_containment,
516 :pleroma_settings_store,
519 :accepts_chat_messages,
523 |> unique_constraint(:nickname)
524 |> validate_format(:nickname, local_nickname_regex())
525 |> validate_length(:bio, max: bio_limit)
526 |> validate_length(:name, min: 1, max: name_limit)
527 |> validate_inclusion(:actor_type, ["Person", "Service"])
530 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
531 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
532 |> put_change_if_present(:banner, &put_upload(&1, :banner))
533 |> put_change_if_present(:background, &put_upload(&1, :background))
534 |> put_change_if_present(
535 :pleroma_settings_store,
536 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
538 |> validate_fields(false)
541 defp put_fields(changeset) do
542 if raw_fields = get_change(changeset, :raw_fields) do
545 |> Enum.filter(fn %{"name" => n} -> n != "" end)
549 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
552 |> put_change(:raw_fields, raw_fields)
553 |> put_change(:fields, fields)
559 defp parse_fields(value) do
561 |> Formatter.linkify(mentions_format: :full)
565 defp put_emoji(changeset) do
566 emojified_fields = [:bio, :name, :raw_fields]
568 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
569 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
570 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
572 emoji = Map.merge(bio, name)
576 |> get_field(:raw_fields)
577 |> Enum.reduce(emoji, fn x, acc ->
578 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
581 put_change(changeset, :emoji, emoji)
587 defp put_change_if_present(changeset, map_field, value_function) do
588 with {:ok, value} <- fetch_change(changeset, map_field),
589 {:ok, new_value} <- value_function.(value) do
590 put_change(changeset, map_field, new_value)
596 defp put_upload(value, type) do
597 with %Plug.Upload{} <- value,
598 {:ok, object} <- ActivityPub.upload(value, type: type) do
603 def update_as_admin_changeset(struct, params) do
605 |> update_changeset(params)
606 |> cast(params, [:email])
607 |> delete_change(:also_known_as)
608 |> unique_constraint(:email)
609 |> validate_format(:email, @email_regex)
610 |> validate_inclusion(:actor_type, ["Person", "Service"])
613 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
614 def update_as_admin(user, params) do
615 params = Map.put(params, "password_confirmation", params["password"])
616 changeset = update_as_admin_changeset(user, params)
618 if params["password"] do
619 reset_password(user, changeset, params)
621 User.update_and_set_cache(changeset)
625 def password_update_changeset(struct, params) do
627 |> cast(params, [:password, :password_confirmation])
628 |> validate_required([:password, :password_confirmation])
629 |> validate_confirmation(:password)
630 |> put_password_hash()
631 |> put_change(:password_reset_pending, false)
634 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
635 def reset_password(%User{} = user, params) do
636 reset_password(user, user, params)
639 def reset_password(%User{id: user_id} = user, struct, params) do
642 |> Multi.update(:user, password_update_changeset(struct, params))
643 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
644 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
646 case Repo.transaction(multi) do
647 {:ok, %{user: user} = _} -> set_cache(user)
648 {:error, _, changeset, _} -> {:error, changeset}
652 def update_password_reset_pending(user, value) do
655 |> put_change(:password_reset_pending, value)
656 |> update_and_set_cache()
659 def force_password_reset_async(user) do
660 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
663 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
664 def force_password_reset(user), do: update_password_reset_pending(user, true)
666 def register_changeset(struct, params \\ %{}, opts \\ []) do
667 bio_limit = Config.get([:instance, :user_bio_length], 5000)
668 name_limit = Config.get([:instance, :user_name_length], 100)
669 reason_limit = Config.get([:instance, :registration_reason_length], 500)
670 params = Map.put_new(params, :accepts_chat_messages, true)
673 if is_nil(opts[:confirmed]) do
674 !Config.get([:instance, :account_activation_required])
680 if is_nil(opts[:approved]) do
681 !Config.get([:instance, :account_approval_required])
687 |> confirmation_changeset(set_confirmation: confirmed?)
688 |> approval_changeset(set_approval: approved?)
696 :password_confirmation,
698 :accepts_chat_messages,
699 :registration_reason,
702 |> validate_required([:name, :nickname, :password, :password_confirmation])
703 |> validate_confirmation(:password)
704 |> unique_constraint(:email)
705 |> validate_format(:email, @email_regex)
706 |> validate_change(:email, fn :email, email ->
708 Config.get([User, :email_blacklist])
709 |> Enum.all?(fn blacklisted_domain ->
710 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
713 if valid?, do: [], else: [email: "Invalid email"]
715 |> unique_constraint(:nickname)
716 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
717 |> validate_format(:nickname, local_nickname_regex())
718 |> validate_length(:bio, max: bio_limit)
719 |> validate_length(:name, min: 1, max: name_limit)
720 |> validate_length(:registration_reason, max: reason_limit)
721 |> maybe_validate_required_email(opts[:external])
724 |> unique_constraint(:ap_id)
725 |> put_following_and_follower_and_featured_address()
728 def maybe_validate_required_email(changeset, true), do: changeset
730 def maybe_validate_required_email(changeset, _) do
731 if Config.get([:instance, :account_activation_required]) do
732 validate_required(changeset, [:email])
738 defp put_ap_id(changeset) do
739 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
740 put_change(changeset, :ap_id, ap_id)
743 defp put_following_and_follower_and_featured_address(changeset) do
744 user = %User{nickname: get_field(changeset, :nickname)}
745 followers = ap_followers(user)
746 following = ap_following(user)
747 featured = ap_featured_collection(user)
750 |> put_change(:follower_address, followers)
751 |> put_change(:following_address, following)
752 |> put_change(:featured_address, featured)
755 defp autofollow_users(user) do
756 candidates = Config.get([:instance, :autofollowed_nicknames])
759 User.Query.build(%{nickname: candidates, local: true, is_active: true})
762 follow_all(user, autofollowed_users)
765 defp autofollowing_users(user) do
766 candidates = Config.get([:instance, :autofollowing_nicknames])
768 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
770 |> Enum.each(&follow(&1, user, :follow_accept))
775 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
776 def register(%Ecto.Changeset{} = changeset) do
777 with {:ok, user} <- Repo.insert(changeset) do
778 post_register_action(user)
782 def post_register_action(%User{is_confirmed: false} = user) do
783 with {:ok, _} <- maybe_send_confirmation_email(user) do
788 def post_register_action(%User{is_approved: false} = user) do
789 with {:ok, _} <- send_user_approval_email(user),
790 {:ok, _} <- send_admin_approval_emails(user) do
795 def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
796 with {:ok, user} <- autofollow_users(user),
797 {:ok, _} <- autofollowing_users(user),
798 {:ok, user} <- set_cache(user),
799 {:ok, _} <- maybe_send_registration_email(user),
800 {:ok, _} <- maybe_send_welcome_email(user),
801 {:ok, _} <- maybe_send_welcome_message(user),
802 {:ok, _} <- maybe_send_welcome_chat_message(user) do
807 defp send_user_approval_email(user) do
809 |> Pleroma.Emails.UserEmail.approval_pending_email()
810 |> Pleroma.Emails.Mailer.deliver_async()
815 defp send_admin_approval_emails(user) do
817 |> Enum.filter(fn user -> not is_nil(user.email) end)
818 |> Enum.each(fn superuser ->
820 |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
821 |> Pleroma.Emails.Mailer.deliver_async()
827 defp maybe_send_welcome_message(user) do
828 if User.WelcomeMessage.enabled?() do
829 User.WelcomeMessage.post_message(user)
836 defp maybe_send_welcome_chat_message(user) do
837 if User.WelcomeChatMessage.enabled?() do
838 User.WelcomeChatMessage.post_message(user)
845 defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
846 if User.WelcomeEmail.enabled?() do
847 User.WelcomeEmail.send_email(user)
854 defp maybe_send_welcome_email(_), do: {:ok, :noop}
856 @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
857 def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
858 when is_binary(email) do
859 if Config.get([:instance, :account_activation_required]) do
860 send_confirmation_email(user)
867 def maybe_send_confirmation_email(_), do: {:ok, :noop}
869 @spec send_confirmation_email(Uset.t()) :: User.t()
870 def send_confirmation_email(%User{} = user) do
872 |> Pleroma.Emails.UserEmail.account_confirmation_email()
873 |> Pleroma.Emails.Mailer.deliver_async()
878 @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
879 defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
880 with false <- User.WelcomeEmail.enabled?(),
881 false <- Config.get([:instance, :account_activation_required], false),
882 false <- Config.get([:instance, :account_approval_required], false) do
884 |> Pleroma.Emails.UserEmail.successful_registration_email()
885 |> Pleroma.Emails.Mailer.deliver_async()
894 defp maybe_send_registration_email(_), do: {:ok, :noop}
896 def needs_update?(%User{local: true}), do: false
898 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
900 def needs_update?(%User{local: false} = user) do
901 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
904 def needs_update?(_), do: true
906 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
908 # "Locked" (self-locked) users demand explicit authorization of follow requests
909 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
910 follow(follower, followed, :follow_pending)
913 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
914 follow(follower, followed)
917 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
918 if not ap_enabled?(followed) do
919 follow(follower, followed)
921 {:ok, follower, followed}
925 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
926 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
927 def follow_all(follower, followeds) do
929 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
930 |> Enum.each(&follow(follower, &1, :follow_accept))
935 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
936 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
939 not followed.is_active ->
940 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
942 deny_follow_blocked and blocks?(followed, follower) ->
943 {:error, "Could not follow user: #{followed.nickname} blocked you."}
946 FollowingRelationship.follow(follower, followed, state)
950 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
951 {:error, "Not subscribed!"}
954 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
955 def unfollow(%User{} = follower, %User{} = followed) do
956 case do_unfollow(follower, followed) do
957 {:ok, follower, followed} ->
958 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
965 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
966 defp do_unfollow(%User{} = follower, %User{} = followed) do
967 case get_follow_state(follower, followed) do
968 state when state in [:follow_pending, :follow_accept] ->
969 FollowingRelationship.unfollow(follower, followed)
972 {:error, "Not subscribed!"}
976 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
977 def get_follow_state(%User{} = follower, %User{} = following) do
978 following_relationship = FollowingRelationship.get(follower, following)
979 get_follow_state(follower, following, following_relationship)
982 def get_follow_state(
985 following_relationship
987 case {following_relationship, following.local} do
989 case Utils.fetch_latest_follow(follower, following) do
990 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
991 FollowingRelationship.state_to_enum(state)
997 {%{state: state}, _} ->
1005 def locked?(%User{} = user) do
1006 user.is_locked || false
1009 def get_by_id(id) do
1010 Repo.get_by(User, id: id)
1013 def get_by_ap_id(ap_id) do
1014 Repo.get_by(User, ap_id: ap_id)
1017 def get_all_by_ap_id(ap_ids) do
1018 from(u in __MODULE__,
1019 where: u.ap_id in ^ap_ids
1024 def get_all_by_ids(ids) do
1025 from(u in __MODULE__, where: u.id in ^ids)
1029 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1030 # of the ap_id and the domain and tries to get that user
1031 def get_by_guessed_nickname(ap_id) do
1032 domain = URI.parse(ap_id).host
1033 name = List.last(String.split(ap_id, "/"))
1034 nickname = "#{name}@#{domain}"
1036 get_cached_by_nickname(nickname)
1039 def set_cache({:ok, user}), do: set_cache(user)
1040 def set_cache({:error, err}), do: {:error, err}
1042 def set_cache(%User{} = user) do
1043 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1044 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1045 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1049 def update_and_set_cache(struct, params) do
1051 |> update_changeset(params)
1052 |> update_and_set_cache()
1055 def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
1056 was_superuser_before_update = User.superuser?(user)
1058 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1061 |> maybe_remove_report_notifications(was_superuser_before_update)
1064 defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
1065 if not User.superuser?(user),
1066 do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
1071 defp maybe_remove_report_notifications(result, _) do
1075 def get_user_friends_ap_ids(user) do
1076 from(u in User.get_friends_query(user), select: u.ap_id)
1080 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1081 def get_cached_user_friends_ap_ids(user) do
1082 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1083 get_user_friends_ap_ids(user)
1087 def invalidate_cache(user) do
1088 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1089 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1090 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1091 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1092 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1095 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1096 def get_cached_by_ap_id(ap_id) do
1097 key = "ap_id:#{ap_id}"
1099 with {:ok, nil} <- @cachex.get(:user_cache, key),
1100 user when not is_nil(user) <- get_by_ap_id(ap_id),
1101 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1109 def get_cached_by_id(id) do
1113 @cachex.fetch!(:user_cache, key, fn _ ->
1114 user = get_by_id(id)
1117 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1118 {:commit, user.ap_id}
1124 get_cached_by_ap_id(ap_id)
1127 def get_cached_by_nickname(nickname) do
1128 key = "nickname:#{nickname}"
1130 @cachex.fetch!(:user_cache, key, fn _ ->
1131 case get_or_fetch_by_nickname(nickname) do
1132 {:ok, user} -> {:commit, user}
1133 {:error, _error} -> {:ignore, nil}
1138 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1139 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1142 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1143 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1145 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1146 get_cached_by_nickname(nickname_or_id)
1148 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1149 get_cached_by_nickname(nickname_or_id)
1156 @spec get_by_nickname(String.t()) :: User.t() | nil
1157 def get_by_nickname(nickname) do
1158 Repo.get_by(User, nickname: nickname) ||
1159 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1160 Repo.get_by(User, nickname: local_nickname(nickname))
1164 def get_by_email(email), do: Repo.get_by(User, email: email)
1166 def get_by_nickname_or_email(nickname_or_email) do
1167 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1170 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1172 def get_or_fetch_by_nickname(nickname) do
1173 with %User{} = user <- get_by_nickname(nickname) do
1177 with [_nick, _domain] <- String.split(nickname, "@"),
1178 {:ok, user} <- fetch_by_nickname(nickname) do
1181 _e -> {:error, "not found " <> nickname}
1186 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1187 def get_followers_query(%User{} = user, nil) do
1188 User.Query.build(%{followers: user, is_active: true})
1191 def get_followers_query(%User{} = user, page) do
1193 |> get_followers_query(nil)
1194 |> User.Query.paginate(page, 20)
1197 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1198 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1200 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1201 def get_followers(%User{} = user, page \\ nil) do
1203 |> get_followers_query(page)
1207 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1208 def get_external_followers(%User{} = user, page \\ nil) do
1210 |> get_followers_query(page)
1211 |> User.Query.build(%{external: true})
1215 def get_followers_ids(%User{} = user, page \\ nil) do
1217 |> get_followers_query(page)
1218 |> select([u], u.id)
1222 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1223 def get_friends_query(%User{} = user, nil) do
1224 User.Query.build(%{friends: user, deactivated: false})
1227 def get_friends_query(%User{} = user, page) do
1229 |> get_friends_query(nil)
1230 |> User.Query.paginate(page, 20)
1233 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1234 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1236 def get_friends(%User{} = user, page \\ nil) do
1238 |> get_friends_query(page)
1242 def get_friends_ap_ids(%User{} = user) do
1244 |> get_friends_query(nil)
1245 |> select([u], u.ap_id)
1249 def get_friends_ids(%User{} = user, page \\ nil) do
1251 |> get_friends_query(page)
1252 |> select([u], u.id)
1256 def increase_note_count(%User{} = user) do
1258 |> where(id: ^user.id)
1259 |> update([u], inc: [note_count: 1])
1261 |> Repo.update_all([])
1263 {1, [user]} -> set_cache(user)
1268 def decrease_note_count(%User{} = user) do
1270 |> where(id: ^user.id)
1273 note_count: fragment("greatest(0, note_count - 1)")
1277 |> Repo.update_all([])
1279 {1, [user]} -> set_cache(user)
1284 def update_note_count(%User{} = user, note_count \\ nil) do
1289 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1295 |> cast(%{note_count: note_count}, [:note_count])
1296 |> update_and_set_cache()
1299 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1300 def maybe_fetch_follow_information(user) do
1301 with {:ok, user} <- fetch_follow_information(user) do
1305 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1311 def fetch_follow_information(user) do
1312 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1314 |> follow_information_changeset(info)
1315 |> update_and_set_cache()
1319 defp follow_information_changeset(user, params) do
1326 :hide_followers_count,
1331 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1332 def update_follower_count(%User{} = user) do
1333 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1334 follower_count = FollowingRelationship.follower_count(user)
1337 |> follow_information_changeset(%{follower_count: follower_count})
1338 |> update_and_set_cache
1340 {:ok, maybe_fetch_follow_information(user)}
1344 @spec update_following_count(User.t()) :: {:ok, User.t()}
1345 def update_following_count(%User{local: false} = user) do
1346 if Config.get([:instance, :external_user_synchronization]) do
1347 {:ok, maybe_fetch_follow_information(user)}
1353 def update_following_count(%User{local: true} = user) do
1354 following_count = FollowingRelationship.following_count(user)
1357 |> follow_information_changeset(%{following_count: following_count})
1358 |> update_and_set_cache()
1361 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1362 def get_users_from_set(ap_ids, opts \\ []) do
1363 local_only = Keyword.get(opts, :local_only, true)
1364 criteria = %{ap_id: ap_ids, is_active: true}
1365 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1367 User.Query.build(criteria)
1371 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1372 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1375 query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
1381 @spec mute(User.t(), User.t(), map()) ::
1382 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1383 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1384 notifications? = Map.get(params, :notifications, true)
1385 expires_in = Map.get(params, :expires_in, 0)
1387 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1388 {:ok, user_notification_mute} <-
1389 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1391 if expires_in > 0 do
1392 Pleroma.Workers.MuteExpireWorker.enqueue(
1394 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1395 schedule_in: expires_in
1399 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1401 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1405 def unmute(%User{} = muter, %User{} = mutee) do
1406 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1407 {:ok, user_notification_mute} <-
1408 UserRelationship.delete_notification_mute(muter, mutee) do
1409 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1410 {:ok, [user_mute, user_notification_mute]}
1414 def unmute(muter_id, mutee_id) do
1415 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1416 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1417 unmute(muter, mutee)
1419 {who, result} = error ->
1421 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1428 def subscribe(%User{} = subscriber, %User{} = target) do
1429 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1431 if blocks?(target, subscriber) and deny_follow_blocked do
1432 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1434 # Note: the relationship is inverse: subscriber acts as relationship target
1435 UserRelationship.create_inverse_subscription(target, subscriber)
1439 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1440 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1441 subscribe(subscriber, subscribee)
1445 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1446 # Note: the relationship is inverse: subscriber acts as relationship target
1447 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1450 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1451 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1452 unsubscribe(unsubscriber, user)
1456 def block(%User{} = blocker, %User{} = blocked) do
1457 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1459 if following?(blocker, blocked) do
1460 {:ok, blocker, _} = unfollow(blocker, blocked)
1466 # clear any requested follows as well
1468 case CommonAPI.reject_follow_request(blocked, blocker) do
1469 {:ok, %User{} = updated_blocked} -> updated_blocked
1473 unsubscribe(blocked, blocker)
1475 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1476 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1478 {:ok, blocker} = update_follower_count(blocker)
1479 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1480 add_to_block(blocker, blocked)
1483 # helper to handle the block given only an actor's AP id
1484 def block(%User{} = blocker, %{ap_id: ap_id}) do
1485 block(blocker, get_cached_by_ap_id(ap_id))
1488 def unblock(%User{} = blocker, %User{} = blocked) do
1489 remove_from_block(blocker, blocked)
1492 # helper to handle the block given only an actor's AP id
1493 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1494 unblock(blocker, get_cached_by_ap_id(ap_id))
1497 def mutes?(nil, _), do: false
1498 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1500 def mutes_user?(%User{} = user, %User{} = target) do
1501 UserRelationship.mute_exists?(user, target)
1504 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1505 def muted_notifications?(nil, _), do: false
1507 def muted_notifications?(%User{} = user, %User{} = target),
1508 do: UserRelationship.notification_mute_exists?(user, target)
1510 def blocks?(nil, _), do: false
1512 def blocks?(%User{} = user, %User{} = target) do
1513 blocks_user?(user, target) ||
1514 (blocks_domain?(user, target) and not User.following?(user, target))
1517 def blocks_user?(%User{} = user, %User{} = target) do
1518 UserRelationship.block_exists?(user, target)
1521 def blocks_user?(_, _), do: false
1523 def blocks_domain?(%User{} = user, %User{} = target) do
1524 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1525 %{host: host} = URI.parse(target.ap_id)
1526 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1529 def blocks_domain?(_, _), do: false
1531 def subscribed_to?(%User{} = user, %User{} = target) do
1532 # Note: the relationship is inverse: subscriber acts as relationship target
1533 UserRelationship.inverse_subscription_exists?(target, user)
1536 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1537 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1538 subscribed_to?(user, target)
1543 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1544 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1546 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1547 def outgoing_relationships_ap_ids(_user, []), do: %{}
1549 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1551 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1552 when is_list(relationship_types) do
1555 |> assoc(:outgoing_relationships)
1556 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1557 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1558 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1559 |> group_by([user_rel, u], user_rel.relationship_type)
1561 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1566 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1570 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1572 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1574 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1576 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1577 when is_list(relationship_types) do
1579 |> assoc(:incoming_relationships)
1580 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1581 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1582 |> maybe_filter_on_ap_id(ap_ids)
1583 |> select([user_rel, u], u.ap_id)
1588 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1589 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1592 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1594 def set_activation_async(user, status \\ true) do
1595 BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
1598 @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1599 def set_activation(users, status) when is_list(users) do
1600 Repo.transaction(fn ->
1601 for user <- users, do: set_activation(user, status)
1605 @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1606 def set_activation(%User{} = user, status) do
1607 with {:ok, user} <- set_activation_status(user, status) do
1610 |> Enum.filter(& &1.local)
1611 |> Enum.each(&set_cache(update_following_count(&1)))
1613 # Only update local user counts, remote will be update during the next pull.
1616 |> Enum.filter(& &1.local)
1617 |> Enum.each(&do_unfollow(user, &1))
1623 def approve(users) when is_list(users) do
1624 Repo.transaction(fn ->
1625 Enum.map(users, fn user ->
1626 with {:ok, user} <- approve(user), do: user
1631 def approve(%User{is_approved: false} = user) do
1632 with chg <- change(user, is_approved: true),
1633 {:ok, user} <- update_and_set_cache(chg) do
1634 post_register_action(user)
1639 def approve(%User{} = user), do: {:ok, user}
1641 def confirm(users) when is_list(users) do
1642 Repo.transaction(fn ->
1643 Enum.map(users, fn user ->
1644 with {:ok, user} <- confirm(user), do: user
1649 def confirm(%User{is_confirmed: false} = user) do
1650 with chg <- confirmation_changeset(user, set_confirmation: true),
1651 {:ok, user} <- update_and_set_cache(chg) do
1652 post_register_action(user)
1657 def confirm(%User{} = user), do: {:ok, user}
1659 def set_suggestion(users, is_suggested) when is_list(users) do
1660 Repo.transaction(fn ->
1661 Enum.map(users, fn user ->
1662 with {:ok, user} <- set_suggestion(user, is_suggested), do: user
1667 def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
1669 def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
1671 |> change(is_suggested: is_suggested)
1672 |> update_and_set_cache()
1675 def update_notification_settings(%User{} = user, settings) do
1677 |> cast(%{notification_settings: settings}, [])
1678 |> cast_embed(:notification_settings)
1679 |> validate_required([:notification_settings])
1680 |> update_and_set_cache()
1683 @spec purge_user_changeset(User.t()) :: Changeset.t()
1684 def purge_user_changeset(user) do
1685 # "Right to be forgotten"
1686 # https://gdpr.eu/right-to-be-forgotten/
1695 last_refreshed_at: nil,
1696 last_digest_emailed_at: nil,
1703 password_reset_pending: false,
1704 registration_reason: nil,
1705 confirmation_token: nil,
1709 is_moderator: false,
1711 mastofe_settings: nil,
1714 pleroma_settings_store: %{},
1717 is_discoverable: false,
1721 # nickname: preserved
1725 # Purge doesn't delete the user from the database.
1726 # It just nulls all its fields and deactivates it.
1727 # See `User.purge_user_changeset/1` above.
1728 defp purge(%User{} = user) do
1730 |> purge_user_changeset()
1731 |> update_and_set_cache()
1734 def delete(users) when is_list(users) do
1735 for user <- users, do: delete(user)
1738 def delete(%User{} = user) do
1739 # Purge the user immediately
1741 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1744 # *Actually* delete the user from the DB
1745 defp delete_from_db(%User{} = user) do
1746 invalidate_cache(user)
1750 # If the user never finalized their account, it's safe to delete them.
1751 defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
1752 do: delete_from_db(user)
1754 defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
1755 do: delete_from_db(user)
1757 defp maybe_delete_from_db(user), do: {:ok, user}
1759 def perform(:force_password_reset, user), do: force_password_reset(user)
1761 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1762 def perform(:delete, %User{} = user) do
1763 # Purge the user again, in case perform/2 is called directly
1766 # Remove all relationships
1769 |> Enum.each(fn follower ->
1770 ActivityPub.unfollow(follower, user)
1771 unfollow(follower, user)
1776 |> Enum.each(fn followed ->
1777 ActivityPub.unfollow(user, followed)
1778 unfollow(user, followed)
1781 delete_user_activities(user)
1782 delete_notifications_from_user_activities(user)
1783 delete_outgoing_pending_follow_requests(user)
1785 maybe_delete_from_db(user)
1788 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1790 @spec external_users_query() :: Ecto.Query.t()
1791 def external_users_query do
1799 @spec external_users(keyword()) :: [User.t()]
1800 def external_users(opts \\ []) do
1802 external_users_query()
1803 |> select([u], struct(u, [:id, :ap_id]))
1807 do: where(query, [u], u.id > ^opts[:max_id]),
1812 do: limit(query, ^opts[:limit]),
1818 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1820 |> join(:inner, [n], activity in assoc(n, :activity))
1821 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1822 |> Repo.delete_all()
1825 def delete_user_activities(%User{ap_id: ap_id} = user) do
1827 |> Activity.Queries.by_actor()
1828 |> Repo.chunk_stream(50, :batches)
1829 |> Stream.each(fn activities ->
1830 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1835 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1836 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1837 {:ok, delete_data, _} <- Builder.delete(user, object) do
1838 Pipeline.common_pipeline(delete_data, local: user.local)
1840 {:find_object, nil} ->
1841 # We have the create activity, but not the object, it was probably pruned.
1842 # Insert a tombstone and try again
1843 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1844 {:ok, _tombstone} <- Object.create(tombstone_data) do
1845 delete_activity(activity, user)
1849 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1850 Logger.error("Error: #{inspect(e)}")
1854 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1855 when type in ["Like", "Announce"] do
1856 {:ok, undo, _} = Builder.undo(user, activity)
1857 Pipeline.common_pipeline(undo, local: user.local)
1860 defp delete_activity(_activity, _user), do: "Doing nothing"
1862 defp delete_outgoing_pending_follow_requests(user) do
1864 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1865 |> Repo.delete_all()
1868 def html_filter_policy(%User{no_rich_text: true}) do
1869 Pleroma.HTML.Scrubber.TwitterText
1872 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1874 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1876 def get_or_fetch_by_ap_id(ap_id) do
1877 cached_user = get_cached_by_ap_id(ap_id)
1879 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1881 case {cached_user, maybe_fetched_user} do
1882 {_, {:ok, %User{} = user}} ->
1885 {%User{} = user, _} ->
1889 {:error, :not_found}
1894 Creates an internal service actor by URI if missing.
1895 Optionally takes nickname for addressing.
1897 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1898 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1900 case get_cached_by_ap_id(uri) do
1902 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1903 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1907 %User{invisible: false} = user ->
1917 @spec set_invisible(User.t()) :: {:ok, User.t()}
1918 defp set_invisible(user) do
1920 |> change(%{invisible: true})
1921 |> update_and_set_cache()
1924 @spec create_service_actor(String.t(), String.t()) ::
1925 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1926 defp create_service_actor(uri, nickname) do
1932 follower_address: uri <> "/followers"
1935 |> unique_constraint(:nickname)
1940 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1943 |> :public_key.pem_decode()
1945 |> :public_key.pem_entry_decode()
1950 def public_key(_), do: {:error, "key not found"}
1952 def get_public_key_for_ap_id(ap_id) do
1953 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1954 {:ok, public_key} <- public_key(user) do
1961 def ap_enabled?(%User{local: true}), do: true
1962 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1963 def ap_enabled?(_), do: false
1965 @doc "Gets or fetch a user by uri or nickname."
1966 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1967 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1968 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1970 # wait a period of time and return newest version of the User structs
1971 # this is because we have synchronous follow APIs and need to simulate them
1972 # with an async handshake
1973 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1974 with %User{} = a <- get_cached_by_id(a.id),
1975 %User{} = b <- get_cached_by_id(b.id) do
1982 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1983 with :ok <- :timer.sleep(timeout),
1984 %User{} = a <- get_cached_by_id(a.id),
1985 %User{} = b <- get_cached_by_id(b.id) do
1992 def parse_bio(bio) when is_binary(bio) and bio != "" do
1994 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1998 def parse_bio(_), do: ""
2000 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
2001 # TODO: get profile URLs other than user.ap_id
2002 profile_urls = [user.ap_id]
2005 |> CommonUtils.format_input("text/plain",
2006 mentions_format: :full,
2007 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
2012 def parse_bio(_, _), do: ""
2014 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2015 Repo.transaction(fn ->
2016 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2020 def tag(nickname, tags) when is_binary(nickname),
2021 do: tag(get_by_nickname(nickname), tags)
2023 def tag(%User{} = user, tags),
2024 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2026 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2027 Repo.transaction(fn ->
2028 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2032 def untag(nickname, tags) when is_binary(nickname),
2033 do: untag(get_by_nickname(nickname), tags)
2035 def untag(%User{} = user, tags),
2036 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2038 defp update_tags(%User{} = user, new_tags) do
2039 {:ok, updated_user} =
2041 |> change(%{tags: new_tags})
2042 |> update_and_set_cache()
2047 defp normalize_tags(tags) do
2050 |> Enum.map(&String.downcase/1)
2053 defp local_nickname_regex do
2054 if Config.get([:instance, :extended_nickname_format]) do
2055 @extended_local_nickname_regex
2057 @strict_local_nickname_regex
2061 def local_nickname(nickname_or_mention) do
2064 |> String.split("@")
2068 def full_nickname(%User{} = user) do
2069 if String.contains?(user.nickname, "@") do
2072 %{host: host} = URI.parse(user.ap_id)
2073 user.nickname <> "@" <> host
2077 def full_nickname(nickname_or_mention),
2078 do: String.trim_leading(nickname_or_mention, "@")
2080 def error_user(ap_id) do
2084 nickname: "erroruser@example.com",
2085 inserted_at: NaiveDateTime.utc_now()
2089 @spec all_superusers() :: [User.t()]
2090 def all_superusers do
2091 User.Query.build(%{super_users: true, local: true, is_active: true})
2095 def muting_reblogs?(%User{} = user, %User{} = target) do
2096 UserRelationship.reblog_mute_exists?(user, target)
2099 def showing_reblogs?(%User{} = user, %User{} = target) do
2100 not muting_reblogs?(user, target)
2104 The function returns a query to get users with no activity for given interval of days.
2105 Inactive users are those who didn't read any notification, or had any activity where
2106 the user is the activity's actor, during `inactivity_threshold` days.
2107 Deactivated users will not appear in this list.
2111 iex> Pleroma.User.list_inactive_users()
2114 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2115 def list_inactive_users_query(inactivity_threshold \\ 7) do
2116 negative_inactivity_threshold = -inactivity_threshold
2117 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2118 # Subqueries are not supported in `where` clauses, join gets too complicated.
2119 has_read_notifications =
2120 from(n in Pleroma.Notification,
2121 where: n.seen == true,
2123 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2126 |> Pleroma.Repo.all()
2128 from(u in Pleroma.User,
2129 left_join: a in Pleroma.Activity,
2130 on: u.ap_id == a.actor,
2131 where: not is_nil(u.nickname),
2132 where: u.is_active == ^true,
2133 where: u.id not in ^has_read_notifications,
2136 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2137 is_nil(max(a.inserted_at))
2142 Enable or disable email notifications for user
2146 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2147 Pleroma.User{email_notifications: %{"digest" => true}}
2149 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2150 Pleroma.User{email_notifications: %{"digest" => false}}
2152 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2153 {:ok, t()} | {:error, Ecto.Changeset.t()}
2154 def switch_email_notifications(user, type, status) do
2155 User.update_email_notifications(user, %{type => status})
2159 Set `last_digest_emailed_at` value for the user to current time
2161 @spec touch_last_digest_emailed_at(t()) :: t()
2162 def touch_last_digest_emailed_at(user) do
2163 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2165 {:ok, updated_user} =
2167 |> change(%{last_digest_emailed_at: now})
2168 |> update_and_set_cache()
2173 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2174 def set_confirmation(%User{} = user, bool) do
2176 |> confirmation_changeset(set_confirmation: bool)
2177 |> update_and_set_cache()
2180 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2184 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2185 # use instance-default
2186 config = Config.get([:assets, :mascots])
2187 default_mascot = Config.get([:assets, :default_mascot])
2188 mascot = Keyword.get(config, default_mascot)
2191 "id" => "default-mascot",
2192 "url" => mascot[:url],
2193 "preview_url" => mascot[:url],
2195 "mime_type" => mascot[:mime_type]
2200 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2202 def ensure_keys_present(%User{} = user) do
2203 with {:ok, pem} <- Keys.generate_rsa_pem() do
2205 |> cast(%{keys: pem}, [:keys])
2206 |> validate_required([:keys])
2207 |> update_and_set_cache()
2211 def get_ap_ids_by_nicknames(nicknames) do
2213 where: u.nickname in ^nicknames,
2219 defp put_password_hash(
2220 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2222 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2225 defp put_password_hash(changeset), do: changeset
2227 def is_internal_user?(%User{nickname: nil}), do: true
2228 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2229 def is_internal_user?(_), do: false
2231 # A hack because user delete activities have a fake id for whatever reason
2232 # TODO: Get rid of this
2233 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2235 def get_delivered_users_by_object_id(object_id) do
2237 inner_join: delivery in assoc(u, :deliveries),
2238 where: delivery.object_id == ^object_id
2243 def change_email(user, email) do
2245 |> cast(%{email: email}, [:email])
2246 |> maybe_validate_required_email(false)
2247 |> unique_constraint(:email)
2248 |> validate_format(:email, @email_regex)
2249 |> update_and_set_cache()
2252 def alias_users(user) do
2254 |> Enum.map(&User.get_cached_by_ap_id/1)
2255 |> Enum.filter(fn user -> user != nil end)
2258 def add_alias(user, new_alias_user) do
2259 current_aliases = user.also_known_as || []
2260 new_alias_ap_id = new_alias_user.ap_id
2262 if new_alias_ap_id in current_aliases do
2266 |> cast(%{also_known_as: current_aliases ++ [new_alias_ap_id]}, [:also_known_as])
2267 |> update_and_set_cache()
2271 def delete_alias(user, alias_user) do
2272 current_aliases = user.also_known_as || []
2273 alias_ap_id = alias_user.ap_id
2275 if alias_ap_id in current_aliases do
2277 |> cast(%{also_known_as: current_aliases -- [alias_ap_id]}, [:also_known_as])
2278 |> update_and_set_cache()
2280 {:error, :no_such_alias}
2284 # Internal function; public one is `deactivate/2`
2285 defp set_activation_status(user, status) do
2287 |> cast(%{is_active: status}, [:is_active])
2288 |> update_and_set_cache()
2291 def update_banner(user, banner) do
2293 |> cast(%{banner: banner}, [:banner])
2294 |> update_and_set_cache()
2297 def update_background(user, background) do
2299 |> cast(%{background: background}, [:background])
2300 |> update_and_set_cache()
2303 def validate_fields(changeset, remote? \\ false) do
2304 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2305 limit = Config.get([:instance, limit_name], 0)
2308 |> validate_length(:fields, max: limit)
2309 |> validate_change(:fields, fn :fields, fields ->
2310 if Enum.all?(fields, &valid_field?/1) do
2318 defp valid_field?(%{"name" => name, "value" => value}) do
2319 name_limit = Config.get([:instance, :account_field_name_length], 255)
2320 value_limit = Config.get([:instance, :account_field_value_length], 255)
2322 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2323 String.length(value) <= value_limit
2326 defp valid_field?(_), do: false
2328 defp truncate_field(%{"name" => name, "value" => value}) do
2330 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2333 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2335 %{"name" => name, "value" => value}
2338 def admin_api_update(user, params) do
2345 |> update_and_set_cache()
2348 @doc "Signs user out of all applications"
2349 def global_sign_out(user) do
2350 OAuth.Authorization.delete_user_authorizations(user)
2351 OAuth.Token.delete_user_tokens(user)
2354 def mascot_update(user, url) do
2356 |> cast(%{mascot: url}, [:mascot])
2357 |> validate_required([:mascot])
2358 |> update_and_set_cache()
2361 def mastodon_settings_update(user, settings) do
2363 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2364 |> validate_required([:mastofe_settings])
2365 |> update_and_set_cache()
2368 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2369 def confirmation_changeset(user, set_confirmation: confirmed?) do
2374 confirmation_token: nil
2378 is_confirmed: false,
2379 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2383 cast(user, params, [:is_confirmed, :confirmation_token])
2386 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2387 def approval_changeset(user, set_approval: approved?) do
2388 cast(user, %{is_approved: approved?}, [:is_approved])
2391 @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
2392 def add_pinned_object_id(%User{} = user, object_id) do
2393 if !user.pinned_objects[object_id] do
2394 params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
2397 |> cast(params, [:pinned_objects])
2398 |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
2399 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2401 if Enum.count(pinned_objects) <= max_pinned_statuses do
2404 [pinned_objects: "You have already pinned the maximum number of statuses"]
2410 |> update_and_set_cache()
2413 @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
2414 def remove_pinned_object_id(%User{} = user, object_id) do
2417 %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
2420 |> update_and_set_cache()
2423 def update_email_notifications(user, settings) do
2424 email_notifications =
2425 user.email_notifications
2426 |> Map.merge(settings)
2427 |> Map.take(["digest"])
2429 params = %{email_notifications: email_notifications}
2430 fields = [:email_notifications]
2433 |> cast(params, fields)
2434 |> validate_required(fields)
2435 |> update_and_set_cache()
2438 defp set_domain_blocks(user, domain_blocks) do
2439 params = %{domain_blocks: domain_blocks}
2442 |> cast(params, [:domain_blocks])
2443 |> validate_required([:domain_blocks])
2444 |> update_and_set_cache()
2447 def block_domain(user, domain_blocked) do
2448 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2451 def unblock_domain(user, domain_blocked) do
2452 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2455 @spec add_to_block(User.t(), User.t()) ::
2456 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2457 defp add_to_block(%User{} = user, %User{} = blocked) do
2458 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2459 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2464 @spec add_to_block(User.t(), User.t()) ::
2465 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2466 defp remove_from_block(%User{} = user, %User{} = blocked) do
2467 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2468 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2473 def set_invisible(user, invisible) do
2474 params = %{invisible: invisible}
2477 |> cast(params, [:invisible])
2478 |> validate_required([:invisible])
2479 |> update_and_set_cache()
2482 def sanitize_html(%User{} = user) do
2483 sanitize_html(user, nil)
2486 # User data that mastodon isn't filtering (treated as plaintext):
2489 def sanitize_html(%User{} = user, filter) do
2491 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2494 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2499 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2500 |> Map.put(:fields, fields)
2503 def get_host(%User{ap_id: ap_id} = _user) do
2504 URI.parse(ap_id).host
2507 def update_last_active_at(%__MODULE__{local: true} = user) do
2509 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2510 |> update_and_set_cache()
2513 def active_user_count(days \\ 30) do
2514 active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
2517 |> where([u], u.last_active_at >= ^active_after)
2518 |> where([u], u.local == true)
2519 |> Repo.aggregate(:count)
2522 def update_last_status_at(user) do
2524 |> where(id: ^user.id)
2525 |> update([u], set: [last_status_at: fragment("NOW()")])
2527 |> Repo.update_all([])
2529 {1, [user]} -> set_cache(user)