1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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
31 alias Pleroma.Web.ActivityPub.ActivityPub
32 alias Pleroma.Web.ActivityPub.Builder
33 alias Pleroma.Web.ActivityPub.Pipeline
34 alias Pleroma.Web.ActivityPub.Utils
35 alias Pleroma.Web.CommonAPI
36 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
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
85 field(:bio, :string, default: "")
86 field(:raw_bio, :string)
87 field(:email, :string)
89 field(:nickname, :string)
90 field(:password_hash, :string)
91 field(:password, :string, virtual: true)
92 field(:password_confirmation, :string, virtual: true)
94 field(:public_key, :string)
95 field(:ap_id, :string)
96 field(:avatar, :map, default: %{})
97 field(:local, :boolean, default: true)
98 field(:follower_address, :string)
99 field(:following_address, :string)
100 field(:search_rank, :float, virtual: true)
101 field(:search_type, :integer, virtual: true)
102 field(:tags, {:array, :string}, default: [])
103 field(:last_refreshed_at, :naive_datetime_usec)
104 field(:last_digest_emailed_at, :naive_datetime)
105 field(:banner, :map, default: %{})
106 field(:background, :map, default: %{})
107 field(:note_count, :integer, default: 0)
108 field(:follower_count, :integer, default: 0)
109 field(:following_count, :integer, default: 0)
110 field(:is_locked, :boolean, default: false)
111 field(:confirmation_pending, :boolean, default: false)
112 field(:password_reset_pending, :boolean, default: false)
113 field(:approval_pending, :boolean, default: false)
114 field(:registration_reason, :string, default: nil)
115 field(:confirmation_token, :string, default: nil)
116 field(:default_scope, :string, default: "public")
117 field(:domain_blocks, {:array, :string}, default: [])
118 field(:deactivated, :boolean, default: false)
119 field(:no_rich_text, :boolean, default: false)
120 field(:ap_enabled, :boolean, default: false)
121 field(:is_moderator, :boolean, default: false)
122 field(:is_admin, :boolean, default: false)
123 field(:show_role, :boolean, default: true)
124 field(:mastofe_settings, :map, default: nil)
125 field(:uri, ObjectValidators.Uri, default: nil)
126 field(:hide_followers_count, :boolean, default: false)
127 field(:hide_follows_count, :boolean, default: false)
128 field(:hide_followers, :boolean, default: false)
129 field(:hide_follows, :boolean, default: false)
130 field(:hide_favorites, :boolean, default: true)
131 field(:pinned_activities, {:array, :string}, default: [])
132 field(:email_notifications, :map, default: %{"digest" => false})
133 field(:mascot, :map, default: nil)
134 field(:emoji, :map, default: %{})
135 field(:pleroma_settings_store, :map, default: %{})
136 field(:fields, {:array, :map}, default: [])
137 field(:raw_fields, {:array, :map}, default: [])
138 field(:is_discoverable, :boolean, default: false)
139 field(:invisible, :boolean, default: false)
140 field(:allow_following_move, :boolean, default: true)
141 field(:skip_thread_containment, :boolean, default: false)
142 field(:actor_type, :string, default: "Person")
143 field(:also_known_as, {:array, :string}, default: [])
144 field(:inbox, :string)
145 field(:shared_inbox, :string)
146 field(:accepts_chat_messages, :boolean, default: nil)
149 :notification_settings,
150 Pleroma.User.NotificationSetting,
154 has_many(:notifications, Notification)
155 has_many(:registrations, Registration)
156 has_many(:deliveries, Delivery)
158 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
159 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
161 for {relationship_type,
163 {outgoing_relation, outgoing_relation_target},
164 {incoming_relation, incoming_relation_source}
165 ]} <- @user_relationships_config do
166 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
167 # :notification_muter_mutes, :subscribee_subscriptions
168 has_many(outgoing_relation, UserRelationship,
169 foreign_key: :source_id,
170 where: [relationship_type: relationship_type]
173 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
174 # :notification_mutee_mutes, :subscriber_subscriptions
175 has_many(incoming_relation, UserRelationship,
176 foreign_key: :target_id,
177 where: [relationship_type: relationship_type]
180 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
181 # :notification_muted_users, :subscriber_users
182 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
184 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
185 # :notification_muter_users, :subscribee_users
186 has_many(incoming_relation_source, through: [incoming_relation, :source])
189 # `:blocks` is deprecated (replaced with `blocked_users` relation)
190 field(:blocks, {:array, :string}, default: [])
191 # `:mutes` is deprecated (replaced with `muted_users` relation)
192 field(:mutes, {:array, :string}, default: [])
193 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
194 field(:muted_reblogs, {:array, :string}, default: [])
195 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
196 field(:muted_notifications, {:array, :string}, default: [])
197 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
198 field(:subscribers, {:array, :string}, default: [])
201 :multi_factor_authentication_settings,
209 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
210 @user_relationships_config do
211 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
212 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
213 # `def subscriber_users/2`
214 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
215 target_users_query = assoc(user, unquote(outgoing_relation_target))
217 if restrict_deactivated? do
218 restrict_deactivated(target_users_query)
224 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
225 # `def notification_muted_users/2`, `def subscriber_users/2`
226 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
228 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
230 restrict_deactivated?
235 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
236 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
237 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
239 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
241 restrict_deactivated?
243 |> select([u], u.ap_id)
248 def cached_blocked_users_ap_ids(user) do
249 Cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
250 blocked_users_ap_ids(user)
254 def cached_muted_users_ap_ids(user) do
255 Cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
256 muted_users_ap_ids(user)
260 defdelegate following_count(user), to: FollowingRelationship
261 defdelegate following(user), to: FollowingRelationship
262 defdelegate following?(follower, followed), to: FollowingRelationship
263 defdelegate following_ap_ids(user), to: FollowingRelationship
264 defdelegate get_follow_requests(user), to: FollowingRelationship
265 defdelegate search(query, opts \\ []), to: User.Search
268 Dumps Flake Id to SQL-compatible format (16-byte UUID).
269 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
271 def binary_id(source_id) when is_binary(source_id) do
272 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
279 def binary_id(source_ids) when is_list(source_ids) do
280 Enum.map(source_ids, &binary_id/1)
283 def binary_id(%User{} = user), do: binary_id(user.id)
285 @doc "Returns status account"
286 @spec account_status(User.t()) :: account_status()
287 def account_status(%User{deactivated: true}), do: :deactivated
288 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
289 def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
291 def account_status(%User{local: true, confirmation_pending: true}) do
292 if Config.get([:instance, :account_activation_required]) do
293 :confirmation_pending
299 def account_status(%User{}), do: :active
301 @spec visible_for(User.t(), User.t() | nil) ::
304 | :restricted_unauthenticated
306 | :confirmation_pending
307 def visible_for(user, for_user \\ nil)
309 def visible_for(%User{invisible: true}, _), do: :invisible
311 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
313 def visible_for(%User{} = user, nil) do
314 if restrict_unauthenticated?(user) do
315 :restrict_unauthenticated
317 visible_account_status(user)
321 def visible_for(%User{} = user, for_user) do
322 if superuser?(for_user) do
325 visible_account_status(user)
329 def visible_for(_, _), do: :invisible
331 defp restrict_unauthenticated?(%User{local: true}) do
332 Config.restrict_unauthenticated_access?(:profiles, :local)
335 defp restrict_unauthenticated?(%User{local: _}) do
336 Config.restrict_unauthenticated_access?(:profiles, :remote)
339 defp visible_account_status(user) do
340 status = account_status(user)
342 if status in [:active, :password_reset_pending] do
349 @spec superuser?(User.t()) :: boolean()
350 def superuser?(%User{local: true, is_admin: true}), do: true
351 def superuser?(%User{local: true, is_moderator: true}), do: true
352 def superuser?(_), do: false
354 @spec invisible?(User.t()) :: boolean()
355 def invisible?(%User{invisible: true}), do: true
356 def invisible?(_), do: false
358 def avatar_url(user, options \\ []) do
360 %{"url" => [%{"href" => href} | _]} ->
364 unless options[:no_default] do
365 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
370 def banner_url(user, options \\ []) do
372 %{"url" => [%{"href" => href} | _]} -> href
373 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
377 # Should probably be renamed or removed
378 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
380 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
381 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
383 @spec ap_following(User.t()) :: String.t()
384 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
385 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
387 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
388 def restrict_deactivated(query) do
389 from(u in query, where: u.deactivated != ^true)
392 defp truncate_fields_param(params) do
393 if Map.has_key?(params, :fields) do
394 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
400 defp truncate_if_exists(params, key, max_length) do
401 if Map.has_key?(params, key) and is_binary(params[key]) do
402 {value, _chopped} = String.split_at(params[key], max_length)
403 Map.put(params, key, value)
409 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
411 defp fix_follower_address(%{nickname: nickname} = params),
412 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
414 defp fix_follower_address(params), do: params
416 def remote_user_changeset(struct \\ %User{local: false}, params) do
417 bio_limit = Config.get([:instance, :user_bio_length], 5000)
418 name_limit = Config.get([:instance, :user_name_length], 100)
421 case params[:name] do
422 name when is_binary(name) and byte_size(name) > 0 -> name
423 _ -> params[:nickname]
428 |> Map.put(:name, name)
429 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
430 |> truncate_if_exists(:name, name_limit)
431 |> truncate_if_exists(:bio, bio_limit)
432 |> truncate_fields_param()
433 |> fix_follower_address()
456 :hide_followers_count,
465 :accepts_chat_messages
468 |> cast(params, [:name], empty_values: [])
469 |> validate_required([:ap_id])
470 |> validate_required([:name], trim: false)
471 |> unique_constraint(:nickname)
472 |> validate_format(:nickname, @email_regex)
473 |> validate_length(:bio, max: bio_limit)
474 |> validate_length(:name, max: name_limit)
475 |> validate_inclusion(:local, [true])
476 |> validate_fields(true)
477 |> validate_non_local()
480 defp validate_non_local(cng) do
481 local? = get_field(cng, :local)
485 |> add_error(:local, "User is local, can't update with this changeset.")
491 def update_changeset(struct, params \\ %{}) do
492 bio_limit = Config.get([:instance, :user_bio_length], 5000)
493 name_limit = Config.get([:instance, :user_name_length], 100)
513 :hide_followers_count,
516 :allow_following_move,
519 :skip_thread_containment,
522 :pleroma_settings_store,
526 :accepts_chat_messages
529 |> unique_constraint(:nickname)
530 |> validate_format(:nickname, local_nickname_regex())
531 |> validate_length(:bio, max: bio_limit)
532 |> validate_length(:name, min: 1, max: name_limit)
533 |> validate_inclusion(:actor_type, ["Person", "Service"])
536 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
537 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
538 |> put_change_if_present(:banner, &put_upload(&1, :banner))
539 |> put_change_if_present(:background, &put_upload(&1, :background))
540 |> put_change_if_present(
541 :pleroma_settings_store,
542 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
544 |> validate_fields(false)
547 defp put_fields(changeset) do
548 if raw_fields = get_change(changeset, :raw_fields) do
551 |> Enum.filter(fn %{"name" => n} -> n != "" end)
555 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
558 |> put_change(:raw_fields, raw_fields)
559 |> put_change(:fields, fields)
565 defp parse_fields(value) do
567 |> Formatter.linkify(mentions_format: :full)
571 defp put_emoji(changeset) do
572 emojified_fields = [:bio, :name, :raw_fields]
574 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
575 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
576 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
578 emoji = Map.merge(bio, name)
582 |> get_field(:raw_fields)
583 |> Enum.reduce(emoji, fn x, acc ->
584 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
587 put_change(changeset, :emoji, emoji)
593 defp put_change_if_present(changeset, map_field, value_function) do
594 with {:ok, value} <- fetch_change(changeset, map_field),
595 {:ok, new_value} <- value_function.(value) do
596 put_change(changeset, map_field, new_value)
602 defp put_upload(value, type) do
603 with %Plug.Upload{} <- value,
604 {:ok, object} <- ActivityPub.upload(value, type: type) do
609 def update_as_admin_changeset(struct, params) do
611 |> update_changeset(params)
612 |> cast(params, [:email])
613 |> delete_change(:also_known_as)
614 |> unique_constraint(:email)
615 |> validate_format(:email, @email_regex)
616 |> validate_inclusion(:actor_type, ["Person", "Service"])
619 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
620 def update_as_admin(user, params) do
621 params = Map.put(params, "password_confirmation", params["password"])
622 changeset = update_as_admin_changeset(user, params)
624 if params["password"] do
625 reset_password(user, changeset, params)
627 User.update_and_set_cache(changeset)
631 def password_update_changeset(struct, params) do
633 |> cast(params, [:password, :password_confirmation])
634 |> validate_required([:password, :password_confirmation])
635 |> validate_confirmation(:password)
636 |> put_password_hash()
637 |> put_change(:password_reset_pending, false)
640 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
641 def reset_password(%User{} = user, params) do
642 reset_password(user, user, params)
645 def reset_password(%User{id: user_id} = user, struct, params) do
648 |> Multi.update(:user, password_update_changeset(struct, params))
649 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
650 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
652 case Repo.transaction(multi) do
653 {:ok, %{user: user} = _} -> set_cache(user)
654 {:error, _, changeset, _} -> {:error, changeset}
658 def update_password_reset_pending(user, value) do
661 |> put_change(:password_reset_pending, value)
662 |> update_and_set_cache()
665 def force_password_reset_async(user) do
666 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
669 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
670 def force_password_reset(user), do: update_password_reset_pending(user, true)
672 # Used to auto-register LDAP accounts which won't have a password hash stored locally
673 def register_changeset_ldap(struct, params = %{password: password})
674 when is_nil(password) do
675 params = Map.put_new(params, :accepts_chat_messages, true)
678 if Map.has_key?(params, :email) do
679 Map.put_new(params, :email, params[:email])
689 :accepts_chat_messages
691 |> validate_required([:name, :nickname])
692 |> unique_constraint(:nickname)
693 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
694 |> validate_format(:nickname, local_nickname_regex())
696 |> unique_constraint(:ap_id)
697 |> put_following_and_follower_address()
700 def register_changeset(struct, params \\ %{}, opts \\ []) do
701 bio_limit = Config.get([:instance, :user_bio_length], 5000)
702 name_limit = Config.get([:instance, :user_name_length], 100)
703 reason_limit = Config.get([:instance, :registration_reason_length], 500)
704 params = Map.put_new(params, :accepts_chat_messages, true)
707 if is_nil(opts[:need_confirmation]) do
708 Config.get([:instance, :account_activation_required])
710 opts[:need_confirmation]
714 if is_nil(opts[:need_approval]) do
715 Config.get([:instance, :account_approval_required])
721 |> confirmation_changeset(need_confirmation: need_confirmation?)
722 |> approval_changeset(need_approval: need_approval?)
730 :password_confirmation,
732 :accepts_chat_messages,
735 |> validate_required([:name, :nickname, :password, :password_confirmation])
736 |> validate_confirmation(:password)
737 |> unique_constraint(:email)
738 |> validate_format(:email, @email_regex)
739 |> validate_change(:email, fn :email, email ->
741 Config.get([User, :email_blacklist])
742 |> Enum.all?(fn blacklisted_domain ->
743 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
746 if valid?, do: [], else: [email: "Invalid email"]
748 |> unique_constraint(:nickname)
749 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
750 |> validate_format(:nickname, local_nickname_regex())
751 |> validate_length(:bio, max: bio_limit)
752 |> validate_length(:name, min: 1, max: name_limit)
753 |> validate_length(:registration_reason, max: reason_limit)
754 |> maybe_validate_required_email(opts[:external])
757 |> unique_constraint(:ap_id)
758 |> put_following_and_follower_address()
761 def maybe_validate_required_email(changeset, true), do: changeset
763 def maybe_validate_required_email(changeset, _) do
764 if Config.get([:instance, :account_activation_required]) do
765 validate_required(changeset, [:email])
771 defp put_ap_id(changeset) do
772 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
773 put_change(changeset, :ap_id, ap_id)
776 defp put_following_and_follower_address(changeset) do
777 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
780 |> put_change(:follower_address, followers)
783 defp autofollow_users(user) do
784 candidates = Config.get([:instance, :autofollowed_nicknames])
787 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
790 follow_all(user, autofollowed_users)
793 defp autofollowing_users(user) do
794 candidates = Config.get([:instance, :autofollowing_nicknames])
796 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
798 |> Enum.each(&follow(&1, user, :follow_accept))
803 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
804 def register(%Ecto.Changeset{} = changeset) do
805 with {:ok, user} <- Repo.insert(changeset) do
806 post_register_action(user)
810 def post_register_action(%User{} = user) do
811 with {:ok, user} <- autofollow_users(user),
812 {:ok, _} <- autofollowing_users(user),
813 {:ok, user} <- set_cache(user),
814 {:ok, _} <- send_welcome_email(user),
815 {:ok, _} <- send_welcome_message(user),
816 {:ok, _} <- send_welcome_chat_message(user),
817 {:ok, _} <- try_send_confirmation_email(user) do
822 def send_welcome_message(user) do
823 if User.WelcomeMessage.enabled?() do
824 User.WelcomeMessage.post_message(user)
831 def send_welcome_chat_message(user) do
832 if User.WelcomeChatMessage.enabled?() do
833 User.WelcomeChatMessage.post_message(user)
840 def send_welcome_email(%User{email: email} = user) when is_binary(email) do
841 if User.WelcomeEmail.enabled?() do
842 User.WelcomeEmail.send_email(user)
849 def send_welcome_email(_), do: {:ok, :noop}
851 @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
852 def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
853 when is_binary(email) do
854 if Config.get([:instance, :account_activation_required]) do
855 send_confirmation_email(user)
862 def try_send_confirmation_email(_), do: {:ok, :noop}
864 @spec send_confirmation_email(Uset.t()) :: User.t()
865 def send_confirmation_email(%User{} = user) do
867 |> Pleroma.Emails.UserEmail.account_confirmation_email()
868 |> Pleroma.Emails.Mailer.deliver_async()
873 def needs_update?(%User{local: true}), do: false
875 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
877 def needs_update?(%User{local: false} = user) do
878 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
881 def needs_update?(_), do: true
883 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
885 # "Locked" (self-locked) users demand explicit authorization of follow requests
886 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
887 follow(follower, followed, :follow_pending)
890 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
891 follow(follower, followed)
894 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
895 if not ap_enabled?(followed) do
896 follow(follower, followed)
902 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
903 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
904 def follow_all(follower, followeds) do
906 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
907 |> Enum.each(&follow(follower, &1, :follow_accept))
912 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
913 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
916 followed.deactivated ->
917 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
919 deny_follow_blocked and blocks?(followed, follower) ->
920 {:error, "Could not follow user: #{followed.nickname} blocked you."}
923 FollowingRelationship.follow(follower, followed, state)
925 {:ok, _} = update_follower_count(followed)
928 |> update_following_count()
932 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
933 {:error, "Not subscribed!"}
936 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
937 def unfollow(%User{} = follower, %User{} = followed) do
938 case do_unfollow(follower, followed) do
939 {:ok, follower, followed} ->
940 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
947 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
948 defp do_unfollow(%User{} = follower, %User{} = followed) do
949 case get_follow_state(follower, followed) do
950 state when state in [:follow_pending, :follow_accept] ->
951 FollowingRelationship.unfollow(follower, followed)
952 {:ok, followed} = update_follower_count(followed)
954 {:ok, follower} = update_following_count(follower)
956 {:ok, follower, followed}
959 {:error, "Not subscribed!"}
963 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
964 def get_follow_state(%User{} = follower, %User{} = following) do
965 following_relationship = FollowingRelationship.get(follower, following)
966 get_follow_state(follower, following, following_relationship)
969 def get_follow_state(
972 following_relationship
974 case {following_relationship, following.local} do
976 case Utils.fetch_latest_follow(follower, following) do
977 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
978 FollowingRelationship.state_to_enum(state)
984 {%{state: state}, _} ->
992 def locked?(%User{} = user) do
993 user.is_locked || false
997 Repo.get_by(User, id: id)
1000 def get_by_ap_id(ap_id) do
1001 Repo.get_by(User, ap_id: ap_id)
1004 def get_all_by_ap_id(ap_ids) do
1005 from(u in __MODULE__,
1006 where: u.ap_id in ^ap_ids
1011 def get_all_by_ids(ids) do
1012 from(u in __MODULE__, where: u.id in ^ids)
1016 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1017 # of the ap_id and the domain and tries to get that user
1018 def get_by_guessed_nickname(ap_id) do
1019 domain = URI.parse(ap_id).host
1020 name = List.last(String.split(ap_id, "/"))
1021 nickname = "#{name}@#{domain}"
1023 get_cached_by_nickname(nickname)
1026 def set_cache({:ok, user}), do: set_cache(user)
1027 def set_cache({:error, err}), do: {:error, err}
1029 def set_cache(%User{} = user) do
1030 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1031 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1032 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1036 def update_and_set_cache(struct, params) do
1038 |> update_changeset(params)
1039 |> update_and_set_cache()
1042 def update_and_set_cache(changeset) do
1043 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1048 def get_user_friends_ap_ids(user) do
1049 from(u in User.get_friends_query(user), select: u.ap_id)
1053 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1054 def get_cached_user_friends_ap_ids(user) do
1055 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1056 get_user_friends_ap_ids(user)
1060 def invalidate_cache(user) do
1061 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1062 Cachex.del(:user_cache, "nickname:#{user.nickname}")
1063 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1064 Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1065 Cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1068 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1069 def get_cached_by_ap_id(ap_id) do
1070 key = "ap_id:#{ap_id}"
1072 with {:ok, nil} <- Cachex.get(:user_cache, key),
1073 user when not is_nil(user) <- get_by_ap_id(ap_id),
1074 {:ok, true} <- Cachex.put(:user_cache, key, user) do
1082 def get_cached_by_id(id) do
1086 Cachex.fetch!(:user_cache, key, fn _ ->
1087 user = get_by_id(id)
1090 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1091 {:commit, user.ap_id}
1097 get_cached_by_ap_id(ap_id)
1100 def get_cached_by_nickname(nickname) do
1101 key = "nickname:#{nickname}"
1103 Cachex.fetch!(:user_cache, key, fn ->
1104 case get_or_fetch_by_nickname(nickname) do
1105 {:ok, user} -> {:commit, user}
1106 {:error, _error} -> {:ignore, nil}
1111 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1112 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1115 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1116 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1118 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1119 get_cached_by_nickname(nickname_or_id)
1121 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1122 get_cached_by_nickname(nickname_or_id)
1129 @spec get_by_nickname(String.t()) :: User.t() | nil
1130 def get_by_nickname(nickname) do
1131 Repo.get_by(User, nickname: nickname) ||
1132 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1133 Repo.get_by(User, nickname: local_nickname(nickname))
1137 def get_by_email(email), do: Repo.get_by(User, email: email)
1139 def get_by_nickname_or_email(nickname_or_email) do
1140 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1143 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1145 def get_or_fetch_by_nickname(nickname) do
1146 with %User{} = user <- get_by_nickname(nickname) do
1150 with [_nick, _domain] <- String.split(nickname, "@"),
1151 {:ok, user} <- fetch_by_nickname(nickname) do
1154 _e -> {:error, "not found " <> nickname}
1159 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1160 def get_followers_query(%User{} = user, nil) do
1161 User.Query.build(%{followers: user, deactivated: false})
1164 def get_followers_query(%User{} = user, page) do
1166 |> get_followers_query(nil)
1167 |> User.Query.paginate(page, 20)
1170 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1171 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1173 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1174 def get_followers(%User{} = user, page \\ nil) do
1176 |> get_followers_query(page)
1180 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1181 def get_external_followers(%User{} = user, page \\ nil) do
1183 |> get_followers_query(page)
1184 |> User.Query.build(%{external: true})
1188 def get_followers_ids(%User{} = user, page \\ nil) do
1190 |> get_followers_query(page)
1191 |> select([u], u.id)
1195 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1196 def get_friends_query(%User{} = user, nil) do
1197 User.Query.build(%{friends: user, deactivated: false})
1200 def get_friends_query(%User{} = user, page) do
1202 |> get_friends_query(nil)
1203 |> User.Query.paginate(page, 20)
1206 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1207 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1209 def get_friends(%User{} = user, page \\ nil) do
1211 |> get_friends_query(page)
1215 def get_friends_ap_ids(%User{} = user) do
1217 |> get_friends_query(nil)
1218 |> select([u], u.ap_id)
1222 def get_friends_ids(%User{} = user, page \\ nil) do
1224 |> get_friends_query(page)
1225 |> select([u], u.id)
1229 def increase_note_count(%User{} = user) do
1231 |> where(id: ^user.id)
1232 |> update([u], inc: [note_count: 1])
1234 |> Repo.update_all([])
1236 {1, [user]} -> set_cache(user)
1241 def decrease_note_count(%User{} = user) do
1243 |> where(id: ^user.id)
1246 note_count: fragment("greatest(0, note_count - 1)")
1250 |> Repo.update_all([])
1252 {1, [user]} -> set_cache(user)
1257 def update_note_count(%User{} = user, note_count \\ nil) do
1262 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1268 |> cast(%{note_count: note_count}, [:note_count])
1269 |> update_and_set_cache()
1272 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1273 def maybe_fetch_follow_information(user) do
1274 with {:ok, user} <- fetch_follow_information(user) do
1278 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1284 def fetch_follow_information(user) do
1285 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1287 |> follow_information_changeset(info)
1288 |> update_and_set_cache()
1292 defp follow_information_changeset(user, params) do
1299 :hide_followers_count,
1304 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1305 def update_follower_count(%User{} = user) do
1306 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1307 follower_count = FollowingRelationship.follower_count(user)
1310 |> follow_information_changeset(%{follower_count: follower_count})
1311 |> update_and_set_cache
1313 {:ok, maybe_fetch_follow_information(user)}
1317 @spec update_following_count(User.t()) :: {:ok, User.t()}
1318 def update_following_count(%User{local: false} = user) do
1319 if Config.get([:instance, :external_user_synchronization]) do
1320 {:ok, maybe_fetch_follow_information(user)}
1326 def update_following_count(%User{local: true} = user) do
1327 following_count = FollowingRelationship.following_count(user)
1330 |> follow_information_changeset(%{following_count: following_count})
1331 |> update_and_set_cache()
1334 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1335 def get_users_from_set(ap_ids, opts \\ []) do
1336 local_only = Keyword.get(opts, :local_only, true)
1337 criteria = %{ap_id: ap_ids, deactivated: false}
1338 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1340 User.Query.build(criteria)
1344 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1345 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1348 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1354 @spec mute(User.t(), User.t(), map()) ::
1355 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1356 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1357 notifications? = Map.get(params, :notifications, true)
1358 expires_in = Map.get(params, :expires_in, 0)
1360 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1361 {:ok, user_notification_mute} <-
1362 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1364 if expires_in > 0 do
1365 Pleroma.Workers.MuteExpireWorker.enqueue(
1367 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1368 schedule_in: expires_in
1372 Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1374 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1378 def unmute(%User{} = muter, %User{} = mutee) do
1379 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1380 {:ok, user_notification_mute} <-
1381 UserRelationship.delete_notification_mute(muter, mutee) do
1382 Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1383 {:ok, [user_mute, user_notification_mute]}
1387 def unmute(muter_id, mutee_id) do
1388 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1389 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1390 unmute(muter, mutee)
1392 {who, result} = error ->
1394 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1401 def subscribe(%User{} = subscriber, %User{} = target) do
1402 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1404 if blocks?(target, subscriber) and deny_follow_blocked do
1405 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1407 # Note: the relationship is inverse: subscriber acts as relationship target
1408 UserRelationship.create_inverse_subscription(target, subscriber)
1412 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1413 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1414 subscribe(subscriber, subscribee)
1418 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1419 # Note: the relationship is inverse: subscriber acts as relationship target
1420 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1423 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1424 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1425 unsubscribe(unsubscriber, user)
1429 def block(%User{} = blocker, %User{} = blocked) do
1430 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1432 if following?(blocker, blocked) do
1433 {:ok, blocker, _} = unfollow(blocker, blocked)
1439 # clear any requested follows as well
1441 case CommonAPI.reject_follow_request(blocked, blocker) do
1442 {:ok, %User{} = updated_blocked} -> updated_blocked
1446 unsubscribe(blocked, blocker)
1448 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1449 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1451 {:ok, blocker} = update_follower_count(blocker)
1452 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1453 add_to_block(blocker, blocked)
1456 # helper to handle the block given only an actor's AP id
1457 def block(%User{} = blocker, %{ap_id: ap_id}) do
1458 block(blocker, get_cached_by_ap_id(ap_id))
1461 def unblock(%User{} = blocker, %User{} = blocked) do
1462 remove_from_block(blocker, blocked)
1465 # helper to handle the block given only an actor's AP id
1466 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1467 unblock(blocker, get_cached_by_ap_id(ap_id))
1470 def mutes?(nil, _), do: false
1471 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1473 def mutes_user?(%User{} = user, %User{} = target) do
1474 UserRelationship.mute_exists?(user, target)
1477 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1478 def muted_notifications?(nil, _), do: false
1480 def muted_notifications?(%User{} = user, %User{} = target),
1481 do: UserRelationship.notification_mute_exists?(user, target)
1483 def blocks?(nil, _), do: false
1485 def blocks?(%User{} = user, %User{} = target) do
1486 blocks_user?(user, target) ||
1487 (blocks_domain?(user, target) and not User.following?(user, target))
1490 def blocks_user?(%User{} = user, %User{} = target) do
1491 UserRelationship.block_exists?(user, target)
1494 def blocks_user?(_, _), do: false
1496 def blocks_domain?(%User{} = user, %User{} = target) do
1497 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1498 %{host: host} = URI.parse(target.ap_id)
1499 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1502 def blocks_domain?(_, _), do: false
1504 def subscribed_to?(%User{} = user, %User{} = target) do
1505 # Note: the relationship is inverse: subscriber acts as relationship target
1506 UserRelationship.inverse_subscription_exists?(target, user)
1509 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1510 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1511 subscribed_to?(user, target)
1516 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1517 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1519 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1520 def outgoing_relationships_ap_ids(_user, []), do: %{}
1522 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1524 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1525 when is_list(relationship_types) do
1528 |> assoc(:outgoing_relationships)
1529 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1530 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1531 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1532 |> group_by([user_rel, u], user_rel.relationship_type)
1534 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1539 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1543 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1545 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1547 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1549 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1550 when is_list(relationship_types) do
1552 |> assoc(:incoming_relationships)
1553 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1554 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1555 |> maybe_filter_on_ap_id(ap_ids)
1556 |> select([user_rel, u], u.ap_id)
1561 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1562 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1565 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1567 def deactivate_async(user, status \\ true) do
1568 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1571 def deactivate(user, status \\ true)
1573 def deactivate(users, status) when is_list(users) do
1574 Repo.transaction(fn ->
1575 for user <- users, do: deactivate(user, status)
1579 def deactivate(%User{} = user, status) do
1580 with {:ok, user} <- set_activation_status(user, status) do
1583 |> Enum.filter(& &1.local)
1584 |> Enum.each(&set_cache(update_following_count(&1)))
1586 # Only update local user counts, remote will be update during the next pull.
1589 |> Enum.filter(& &1.local)
1590 |> Enum.each(&do_unfollow(user, &1))
1596 def approve(users) when is_list(users) do
1597 Repo.transaction(fn ->
1598 Enum.map(users, fn user ->
1599 with {:ok, user} <- approve(user), do: user
1604 def approve(%User{} = user) do
1605 change(user, approval_pending: false)
1606 |> update_and_set_cache()
1609 def update_notification_settings(%User{} = user, settings) do
1611 |> cast(%{notification_settings: settings}, [])
1612 |> cast_embed(:notification_settings)
1613 |> validate_required([:notification_settings])
1614 |> update_and_set_cache()
1617 @spec purge_user_changeset(User.t()) :: Changeset.t()
1618 def purge_user_changeset(user) do
1619 # "Right to be forgotten"
1620 # https://gdpr.eu/right-to-be-forgotten/
1631 last_refreshed_at: nil,
1632 last_digest_emailed_at: nil,
1639 confirmation_pending: false,
1640 password_reset_pending: false,
1641 approval_pending: false,
1642 registration_reason: nil,
1643 confirmation_token: nil,
1647 is_moderator: false,
1649 mastofe_settings: nil,
1652 pleroma_settings_store: %{},
1655 is_discoverable: false,
1660 def delete(users) when is_list(users) do
1661 for user <- users, do: delete(user)
1664 def delete(%User{} = user) do
1665 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1668 defp delete_and_invalidate_cache(%User{} = user) do
1669 invalidate_cache(user)
1673 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1675 defp delete_or_deactivate(%User{local: true} = user) do
1676 status = account_status(user)
1679 :confirmation_pending ->
1680 delete_and_invalidate_cache(user)
1682 :approval_pending ->
1683 delete_and_invalidate_cache(user)
1687 |> purge_user_changeset()
1688 |> update_and_set_cache()
1692 def perform(:force_password_reset, user), do: force_password_reset(user)
1694 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1695 def perform(:delete, %User{} = user) do
1696 # Remove all relationships
1699 |> Enum.each(fn follower ->
1700 ActivityPub.unfollow(follower, user)
1701 unfollow(follower, user)
1706 |> Enum.each(fn followed ->
1707 ActivityPub.unfollow(user, followed)
1708 unfollow(user, followed)
1711 delete_user_activities(user)
1712 delete_notifications_from_user_activities(user)
1714 delete_outgoing_pending_follow_requests(user)
1716 delete_or_deactivate(user)
1719 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1721 @spec external_users_query() :: Ecto.Query.t()
1722 def external_users_query do
1730 @spec external_users(keyword()) :: [User.t()]
1731 def external_users(opts \\ []) do
1733 external_users_query()
1734 |> select([u], struct(u, [:id, :ap_id]))
1738 do: where(query, [u], u.id > ^opts[:max_id]),
1743 do: limit(query, ^opts[:limit]),
1749 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1751 |> join(:inner, [n], activity in assoc(n, :activity))
1752 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1753 |> Repo.delete_all()
1756 def delete_user_activities(%User{ap_id: ap_id} = user) do
1758 |> Activity.Queries.by_actor()
1759 |> Repo.chunk_stream(50, :batches)
1760 |> Stream.each(fn activities ->
1761 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1766 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1767 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1768 {:ok, delete_data, _} <- Builder.delete(user, object) do
1769 Pipeline.common_pipeline(delete_data, local: user.local)
1771 {:find_object, nil} ->
1772 # We have the create activity, but not the object, it was probably pruned.
1773 # Insert a tombstone and try again
1774 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1775 {:ok, _tombstone} <- Object.create(tombstone_data) do
1776 delete_activity(activity, user)
1780 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1781 Logger.error("Error: #{inspect(e)}")
1785 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1786 when type in ["Like", "Announce"] do
1787 {:ok, undo, _} = Builder.undo(user, activity)
1788 Pipeline.common_pipeline(undo, local: user.local)
1791 defp delete_activity(_activity, _user), do: "Doing nothing"
1793 defp delete_outgoing_pending_follow_requests(user) do
1795 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1796 |> Repo.delete_all()
1799 def html_filter_policy(%User{no_rich_text: true}) do
1800 Pleroma.HTML.Scrubber.TwitterText
1803 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1805 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1807 def get_or_fetch_by_ap_id(ap_id) do
1808 cached_user = get_cached_by_ap_id(ap_id)
1810 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1812 case {cached_user, maybe_fetched_user} do
1813 {_, {:ok, %User{} = user}} ->
1816 {%User{} = user, _} ->
1820 {:error, :not_found}
1825 Creates an internal service actor by URI if missing.
1826 Optionally takes nickname for addressing.
1828 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1829 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1831 case get_cached_by_ap_id(uri) do
1833 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1834 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1838 %User{invisible: false} = user ->
1848 @spec set_invisible(User.t()) :: {:ok, User.t()}
1849 defp set_invisible(user) do
1851 |> change(%{invisible: true})
1852 |> update_and_set_cache()
1855 @spec create_service_actor(String.t(), String.t()) ::
1856 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1857 defp create_service_actor(uri, nickname) do
1863 follower_address: uri <> "/followers"
1866 |> unique_constraint(:nickname)
1871 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1874 |> :public_key.pem_decode()
1876 |> :public_key.pem_entry_decode()
1881 def public_key(_), do: {:error, "key not found"}
1883 def get_public_key_for_ap_id(ap_id) do
1884 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1885 {:ok, public_key} <- public_key(user) do
1892 def ap_enabled?(%User{local: true}), do: true
1893 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1894 def ap_enabled?(_), do: false
1896 @doc "Gets or fetch a user by uri or nickname."
1897 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1898 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1899 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1901 # wait a period of time and return newest version of the User structs
1902 # this is because we have synchronous follow APIs and need to simulate them
1903 # with an async handshake
1904 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1905 with %User{} = a <- get_cached_by_id(a.id),
1906 %User{} = b <- get_cached_by_id(b.id) do
1913 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1914 with :ok <- :timer.sleep(timeout),
1915 %User{} = a <- get_cached_by_id(a.id),
1916 %User{} = b <- get_cached_by_id(b.id) do
1923 def parse_bio(bio) when is_binary(bio) and bio != "" do
1925 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1929 def parse_bio(_), do: ""
1931 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1932 # TODO: get profile URLs other than user.ap_id
1933 profile_urls = [user.ap_id]
1936 |> CommonUtils.format_input("text/plain",
1937 mentions_format: :full,
1938 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1943 def parse_bio(_, _), do: ""
1945 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1946 Repo.transaction(fn ->
1947 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1951 def tag(nickname, tags) when is_binary(nickname),
1952 do: tag(get_by_nickname(nickname), tags)
1954 def tag(%User{} = user, tags),
1955 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1957 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1958 Repo.transaction(fn ->
1959 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1963 def untag(nickname, tags) when is_binary(nickname),
1964 do: untag(get_by_nickname(nickname), tags)
1966 def untag(%User{} = user, tags),
1967 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1969 defp update_tags(%User{} = user, new_tags) do
1970 {:ok, updated_user} =
1972 |> change(%{tags: new_tags})
1973 |> update_and_set_cache()
1978 defp normalize_tags(tags) do
1981 |> Enum.map(&String.downcase/1)
1984 defp local_nickname_regex do
1985 if Config.get([:instance, :extended_nickname_format]) do
1986 @extended_local_nickname_regex
1988 @strict_local_nickname_regex
1992 def local_nickname(nickname_or_mention) do
1995 |> String.split("@")
1999 def full_nickname(nickname_or_mention),
2000 do: String.trim_leading(nickname_or_mention, "@")
2002 def error_user(ap_id) do
2006 nickname: "erroruser@example.com",
2007 inserted_at: NaiveDateTime.utc_now()
2011 @spec all_superusers() :: [User.t()]
2012 def all_superusers do
2013 User.Query.build(%{super_users: true, local: true, deactivated: false})
2017 def muting_reblogs?(%User{} = user, %User{} = target) do
2018 UserRelationship.reblog_mute_exists?(user, target)
2021 def showing_reblogs?(%User{} = user, %User{} = target) do
2022 not muting_reblogs?(user, target)
2026 The function returns a query to get users with no activity for given interval of days.
2027 Inactive users are those who didn't read any notification, or had any activity where
2028 the user is the activity's actor, during `inactivity_threshold` days.
2029 Deactivated users will not appear in this list.
2033 iex> Pleroma.User.list_inactive_users()
2036 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2037 def list_inactive_users_query(inactivity_threshold \\ 7) do
2038 negative_inactivity_threshold = -inactivity_threshold
2039 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2040 # Subqueries are not supported in `where` clauses, join gets too complicated.
2041 has_read_notifications =
2042 from(n in Pleroma.Notification,
2043 where: n.seen == true,
2045 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2048 |> Pleroma.Repo.all()
2050 from(u in Pleroma.User,
2051 left_join: a in Pleroma.Activity,
2052 on: u.ap_id == a.actor,
2053 where: not is_nil(u.nickname),
2054 where: u.deactivated != ^true,
2055 where: u.id not in ^has_read_notifications,
2058 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2059 is_nil(max(a.inserted_at))
2064 Enable or disable email notifications for user
2068 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2069 Pleroma.User{email_notifications: %{"digest" => true}}
2071 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2072 Pleroma.User{email_notifications: %{"digest" => false}}
2074 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2075 {:ok, t()} | {:error, Ecto.Changeset.t()}
2076 def switch_email_notifications(user, type, status) do
2077 User.update_email_notifications(user, %{type => status})
2081 Set `last_digest_emailed_at` value for the user to current time
2083 @spec touch_last_digest_emailed_at(t()) :: t()
2084 def touch_last_digest_emailed_at(user) do
2085 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2087 {:ok, updated_user} =
2089 |> change(%{last_digest_emailed_at: now})
2090 |> update_and_set_cache()
2095 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
2096 def toggle_confirmation(%User{} = user) do
2098 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
2099 |> update_and_set_cache()
2102 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
2103 def toggle_confirmation(users) do
2104 Enum.map(users, &toggle_confirmation/1)
2107 @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2108 def need_confirmation(%User{} = user, bool) do
2110 |> confirmation_changeset(need_confirmation: bool)
2111 |> update_and_set_cache()
2114 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2118 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2119 # use instance-default
2120 config = Config.get([:assets, :mascots])
2121 default_mascot = Config.get([:assets, :default_mascot])
2122 mascot = Keyword.get(config, default_mascot)
2125 "id" => "default-mascot",
2126 "url" => mascot[:url],
2127 "preview_url" => mascot[:url],
2129 "mime_type" => mascot[:mime_type]
2134 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2136 def ensure_keys_present(%User{} = user) do
2137 with {:ok, pem} <- Keys.generate_rsa_pem() do
2139 |> cast(%{keys: pem}, [:keys])
2140 |> validate_required([:keys])
2141 |> update_and_set_cache()
2145 def get_ap_ids_by_nicknames(nicknames) do
2147 where: u.nickname in ^nicknames,
2153 defp put_password_hash(
2154 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2156 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2159 defp put_password_hash(changeset), do: changeset
2161 def is_internal_user?(%User{nickname: nil}), do: true
2162 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2163 def is_internal_user?(_), do: false
2165 # A hack because user delete activities have a fake id for whatever reason
2166 # TODO: Get rid of this
2167 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2169 def get_delivered_users_by_object_id(object_id) do
2171 inner_join: delivery in assoc(u, :deliveries),
2172 where: delivery.object_id == ^object_id
2177 def change_email(user, email) do
2179 |> cast(%{email: email}, [:email])
2180 |> validate_required([:email])
2181 |> unique_constraint(:email)
2182 |> validate_format(:email, @email_regex)
2183 |> update_and_set_cache()
2186 # Internal function; public one is `deactivate/2`
2187 defp set_activation_status(user, deactivated) do
2189 |> cast(%{deactivated: deactivated}, [:deactivated])
2190 |> update_and_set_cache()
2193 def update_banner(user, banner) do
2195 |> cast(%{banner: banner}, [:banner])
2196 |> update_and_set_cache()
2199 def update_background(user, background) do
2201 |> cast(%{background: background}, [:background])
2202 |> update_and_set_cache()
2205 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2208 moderator: is_moderator
2212 def validate_fields(changeset, remote? \\ false) do
2213 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2214 limit = Config.get([:instance, limit_name], 0)
2217 |> validate_length(:fields, max: limit)
2218 |> validate_change(:fields, fn :fields, fields ->
2219 if Enum.all?(fields, &valid_field?/1) do
2227 defp valid_field?(%{"name" => name, "value" => value}) do
2228 name_limit = Config.get([:instance, :account_field_name_length], 255)
2229 value_limit = Config.get([:instance, :account_field_value_length], 255)
2231 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2232 String.length(value) <= value_limit
2235 defp valid_field?(_), do: false
2237 defp truncate_field(%{"name" => name, "value" => value}) do
2239 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2242 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2244 %{"name" => name, "value" => value}
2247 def admin_api_update(user, params) do
2254 |> update_and_set_cache()
2257 @doc "Signs user out of all applications"
2258 def global_sign_out(user) do
2259 OAuth.Authorization.delete_user_authorizations(user)
2260 OAuth.Token.delete_user_tokens(user)
2263 def mascot_update(user, url) do
2265 |> cast(%{mascot: url}, [:mascot])
2266 |> validate_required([:mascot])
2267 |> update_and_set_cache()
2270 def mastodon_settings_update(user, settings) do
2272 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2273 |> validate_required([:mastofe_settings])
2274 |> update_and_set_cache()
2277 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2278 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2280 if need_confirmation? do
2282 confirmation_pending: true,
2283 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2287 confirmation_pending: false,
2288 confirmation_token: nil
2292 cast(user, params, [:confirmation_pending, :confirmation_token])
2295 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2296 def approval_changeset(user, need_approval: need_approval?) do
2297 params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
2298 cast(user, params, [:approval_pending])
2301 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2302 if id not in user.pinned_activities do
2303 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2304 params = %{pinned_activities: user.pinned_activities ++ [id]}
2306 # if pinned activity was scheduled for deletion, we remove job
2307 if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
2308 Oban.cancel_job(expiration.id)
2312 |> cast(params, [:pinned_activities])
2313 |> validate_length(:pinned_activities,
2314 max: max_pinned_statuses,
2315 message: "You have already pinned the maximum number of statuses"
2320 |> update_and_set_cache()
2323 def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
2324 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2326 # if pinned activity was scheduled for deletion, we reschedule it for deletion
2327 if data["expires_at"] do
2328 # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
2330 data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
2332 Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
2334 expires_at: expires_at
2339 |> cast(params, [:pinned_activities])
2340 |> update_and_set_cache()
2343 def update_email_notifications(user, settings) do
2344 email_notifications =
2345 user.email_notifications
2346 |> Map.merge(settings)
2347 |> Map.take(["digest"])
2349 params = %{email_notifications: email_notifications}
2350 fields = [:email_notifications]
2353 |> cast(params, fields)
2354 |> validate_required(fields)
2355 |> update_and_set_cache()
2358 defp set_domain_blocks(user, domain_blocks) do
2359 params = %{domain_blocks: domain_blocks}
2362 |> cast(params, [:domain_blocks])
2363 |> validate_required([:domain_blocks])
2364 |> update_and_set_cache()
2367 def block_domain(user, domain_blocked) do
2368 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2371 def unblock_domain(user, domain_blocked) do
2372 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2375 @spec add_to_block(User.t(), User.t()) ::
2376 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2377 defp add_to_block(%User{} = user, %User{} = blocked) do
2378 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2379 Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2384 @spec add_to_block(User.t(), User.t()) ::
2385 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2386 defp remove_from_block(%User{} = user, %User{} = blocked) do
2387 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2388 Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2393 def set_invisible(user, invisible) do
2394 params = %{invisible: invisible}
2397 |> cast(params, [:invisible])
2398 |> validate_required([:invisible])
2399 |> update_and_set_cache()
2402 def sanitize_html(%User{} = user) do
2403 sanitize_html(user, nil)
2406 # User data that mastodon isn't filtering (treated as plaintext):
2409 def sanitize_html(%User{} = user, filter) do
2411 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2414 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2419 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2420 |> Map.put(:fields, fields)