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
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
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(:search_rank, :float, virtual: true)
103 field(:search_type, :integer, virtual: true)
104 field(:tags, {:array, :string}, default: [])
105 field(:last_refreshed_at, :naive_datetime_usec)
106 field(:last_digest_emailed_at, :naive_datetime)
107 field(:banner, :map, default: %{})
108 field(:background, :map, default: %{})
109 field(:note_count, :integer, default: 0)
110 field(:follower_count, :integer, default: 0)
111 field(:following_count, :integer, default: 0)
112 field(:is_locked, :boolean, default: false)
113 field(:is_confirmed, :boolean, default: true)
114 field(:password_reset_pending, :boolean, default: false)
115 field(:is_approved, :boolean, default: true)
116 field(:registration_reason, :string, default: nil)
117 field(:confirmation_token, :string, default: nil)
118 field(:default_scope, :string, default: "public")
119 field(:domain_blocks, {:array, :string}, default: [])
120 field(:is_active, :boolean, default: true)
121 field(:no_rich_text, :boolean, default: false)
122 field(:ap_enabled, :boolean, default: false)
123 field(:is_moderator, :boolean, default: false)
124 field(:is_admin, :boolean, default: false)
125 field(:show_role, :boolean, default: true)
126 field(:mastofe_settings, :map, default: nil)
127 field(:uri, ObjectValidators.Uri, default: nil)
128 field(:hide_followers_count, :boolean, default: false)
129 field(:hide_follows_count, :boolean, default: false)
130 field(:hide_followers, :boolean, default: false)
131 field(:hide_follows, :boolean, default: false)
132 field(:hide_favorites, :boolean, default: true)
133 field(:pinned_activities, {:array, :string}, default: [])
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)
153 :notification_settings,
154 Pleroma.User.NotificationSetting,
158 has_many(:notifications, Notification)
159 has_many(:registrations, Registration)
160 has_many(:deliveries, Delivery)
162 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
163 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
165 for {relationship_type,
167 {outgoing_relation, outgoing_relation_target},
168 {incoming_relation, incoming_relation_source}
169 ]} <- @user_relationships_config do
170 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
171 # :notification_muter_mutes, :subscribee_subscriptions
172 has_many(outgoing_relation, UserRelationship,
173 foreign_key: :source_id,
174 where: [relationship_type: relationship_type]
177 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
178 # :notification_mutee_mutes, :subscriber_subscriptions
179 has_many(incoming_relation, UserRelationship,
180 foreign_key: :target_id,
181 where: [relationship_type: relationship_type]
184 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
185 # :notification_muted_users, :subscriber_users
186 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
188 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
189 # :notification_muter_users, :subscribee_users
190 has_many(incoming_relation_source, through: [incoming_relation, :source])
193 # `:blocks` is deprecated (replaced with `blocked_users` relation)
194 field(:blocks, {:array, :string}, default: [])
195 # `:mutes` is deprecated (replaced with `muted_users` relation)
196 field(:mutes, {:array, :string}, default: [])
197 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
198 field(:muted_reblogs, {:array, :string}, default: [])
199 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
200 field(:muted_notifications, {:array, :string}, default: [])
201 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
202 field(:subscribers, {:array, :string}, default: [])
205 :multi_factor_authentication_settings,
213 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
214 @user_relationships_config do
215 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
216 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
217 # `def subscriber_users/2`
218 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
219 target_users_query = assoc(user, unquote(outgoing_relation_target))
221 if restrict_deactivated? do
223 |> User.Query.build(%{deactivated: false})
229 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
230 # `def notification_muted_users/2`, `def subscriber_users/2`
231 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
233 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
235 restrict_deactivated?
240 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
241 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
242 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
244 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
246 restrict_deactivated?
248 |> select([u], u.ap_id)
253 def cached_blocked_users_ap_ids(user) do
254 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
255 blocked_users_ap_ids(user)
259 def cached_muted_users_ap_ids(user) do
260 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
261 muted_users_ap_ids(user)
265 defdelegate following_count(user), to: FollowingRelationship
266 defdelegate following(user), to: FollowingRelationship
267 defdelegate following?(follower, followed), to: FollowingRelationship
268 defdelegate following_ap_ids(user), to: FollowingRelationship
269 defdelegate get_follow_requests(user), to: FollowingRelationship
270 defdelegate search(query, opts \\ []), to: User.Search
273 Dumps Flake Id to SQL-compatible format (16-byte UUID).
274 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
276 def binary_id(source_id) when is_binary(source_id) do
277 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
284 def binary_id(source_ids) when is_list(source_ids) do
285 Enum.map(source_ids, &binary_id/1)
288 def binary_id(%User{} = user), do: binary_id(user.id)
290 @doc "Returns status account"
291 @spec account_status(User.t()) :: account_status()
292 def account_status(%User{is_active: false}), do: :deactivated
293 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
294 def account_status(%User{local: true, is_approved: false}), do: :approval_pending
295 def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
296 def account_status(%User{}), do: :active
298 @spec visible_for(User.t(), User.t() | nil) ::
301 | :restricted_unauthenticated
303 | :confirmation_pending
304 def visible_for(user, for_user \\ nil)
306 def visible_for(%User{invisible: true}, _), do: :invisible
308 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
310 def visible_for(%User{} = user, nil) do
311 if restrict_unauthenticated?(user) do
312 :restrict_unauthenticated
314 visible_account_status(user)
318 def visible_for(%User{} = user, for_user) do
319 if superuser?(for_user) do
322 visible_account_status(user)
326 def visible_for(_, _), do: :invisible
328 defp restrict_unauthenticated?(%User{local: true}) do
329 Config.restrict_unauthenticated_access?(:profiles, :local)
332 defp restrict_unauthenticated?(%User{local: _}) do
333 Config.restrict_unauthenticated_access?(:profiles, :remote)
336 defp visible_account_status(user) do
337 status = account_status(user)
339 if status in [:active, :password_reset_pending] do
346 @spec superuser?(User.t()) :: boolean()
347 def superuser?(%User{local: true, is_admin: true}), do: true
348 def superuser?(%User{local: true, is_moderator: true}), do: true
349 def superuser?(_), do: false
351 @spec invisible?(User.t()) :: boolean()
352 def invisible?(%User{invisible: true}), do: true
353 def invisible?(_), do: false
355 def avatar_url(user, options \\ []) do
357 %{"url" => [%{"href" => href} | _]} ->
361 unless options[:no_default] do
362 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
367 def banner_url(user, options \\ []) do
369 %{"url" => [%{"href" => href} | _]} -> href
370 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
374 # Should probably be renamed or removed
375 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
377 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
378 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
380 @spec ap_following(User.t()) :: String.t()
381 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
382 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
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()
448 :hide_followers_count,
457 :accepts_chat_messages
460 |> cast(params, [:name], empty_values: [])
461 |> validate_required([:ap_id])
462 |> validate_required([:name], trim: false)
463 |> unique_constraint(:nickname)
464 |> validate_format(:nickname, @email_regex)
465 |> validate_length(:bio, max: bio_limit)
466 |> validate_length(:name, max: name_limit)
467 |> validate_fields(true)
468 |> validate_non_local()
471 defp validate_non_local(cng) do
472 local? = get_field(cng, :local)
476 |> add_error(:local, "User is local, can't update with this changeset.")
482 def update_changeset(struct, params \\ %{}) do
483 bio_limit = Config.get([:instance, :user_bio_length], 5000)
484 name_limit = Config.get([:instance, :user_name_length], 100)
504 :hide_followers_count,
507 :allow_following_move,
511 :skip_thread_containment,
514 :pleroma_settings_store,
517 :accepts_chat_messages,
521 |> unique_constraint(:nickname)
522 |> validate_format(:nickname, local_nickname_regex())
523 |> validate_length(:bio, max: bio_limit)
524 |> validate_length(:name, min: 1, max: name_limit)
525 |> validate_inclusion(:actor_type, ["Person", "Service"])
528 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
529 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
530 |> put_change_if_present(:banner, &put_upload(&1, :banner))
531 |> put_change_if_present(:background, &put_upload(&1, :background))
532 |> put_change_if_present(
533 :pleroma_settings_store,
534 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
536 |> validate_fields(false)
539 defp put_fields(changeset) do
540 if raw_fields = get_change(changeset, :raw_fields) do
543 |> Enum.filter(fn %{"name" => n} -> n != "" end)
547 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
550 |> put_change(:raw_fields, raw_fields)
551 |> put_change(:fields, fields)
557 defp parse_fields(value) do
559 |> Formatter.linkify(mentions_format: :full)
563 defp put_emoji(changeset) do
564 emojified_fields = [:bio, :name, :raw_fields]
566 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
567 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
568 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
570 emoji = Map.merge(bio, name)
574 |> get_field(:raw_fields)
575 |> Enum.reduce(emoji, fn x, acc ->
576 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
579 put_change(changeset, :emoji, emoji)
585 defp put_change_if_present(changeset, map_field, value_function) do
586 with {:ok, value} <- fetch_change(changeset, map_field),
587 {:ok, new_value} <- value_function.(value) do
588 put_change(changeset, map_field, new_value)
594 defp put_upload(value, type) do
595 with %Plug.Upload{} <- value,
596 {:ok, object} <- ActivityPub.upload(value, type: type) do
601 def update_as_admin_changeset(struct, params) do
603 |> update_changeset(params)
604 |> cast(params, [:email])
605 |> delete_change(:also_known_as)
606 |> unique_constraint(:email)
607 |> validate_format(:email, @email_regex)
608 |> validate_inclusion(:actor_type, ["Person", "Service"])
611 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
612 def update_as_admin(user, params) do
613 params = Map.put(params, "password_confirmation", params["password"])
614 changeset = update_as_admin_changeset(user, params)
616 if params["password"] do
617 reset_password(user, changeset, params)
619 User.update_and_set_cache(changeset)
623 def password_update_changeset(struct, params) do
625 |> cast(params, [:password, :password_confirmation])
626 |> validate_required([:password, :password_confirmation])
627 |> validate_confirmation(:password)
628 |> put_password_hash()
629 |> put_change(:password_reset_pending, false)
632 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
633 def reset_password(%User{} = user, params) do
634 reset_password(user, user, params)
637 def reset_password(%User{id: user_id} = user, struct, params) do
640 |> Multi.update(:user, password_update_changeset(struct, params))
641 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
642 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
644 case Repo.transaction(multi) do
645 {:ok, %{user: user} = _} -> set_cache(user)
646 {:error, _, changeset, _} -> {:error, changeset}
650 def update_password_reset_pending(user, value) do
653 |> put_change(:password_reset_pending, value)
654 |> update_and_set_cache()
657 def force_password_reset_async(user) do
658 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
661 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
662 def force_password_reset(user), do: update_password_reset_pending(user, true)
664 # Used to auto-register LDAP accounts which won't have a password hash stored locally
665 def register_changeset_ldap(struct, params = %{password: password})
666 when is_nil(password) do
667 params = Map.put_new(params, :accepts_chat_messages, true)
670 if Map.has_key?(params, :email) do
671 Map.put_new(params, :email, params[:email])
681 :accepts_chat_messages
683 |> validate_required([:name, :nickname])
684 |> unique_constraint(:nickname)
685 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
686 |> validate_format(:nickname, local_nickname_regex())
688 |> unique_constraint(:ap_id)
689 |> put_following_and_follower_address()
692 def register_changeset(struct, params \\ %{}, opts \\ []) do
693 bio_limit = Config.get([:instance, :user_bio_length], 5000)
694 name_limit = Config.get([:instance, :user_name_length], 100)
695 reason_limit = Config.get([:instance, :registration_reason_length], 500)
696 params = Map.put_new(params, :accepts_chat_messages, true)
699 if is_nil(opts[:confirmed]) do
700 !Config.get([:instance, :account_activation_required])
706 if is_nil(opts[:approved]) do
707 !Config.get([:instance, :account_approval_required])
713 |> confirmation_changeset(set_confirmation: confirmed?)
714 |> approval_changeset(set_approval: approved?)
722 :password_confirmation,
724 :accepts_chat_messages,
727 |> validate_required([:name, :nickname, :password, :password_confirmation])
728 |> validate_confirmation(:password)
729 |> unique_constraint(:email)
730 |> validate_format(:email, @email_regex)
731 |> validate_change(:email, fn :email, email ->
733 Config.get([User, :email_blacklist])
734 |> Enum.all?(fn blacklisted_domain ->
735 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
738 if valid?, do: [], else: [email: "Invalid email"]
740 |> unique_constraint(:nickname)
741 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
742 |> validate_format(:nickname, local_nickname_regex())
743 |> validate_length(:bio, max: bio_limit)
744 |> validate_length(:name, min: 1, max: name_limit)
745 |> validate_length(:registration_reason, max: reason_limit)
746 |> maybe_validate_required_email(opts[:external])
749 |> unique_constraint(:ap_id)
750 |> put_following_and_follower_address()
753 def maybe_validate_required_email(changeset, true), do: changeset
755 def maybe_validate_required_email(changeset, _) do
756 if Config.get([:instance, :account_activation_required]) do
757 validate_required(changeset, [:email])
763 defp put_ap_id(changeset) do
764 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
765 put_change(changeset, :ap_id, ap_id)
768 defp put_following_and_follower_address(changeset) do
769 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
772 |> put_change(:follower_address, followers)
775 defp autofollow_users(user) do
776 candidates = Config.get([:instance, :autofollowed_nicknames])
779 User.Query.build(%{nickname: candidates, local: true, is_active: true})
782 follow_all(user, autofollowed_users)
785 defp autofollowing_users(user) do
786 candidates = Config.get([:instance, :autofollowing_nicknames])
788 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
790 |> Enum.each(&follow(&1, user, :follow_accept))
795 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
796 def register(%Ecto.Changeset{} = changeset) do
797 with {:ok, user} <- Repo.insert(changeset) do
798 post_register_action(user)
802 def post_register_action(%User{is_confirmed: false} = user) do
803 with {:ok, _} <- maybe_send_confirmation_email(user) do
808 def post_register_action(%User{is_approved: false} = user) do
809 with {:ok, _} <- send_user_approval_email(user),
810 {:ok, _} <- send_admin_approval_emails(user) do
815 def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
816 with {:ok, user} <- autofollow_users(user),
817 {:ok, _} <- autofollowing_users(user),
818 {:ok, user} <- set_cache(user),
819 {:ok, _} <- maybe_send_registration_email(user),
820 {:ok, _} <- maybe_send_welcome_email(user),
821 {:ok, _} <- maybe_send_welcome_message(user),
822 {:ok, _} <- maybe_send_welcome_chat_message(user) do
827 defp send_user_approval_email(user) do
829 |> Pleroma.Emails.UserEmail.approval_pending_email()
830 |> Pleroma.Emails.Mailer.deliver_async()
835 defp send_admin_approval_emails(user) do
837 |> Enum.filter(fn user -> not is_nil(user.email) end)
838 |> Enum.each(fn superuser ->
840 |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
841 |> Pleroma.Emails.Mailer.deliver_async()
847 defp maybe_send_welcome_message(user) do
848 if User.WelcomeMessage.enabled?() do
849 User.WelcomeMessage.post_message(user)
856 defp maybe_send_welcome_chat_message(user) do
857 if User.WelcomeChatMessage.enabled?() do
858 User.WelcomeChatMessage.post_message(user)
865 defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
866 if User.WelcomeEmail.enabled?() do
867 User.WelcomeEmail.send_email(user)
874 defp maybe_send_welcome_email(_), do: {:ok, :noop}
876 @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
877 def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
878 when is_binary(email) do
879 if Config.get([:instance, :account_activation_required]) do
880 send_confirmation_email(user)
887 def maybe_send_confirmation_email(_), do: {:ok, :noop}
889 @spec send_confirmation_email(Uset.t()) :: User.t()
890 def send_confirmation_email(%User{} = user) do
892 |> Pleroma.Emails.UserEmail.account_confirmation_email()
893 |> Pleroma.Emails.Mailer.deliver_async()
898 @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
899 defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
900 with false <- User.WelcomeEmail.enabled?(),
901 false <- Config.get([:instance, :account_activation_required], false),
902 false <- Config.get([:instance, :account_approval_required], false) do
904 |> Pleroma.Emails.UserEmail.successful_registration_email()
905 |> Pleroma.Emails.Mailer.deliver_async()
914 defp maybe_send_registration_email(_), do: {:ok, :noop}
916 def needs_update?(%User{local: true}), do: false
918 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
920 def needs_update?(%User{local: false} = user) do
921 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
924 def needs_update?(_), do: true
926 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
928 # "Locked" (self-locked) users demand explicit authorization of follow requests
929 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
930 follow(follower, followed, :follow_pending)
933 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
934 follow(follower, followed)
937 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
938 if not ap_enabled?(followed) do
939 follow(follower, followed)
941 {:ok, follower, followed}
945 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
946 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
947 def follow_all(follower, followeds) do
949 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
950 |> Enum.each(&follow(follower, &1, :follow_accept))
955 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
956 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
959 not followed.is_active ->
960 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
962 deny_follow_blocked and blocks?(followed, follower) ->
963 {:error, "Could not follow user: #{followed.nickname} blocked you."}
966 FollowingRelationship.follow(follower, followed, state)
970 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
971 {:error, "Not subscribed!"}
974 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
975 def unfollow(%User{} = follower, %User{} = followed) do
976 case do_unfollow(follower, followed) do
977 {:ok, follower, followed} ->
978 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
985 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
986 defp do_unfollow(%User{} = follower, %User{} = followed) do
987 case get_follow_state(follower, followed) do
988 state when state in [:follow_pending, :follow_accept] ->
989 FollowingRelationship.unfollow(follower, followed)
992 {:error, "Not subscribed!"}
996 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
997 def get_follow_state(%User{} = follower, %User{} = following) do
998 following_relationship = FollowingRelationship.get(follower, following)
999 get_follow_state(follower, following, following_relationship)
1002 def get_follow_state(
1004 %User{} = following,
1005 following_relationship
1007 case {following_relationship, following.local} do
1009 case Utils.fetch_latest_follow(follower, following) do
1010 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
1011 FollowingRelationship.state_to_enum(state)
1017 {%{state: state}, _} ->
1025 def locked?(%User{} = user) do
1026 user.is_locked || false
1029 def get_by_id(id) do
1030 Repo.get_by(User, id: id)
1033 def get_by_ap_id(ap_id) do
1034 Repo.get_by(User, ap_id: ap_id)
1037 def get_all_by_ap_id(ap_ids) do
1038 from(u in __MODULE__,
1039 where: u.ap_id in ^ap_ids
1044 def get_all_by_ids(ids) do
1045 from(u in __MODULE__, where: u.id in ^ids)
1049 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1050 # of the ap_id and the domain and tries to get that user
1051 def get_by_guessed_nickname(ap_id) do
1052 domain = URI.parse(ap_id).host
1053 name = List.last(String.split(ap_id, "/"))
1054 nickname = "#{name}@#{domain}"
1056 get_cached_by_nickname(nickname)
1059 def set_cache({:ok, user}), do: set_cache(user)
1060 def set_cache({:error, err}), do: {:error, err}
1062 def set_cache(%User{} = user) do
1063 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1064 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1065 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1069 def update_and_set_cache(struct, params) do
1071 |> update_changeset(params)
1072 |> update_and_set_cache()
1075 def update_and_set_cache(changeset) do
1076 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1081 def get_user_friends_ap_ids(user) do
1082 from(u in User.get_friends_query(user), select: u.ap_id)
1086 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1087 def get_cached_user_friends_ap_ids(user) do
1088 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1089 get_user_friends_ap_ids(user)
1093 def invalidate_cache(user) do
1094 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1095 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1096 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1097 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1098 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1101 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1102 def get_cached_by_ap_id(ap_id) do
1103 key = "ap_id:#{ap_id}"
1105 with {:ok, nil} <- @cachex.get(:user_cache, key),
1106 user when not is_nil(user) <- get_by_ap_id(ap_id),
1107 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1115 def get_cached_by_id(id) do
1119 @cachex.fetch!(:user_cache, key, fn _ ->
1120 user = get_by_id(id)
1123 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1124 {:commit, user.ap_id}
1130 get_cached_by_ap_id(ap_id)
1133 def get_cached_by_nickname(nickname) do
1134 key = "nickname:#{nickname}"
1136 @cachex.fetch!(:user_cache, key, fn _ ->
1137 case get_or_fetch_by_nickname(nickname) do
1138 {:ok, user} -> {:commit, user}
1139 {:error, _error} -> {:ignore, nil}
1144 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1145 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1148 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1149 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1151 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1152 get_cached_by_nickname(nickname_or_id)
1154 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1155 get_cached_by_nickname(nickname_or_id)
1162 @spec get_by_nickname(String.t()) :: User.t() | nil
1163 def get_by_nickname(nickname) do
1164 Repo.get_by(User, nickname: nickname) ||
1165 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1166 Repo.get_by(User, nickname: local_nickname(nickname))
1170 def get_by_email(email), do: Repo.get_by(User, email: email)
1172 def get_by_nickname_or_email(nickname_or_email) do
1173 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1176 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1178 def get_or_fetch_by_nickname(nickname) do
1179 with %User{} = user <- get_by_nickname(nickname) do
1183 with [_nick, _domain] <- String.split(nickname, "@"),
1184 {:ok, user} <- fetch_by_nickname(nickname) do
1187 _e -> {:error, "not found " <> nickname}
1192 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1193 def get_followers_query(%User{} = user, nil) do
1194 User.Query.build(%{followers: user, is_active: true})
1197 def get_followers_query(%User{} = user, page) do
1199 |> get_followers_query(nil)
1200 |> User.Query.paginate(page, 20)
1203 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1204 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1206 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1207 def get_followers(%User{} = user, page \\ nil) do
1209 |> get_followers_query(page)
1213 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1214 def get_external_followers(%User{} = user, page \\ nil) do
1216 |> get_followers_query(page)
1217 |> User.Query.build(%{external: true})
1221 def get_followers_ids(%User{} = user, page \\ nil) do
1223 |> get_followers_query(page)
1224 |> select([u], u.id)
1228 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1229 def get_friends_query(%User{} = user, nil) do
1230 User.Query.build(%{friends: user, deactivated: false})
1233 def get_friends_query(%User{} = user, page) do
1235 |> get_friends_query(nil)
1236 |> User.Query.paginate(page, 20)
1239 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1240 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1242 def get_friends(%User{} = user, page \\ nil) do
1244 |> get_friends_query(page)
1248 def get_friends_ap_ids(%User{} = user) do
1250 |> get_friends_query(nil)
1251 |> select([u], u.ap_id)
1255 def get_friends_ids(%User{} = user, page \\ nil) do
1257 |> get_friends_query(page)
1258 |> select([u], u.id)
1262 def increase_note_count(%User{} = user) do
1264 |> where(id: ^user.id)
1265 |> update([u], inc: [note_count: 1])
1267 |> Repo.update_all([])
1269 {1, [user]} -> set_cache(user)
1274 def decrease_note_count(%User{} = user) do
1276 |> where(id: ^user.id)
1279 note_count: fragment("greatest(0, note_count - 1)")
1283 |> Repo.update_all([])
1285 {1, [user]} -> set_cache(user)
1290 def update_note_count(%User{} = user, note_count \\ nil) do
1295 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1301 |> cast(%{note_count: note_count}, [:note_count])
1302 |> update_and_set_cache()
1305 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1306 def maybe_fetch_follow_information(user) do
1307 with {:ok, user} <- fetch_follow_information(user) do
1311 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1317 def fetch_follow_information(user) do
1318 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1320 |> follow_information_changeset(info)
1321 |> update_and_set_cache()
1325 defp follow_information_changeset(user, params) do
1332 :hide_followers_count,
1337 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1338 def update_follower_count(%User{} = user) do
1339 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1340 follower_count = FollowingRelationship.follower_count(user)
1343 |> follow_information_changeset(%{follower_count: follower_count})
1344 |> update_and_set_cache
1346 {:ok, maybe_fetch_follow_information(user)}
1350 @spec update_following_count(User.t()) :: {:ok, User.t()}
1351 def update_following_count(%User{local: false} = user) do
1352 if Config.get([:instance, :external_user_synchronization]) do
1353 {:ok, maybe_fetch_follow_information(user)}
1359 def update_following_count(%User{local: true} = user) do
1360 following_count = FollowingRelationship.following_count(user)
1363 |> follow_information_changeset(%{following_count: following_count})
1364 |> update_and_set_cache()
1367 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1368 def get_users_from_set(ap_ids, opts \\ []) do
1369 local_only = Keyword.get(opts, :local_only, true)
1370 criteria = %{ap_id: ap_ids, is_active: true}
1371 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1373 User.Query.build(criteria)
1377 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1378 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1381 query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
1387 @spec mute(User.t(), User.t(), map()) ::
1388 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1389 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1390 notifications? = Map.get(params, :notifications, true)
1391 expires_in = Map.get(params, :expires_in, 0)
1393 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1394 {:ok, user_notification_mute} <-
1395 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1397 if expires_in > 0 do
1398 Pleroma.Workers.MuteExpireWorker.enqueue(
1400 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1401 schedule_in: expires_in
1405 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1407 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1411 def unmute(%User{} = muter, %User{} = mutee) do
1412 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1413 {:ok, user_notification_mute} <-
1414 UserRelationship.delete_notification_mute(muter, mutee) do
1415 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1416 {:ok, [user_mute, user_notification_mute]}
1420 def unmute(muter_id, mutee_id) do
1421 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1422 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1423 unmute(muter, mutee)
1425 {who, result} = error ->
1427 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1434 def subscribe(%User{} = subscriber, %User{} = target) do
1435 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1437 if blocks?(target, subscriber) and deny_follow_blocked do
1438 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1440 # Note: the relationship is inverse: subscriber acts as relationship target
1441 UserRelationship.create_inverse_subscription(target, subscriber)
1445 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1446 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1447 subscribe(subscriber, subscribee)
1451 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1452 # Note: the relationship is inverse: subscriber acts as relationship target
1453 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1456 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1457 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1458 unsubscribe(unsubscriber, user)
1462 def block(%User{} = blocker, %User{} = blocked) do
1463 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1465 if following?(blocker, blocked) do
1466 {:ok, blocker, _} = unfollow(blocker, blocked)
1472 # clear any requested follows as well
1474 case CommonAPI.reject_follow_request(blocked, blocker) do
1475 {:ok, %User{} = updated_blocked} -> updated_blocked
1479 unsubscribe(blocked, blocker)
1481 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1482 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1484 {:ok, blocker} = update_follower_count(blocker)
1485 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1486 add_to_block(blocker, blocked)
1489 # helper to handle the block given only an actor's AP id
1490 def block(%User{} = blocker, %{ap_id: ap_id}) do
1491 block(blocker, get_cached_by_ap_id(ap_id))
1494 def unblock(%User{} = blocker, %User{} = blocked) do
1495 remove_from_block(blocker, blocked)
1498 # helper to handle the block given only an actor's AP id
1499 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1500 unblock(blocker, get_cached_by_ap_id(ap_id))
1503 def mutes?(nil, _), do: false
1504 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1506 def mutes_user?(%User{} = user, %User{} = target) do
1507 UserRelationship.mute_exists?(user, target)
1510 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1511 def muted_notifications?(nil, _), do: false
1513 def muted_notifications?(%User{} = user, %User{} = target),
1514 do: UserRelationship.notification_mute_exists?(user, target)
1516 def blocks?(nil, _), do: false
1518 def blocks?(%User{} = user, %User{} = target) do
1519 blocks_user?(user, target) ||
1520 (blocks_domain?(user, target) and not User.following?(user, target))
1523 def blocks_user?(%User{} = user, %User{} = target) do
1524 UserRelationship.block_exists?(user, target)
1527 def blocks_user?(_, _), do: false
1529 def blocks_domain?(%User{} = user, %User{} = target) do
1530 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1531 %{host: host} = URI.parse(target.ap_id)
1532 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1535 def blocks_domain?(_, _), do: false
1537 def subscribed_to?(%User{} = user, %User{} = target) do
1538 # Note: the relationship is inverse: subscriber acts as relationship target
1539 UserRelationship.inverse_subscription_exists?(target, user)
1542 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1543 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1544 subscribed_to?(user, target)
1549 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1550 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1552 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1553 def outgoing_relationships_ap_ids(_user, []), do: %{}
1555 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1557 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1558 when is_list(relationship_types) do
1561 |> assoc(:outgoing_relationships)
1562 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1563 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1564 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1565 |> group_by([user_rel, u], user_rel.relationship_type)
1567 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1572 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1576 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1578 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1580 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1582 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1583 when is_list(relationship_types) do
1585 |> assoc(:incoming_relationships)
1586 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1587 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1588 |> maybe_filter_on_ap_id(ap_ids)
1589 |> select([user_rel, u], u.ap_id)
1594 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1595 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1598 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1600 def set_activation_async(user, status \\ true) do
1601 BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
1604 @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1605 def set_activation(users, status) when is_list(users) do
1606 Repo.transaction(fn ->
1607 for user <- users, do: set_activation(user, status)
1611 @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1612 def set_activation(%User{} = user, status) do
1613 with {:ok, user} <- set_activation_status(user, status) do
1616 |> Enum.filter(& &1.local)
1617 |> Enum.each(&set_cache(update_following_count(&1)))
1619 # Only update local user counts, remote will be update during the next pull.
1622 |> Enum.filter(& &1.local)
1623 |> Enum.each(&do_unfollow(user, &1))
1629 def approve(users) when is_list(users) do
1630 Repo.transaction(fn ->
1631 Enum.map(users, fn user ->
1632 with {:ok, user} <- approve(user), do: user
1637 def approve(%User{is_approved: false} = user) do
1638 with chg <- change(user, is_approved: true),
1639 {:ok, user} <- update_and_set_cache(chg) do
1640 post_register_action(user)
1645 def approve(%User{} = user), do: {:ok, user}
1647 def confirm(users) when is_list(users) do
1648 Repo.transaction(fn ->
1649 Enum.map(users, fn user ->
1650 with {:ok, user} <- confirm(user), do: user
1655 def confirm(%User{is_confirmed: false} = user) do
1656 with chg <- confirmation_changeset(user, set_confirmation: true),
1657 {:ok, user} <- update_and_set_cache(chg) do
1658 post_register_action(user)
1663 def confirm(%User{} = user), do: {:ok, user}
1665 def update_notification_settings(%User{} = user, settings) do
1667 |> cast(%{notification_settings: settings}, [])
1668 |> cast_embed(:notification_settings)
1669 |> validate_required([:notification_settings])
1670 |> update_and_set_cache()
1673 @spec purge_user_changeset(User.t()) :: Changeset.t()
1674 def purge_user_changeset(user) do
1675 # "Right to be forgotten"
1676 # https://gdpr.eu/right-to-be-forgotten/
1687 last_refreshed_at: nil,
1688 last_digest_emailed_at: nil,
1696 password_reset_pending: false,
1698 registration_reason: nil,
1699 confirmation_token: nil,
1703 is_moderator: false,
1705 mastofe_settings: nil,
1708 pleroma_settings_store: %{},
1711 is_discoverable: false,
1716 def delete(users) when is_list(users) do
1717 for user <- users, do: delete(user)
1720 def delete(%User{} = user) do
1721 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1724 defp delete_and_invalidate_cache(%User{} = user) do
1725 invalidate_cache(user)
1729 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1731 defp delete_or_deactivate(%User{local: true} = user) do
1732 status = account_status(user)
1735 :confirmation_pending ->
1736 delete_and_invalidate_cache(user)
1738 :approval_pending ->
1739 delete_and_invalidate_cache(user)
1743 |> purge_user_changeset()
1744 |> update_and_set_cache()
1748 def perform(:force_password_reset, user), do: force_password_reset(user)
1750 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1751 def perform(:delete, %User{} = user) do
1752 # Remove all relationships
1755 |> Enum.each(fn follower ->
1756 ActivityPub.unfollow(follower, user)
1757 unfollow(follower, user)
1762 |> Enum.each(fn followed ->
1763 ActivityPub.unfollow(user, followed)
1764 unfollow(user, followed)
1767 delete_user_activities(user)
1768 delete_notifications_from_user_activities(user)
1770 delete_outgoing_pending_follow_requests(user)
1772 delete_or_deactivate(user)
1775 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1777 @spec external_users_query() :: Ecto.Query.t()
1778 def external_users_query do
1786 @spec external_users(keyword()) :: [User.t()]
1787 def external_users(opts \\ []) do
1789 external_users_query()
1790 |> select([u], struct(u, [:id, :ap_id]))
1794 do: where(query, [u], u.id > ^opts[:max_id]),
1799 do: limit(query, ^opts[:limit]),
1805 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1807 |> join(:inner, [n], activity in assoc(n, :activity))
1808 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1809 |> Repo.delete_all()
1812 def delete_user_activities(%User{ap_id: ap_id} = user) do
1814 |> Activity.Queries.by_actor()
1815 |> Repo.chunk_stream(50, :batches)
1816 |> Stream.each(fn activities ->
1817 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1822 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1823 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1824 {:ok, delete_data, _} <- Builder.delete(user, object) do
1825 Pipeline.common_pipeline(delete_data, local: user.local)
1827 {:find_object, nil} ->
1828 # We have the create activity, but not the object, it was probably pruned.
1829 # Insert a tombstone and try again
1830 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1831 {:ok, _tombstone} <- Object.create(tombstone_data) do
1832 delete_activity(activity, user)
1836 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1837 Logger.error("Error: #{inspect(e)}")
1841 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1842 when type in ["Like", "Announce"] do
1843 {:ok, undo, _} = Builder.undo(user, activity)
1844 Pipeline.common_pipeline(undo, local: user.local)
1847 defp delete_activity(_activity, _user), do: "Doing nothing"
1849 defp delete_outgoing_pending_follow_requests(user) do
1851 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1852 |> Repo.delete_all()
1855 def html_filter_policy(%User{no_rich_text: true}) do
1856 Pleroma.HTML.Scrubber.TwitterText
1859 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1861 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1863 def get_or_fetch_by_ap_id(ap_id) do
1864 cached_user = get_cached_by_ap_id(ap_id)
1866 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1868 case {cached_user, maybe_fetched_user} do
1869 {_, {:ok, %User{} = user}} ->
1872 {%User{} = user, _} ->
1876 {:error, :not_found}
1881 Creates an internal service actor by URI if missing.
1882 Optionally takes nickname for addressing.
1884 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1885 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1887 case get_cached_by_ap_id(uri) do
1889 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1890 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1894 %User{invisible: false} = user ->
1904 @spec set_invisible(User.t()) :: {:ok, User.t()}
1905 defp set_invisible(user) do
1907 |> change(%{invisible: true})
1908 |> update_and_set_cache()
1911 @spec create_service_actor(String.t(), String.t()) ::
1912 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1913 defp create_service_actor(uri, nickname) do
1919 follower_address: uri <> "/followers"
1922 |> unique_constraint(:nickname)
1927 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1930 |> :public_key.pem_decode()
1932 |> :public_key.pem_entry_decode()
1937 def public_key(_), do: {:error, "key not found"}
1939 def get_public_key_for_ap_id(ap_id) do
1940 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1941 {:ok, public_key} <- public_key(user) do
1948 def ap_enabled?(%User{local: true}), do: true
1949 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1950 def ap_enabled?(_), do: false
1952 @doc "Gets or fetch a user by uri or nickname."
1953 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1954 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1955 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1957 # wait a period of time and return newest version of the User structs
1958 # this is because we have synchronous follow APIs and need to simulate them
1959 # with an async handshake
1960 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1961 with %User{} = a <- get_cached_by_id(a.id),
1962 %User{} = b <- get_cached_by_id(b.id) do
1969 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1970 with :ok <- :timer.sleep(timeout),
1971 %User{} = a <- get_cached_by_id(a.id),
1972 %User{} = b <- get_cached_by_id(b.id) do
1979 def parse_bio(bio) when is_binary(bio) and bio != "" do
1981 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1985 def parse_bio(_), do: ""
1987 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1988 # TODO: get profile URLs other than user.ap_id
1989 profile_urls = [user.ap_id]
1992 |> CommonUtils.format_input("text/plain",
1993 mentions_format: :full,
1994 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1999 def parse_bio(_, _), do: ""
2001 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2002 Repo.transaction(fn ->
2003 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2007 def tag(nickname, tags) when is_binary(nickname),
2008 do: tag(get_by_nickname(nickname), tags)
2010 def tag(%User{} = user, tags),
2011 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2013 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2014 Repo.transaction(fn ->
2015 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2019 def untag(nickname, tags) when is_binary(nickname),
2020 do: untag(get_by_nickname(nickname), tags)
2022 def untag(%User{} = user, tags),
2023 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2025 defp update_tags(%User{} = user, new_tags) do
2026 {:ok, updated_user} =
2028 |> change(%{tags: new_tags})
2029 |> update_and_set_cache()
2034 defp normalize_tags(tags) do
2037 |> Enum.map(&String.downcase/1)
2040 defp local_nickname_regex do
2041 if Config.get([:instance, :extended_nickname_format]) do
2042 @extended_local_nickname_regex
2044 @strict_local_nickname_regex
2048 def local_nickname(nickname_or_mention) do
2051 |> String.split("@")
2055 def full_nickname(%User{} = user) do
2056 if String.contains?(user.nickname, "@") do
2059 %{host: host} = URI.parse(user.ap_id)
2060 user.nickname <> "@" <> host
2064 def full_nickname(nickname_or_mention),
2065 do: String.trim_leading(nickname_or_mention, "@")
2067 def error_user(ap_id) do
2071 nickname: "erroruser@example.com",
2072 inserted_at: NaiveDateTime.utc_now()
2076 @spec all_superusers() :: [User.t()]
2077 def all_superusers do
2078 User.Query.build(%{super_users: true, local: true, is_active: true})
2082 def muting_reblogs?(%User{} = user, %User{} = target) do
2083 UserRelationship.reblog_mute_exists?(user, target)
2086 def showing_reblogs?(%User{} = user, %User{} = target) do
2087 not muting_reblogs?(user, target)
2091 The function returns a query to get users with no activity for given interval of days.
2092 Inactive users are those who didn't read any notification, or had any activity where
2093 the user is the activity's actor, during `inactivity_threshold` days.
2094 Deactivated users will not appear in this list.
2098 iex> Pleroma.User.list_inactive_users()
2101 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2102 def list_inactive_users_query(inactivity_threshold \\ 7) do
2103 negative_inactivity_threshold = -inactivity_threshold
2104 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2105 # Subqueries are not supported in `where` clauses, join gets too complicated.
2106 has_read_notifications =
2107 from(n in Pleroma.Notification,
2108 where: n.seen == true,
2110 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2113 |> Pleroma.Repo.all()
2115 from(u in Pleroma.User,
2116 left_join: a in Pleroma.Activity,
2117 on: u.ap_id == a.actor,
2118 where: not is_nil(u.nickname),
2119 where: u.is_active == ^true,
2120 where: u.id not in ^has_read_notifications,
2123 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2124 is_nil(max(a.inserted_at))
2129 Enable or disable email notifications for user
2133 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2134 Pleroma.User{email_notifications: %{"digest" => true}}
2136 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2137 Pleroma.User{email_notifications: %{"digest" => false}}
2139 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2140 {:ok, t()} | {:error, Ecto.Changeset.t()}
2141 def switch_email_notifications(user, type, status) do
2142 User.update_email_notifications(user, %{type => status})
2146 Set `last_digest_emailed_at` value for the user to current time
2148 @spec touch_last_digest_emailed_at(t()) :: t()
2149 def touch_last_digest_emailed_at(user) do
2150 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2152 {:ok, updated_user} =
2154 |> change(%{last_digest_emailed_at: now})
2155 |> update_and_set_cache()
2160 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2161 def set_confirmation(%User{} = user, bool) do
2163 |> confirmation_changeset(set_confirmation: bool)
2164 |> update_and_set_cache()
2167 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2171 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2172 # use instance-default
2173 config = Config.get([:assets, :mascots])
2174 default_mascot = Config.get([:assets, :default_mascot])
2175 mascot = Keyword.get(config, default_mascot)
2178 "id" => "default-mascot",
2179 "url" => mascot[:url],
2180 "preview_url" => mascot[:url],
2182 "mime_type" => mascot[:mime_type]
2187 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2189 def ensure_keys_present(%User{} = user) do
2190 with {:ok, pem} <- Keys.generate_rsa_pem() do
2192 |> cast(%{keys: pem}, [:keys])
2193 |> validate_required([:keys])
2194 |> update_and_set_cache()
2198 def get_ap_ids_by_nicknames(nicknames) do
2200 where: u.nickname in ^nicknames,
2206 defp put_password_hash(
2207 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2209 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2212 defp put_password_hash(changeset), do: changeset
2214 def is_internal_user?(%User{nickname: nil}), do: true
2215 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2216 def is_internal_user?(_), do: false
2218 # A hack because user delete activities have a fake id for whatever reason
2219 # TODO: Get rid of this
2220 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2222 def get_delivered_users_by_object_id(object_id) do
2224 inner_join: delivery in assoc(u, :deliveries),
2225 where: delivery.object_id == ^object_id
2230 def change_email(user, email) do
2232 |> cast(%{email: email}, [:email])
2233 |> validate_required([:email])
2234 |> unique_constraint(:email)
2235 |> validate_format(:email, @email_regex)
2236 |> update_and_set_cache()
2239 # Internal function; public one is `deactivate/2`
2240 defp set_activation_status(user, status) do
2242 |> cast(%{is_active: status}, [:is_active])
2243 |> update_and_set_cache()
2246 def update_banner(user, banner) do
2248 |> cast(%{banner: banner}, [:banner])
2249 |> update_and_set_cache()
2252 def update_background(user, background) do
2254 |> cast(%{background: background}, [:background])
2255 |> update_and_set_cache()
2258 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2261 moderator: is_moderator
2265 def validate_fields(changeset, remote? \\ false) do
2266 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2267 limit = Config.get([:instance, limit_name], 0)
2270 |> validate_length(:fields, max: limit)
2271 |> validate_change(:fields, fn :fields, fields ->
2272 if Enum.all?(fields, &valid_field?/1) do
2280 defp valid_field?(%{"name" => name, "value" => value}) do
2281 name_limit = Config.get([:instance, :account_field_name_length], 255)
2282 value_limit = Config.get([:instance, :account_field_value_length], 255)
2284 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2285 String.length(value) <= value_limit
2288 defp valid_field?(_), do: false
2290 defp truncate_field(%{"name" => name, "value" => value}) do
2292 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2295 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2297 %{"name" => name, "value" => value}
2300 def admin_api_update(user, params) do
2307 |> update_and_set_cache()
2310 @doc "Signs user out of all applications"
2311 def global_sign_out(user) do
2312 OAuth.Authorization.delete_user_authorizations(user)
2313 OAuth.Token.delete_user_tokens(user)
2316 def mascot_update(user, url) do
2318 |> cast(%{mascot: url}, [:mascot])
2319 |> validate_required([:mascot])
2320 |> update_and_set_cache()
2323 def mastodon_settings_update(user, settings) do
2325 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2326 |> validate_required([:mastofe_settings])
2327 |> update_and_set_cache()
2330 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2331 def confirmation_changeset(user, set_confirmation: confirmed?) do
2336 confirmation_token: nil
2340 is_confirmed: false,
2341 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2345 cast(user, params, [:is_confirmed, :confirmation_token])
2348 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2349 def approval_changeset(user, set_approval: approved?) do
2350 cast(user, %{is_approved: approved?}, [:is_approved])
2353 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2354 if id not in user.pinned_activities do
2355 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2356 params = %{pinned_activities: user.pinned_activities ++ [id]}
2358 # if pinned activity was scheduled for deletion, we remove job
2359 if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
2360 Oban.cancel_job(expiration.id)
2364 |> cast(params, [:pinned_activities])
2365 |> validate_length(:pinned_activities,
2366 max: max_pinned_statuses,
2367 message: "You have already pinned the maximum number of statuses"
2372 |> update_and_set_cache()
2375 def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
2376 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2378 # if pinned activity was scheduled for deletion, we reschedule it for deletion
2379 if data["expires_at"] do
2380 # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
2382 data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
2384 Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
2386 expires_at: expires_at
2391 |> cast(params, [:pinned_activities])
2392 |> update_and_set_cache()
2395 def update_email_notifications(user, settings) do
2396 email_notifications =
2397 user.email_notifications
2398 |> Map.merge(settings)
2399 |> Map.take(["digest"])
2401 params = %{email_notifications: email_notifications}
2402 fields = [:email_notifications]
2405 |> cast(params, fields)
2406 |> validate_required(fields)
2407 |> update_and_set_cache()
2410 defp set_domain_blocks(user, domain_blocks) do
2411 params = %{domain_blocks: domain_blocks}
2414 |> cast(params, [:domain_blocks])
2415 |> validate_required([:domain_blocks])
2416 |> update_and_set_cache()
2419 def block_domain(user, domain_blocked) do
2420 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2423 def unblock_domain(user, domain_blocked) do
2424 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2427 @spec add_to_block(User.t(), User.t()) ::
2428 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2429 defp add_to_block(%User{} = user, %User{} = blocked) do
2430 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2431 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2436 @spec add_to_block(User.t(), User.t()) ::
2437 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2438 defp remove_from_block(%User{} = user, %User{} = blocked) do
2439 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2440 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2445 def set_invisible(user, invisible) do
2446 params = %{invisible: invisible}
2449 |> cast(params, [:invisible])
2450 |> validate_required([:invisible])
2451 |> update_and_set_cache()
2454 def sanitize_html(%User{} = user) do
2455 sanitize_html(user, nil)
2458 # User data that mastodon isn't filtering (treated as plaintext):
2461 def sanitize_html(%User{} = user, filter) do
2463 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2466 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2471 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2472 |> Map.put(:fields, fields)
2475 def get_host(%User{ap_id: ap_id} = _user) do
2476 URI.parse(ap_id).host
2479 def update_last_active_at(%__MODULE__{local: true} = user) do
2481 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2482 |> update_and_set_cache()
2485 def active_user_count(weeks \\ 4) do
2486 active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
2489 |> where([u], u.last_active_at >= ^active_after)
2490 |> where([u], u.local == true)
2491 |> Repo.aggregate(:count)