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,
1695 password_reset_pending: false,
1696 registration_reason: nil,
1697 confirmation_token: nil,
1701 is_moderator: false,
1703 mastofe_settings: nil,
1706 pleroma_settings_store: %{},
1709 is_discoverable: false,
1713 # nickname: preserved
1717 # Purge doesn't delete the user from the database.
1718 # It just nulls all its fields and deactivates it.
1719 # See `User.purge_user_changeset/1` above.
1720 def purge(%User{} = user) do
1722 |> purge_user_changeset()
1723 |> update_and_set_cache()
1726 def delete(users) when is_list(users) do
1727 for user <- users, do: delete(user)
1730 def delete(%User{} = user) do
1731 # Purge the user immediately
1733 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1736 # *Actually* delete the user from the DB
1737 defp delete_from_db(%User{} = user) do
1738 invalidate_cache(user)
1742 # If the user never finalized their account, it's safe to delete them.
1743 defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
1744 do: delete_from_db(user)
1746 defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
1747 do: delete_from_db(user)
1749 defp maybe_delete_from_db(user), do: {:ok, user}
1751 def perform(:force_password_reset, user), do: force_password_reset(user)
1753 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1754 def perform(:delete, %User{} = user) do
1755 # Purge the user again, in case perform/2 is called directly
1758 # Remove all relationships
1761 |> Enum.each(fn follower ->
1762 ActivityPub.unfollow(follower, user)
1763 unfollow(follower, user)
1768 |> Enum.each(fn followed ->
1769 ActivityPub.unfollow(user, followed)
1770 unfollow(user, followed)
1773 delete_user_activities(user)
1774 delete_notifications_from_user_activities(user)
1775 delete_outgoing_pending_follow_requests(user)
1777 maybe_delete_from_db(user)
1780 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1782 @spec external_users_query() :: Ecto.Query.t()
1783 def external_users_query do
1791 @spec external_users(keyword()) :: [User.t()]
1792 def external_users(opts \\ []) do
1794 external_users_query()
1795 |> select([u], struct(u, [:id, :ap_id]))
1799 do: where(query, [u], u.id > ^opts[:max_id]),
1804 do: limit(query, ^opts[:limit]),
1810 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1812 |> join(:inner, [n], activity in assoc(n, :activity))
1813 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1814 |> Repo.delete_all()
1817 def delete_user_activities(%User{ap_id: ap_id} = user) do
1819 |> Activity.Queries.by_actor()
1820 |> Repo.chunk_stream(50, :batches)
1821 |> Stream.each(fn activities ->
1822 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1827 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1828 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1829 {:ok, delete_data, _} <- Builder.delete(user, object) do
1830 Pipeline.common_pipeline(delete_data, local: user.local)
1832 {:find_object, nil} ->
1833 # We have the create activity, but not the object, it was probably pruned.
1834 # Insert a tombstone and try again
1835 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1836 {:ok, _tombstone} <- Object.create(tombstone_data) do
1837 delete_activity(activity, user)
1841 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1842 Logger.error("Error: #{inspect(e)}")
1846 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1847 when type in ["Like", "Announce"] do
1848 {:ok, undo, _} = Builder.undo(user, activity)
1849 Pipeline.common_pipeline(undo, local: user.local)
1852 defp delete_activity(_activity, _user), do: "Doing nothing"
1854 defp delete_outgoing_pending_follow_requests(user) do
1856 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1857 |> Repo.delete_all()
1860 def html_filter_policy(%User{no_rich_text: true}) do
1861 Pleroma.HTML.Scrubber.TwitterText
1864 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1866 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1868 def get_or_fetch_by_ap_id(ap_id) do
1869 cached_user = get_cached_by_ap_id(ap_id)
1871 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1873 case {cached_user, maybe_fetched_user} do
1874 {_, {:ok, %User{} = user}} ->
1877 {%User{} = user, _} ->
1881 {:error, :not_found}
1886 Creates an internal service actor by URI if missing.
1887 Optionally takes nickname for addressing.
1889 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1890 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1892 case get_cached_by_ap_id(uri) do
1894 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1895 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1899 %User{invisible: false} = user ->
1909 @spec set_invisible(User.t()) :: {:ok, User.t()}
1910 defp set_invisible(user) do
1912 |> change(%{invisible: true})
1913 |> update_and_set_cache()
1916 @spec create_service_actor(String.t(), String.t()) ::
1917 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1918 defp create_service_actor(uri, nickname) do
1924 follower_address: uri <> "/followers"
1927 |> unique_constraint(:nickname)
1932 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1935 |> :public_key.pem_decode()
1937 |> :public_key.pem_entry_decode()
1942 def public_key(_), do: {:error, "key not found"}
1944 def get_public_key_for_ap_id(ap_id) do
1945 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1946 {:ok, public_key} <- public_key(user) do
1953 def ap_enabled?(%User{local: true}), do: true
1954 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1955 def ap_enabled?(_), do: false
1957 @doc "Gets or fetch a user by uri or nickname."
1958 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1959 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1960 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1962 # wait a period of time and return newest version of the User structs
1963 # this is because we have synchronous follow APIs and need to simulate them
1964 # with an async handshake
1965 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1966 with %User{} = a <- get_cached_by_id(a.id),
1967 %User{} = b <- get_cached_by_id(b.id) do
1974 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1975 with :ok <- :timer.sleep(timeout),
1976 %User{} = a <- get_cached_by_id(a.id),
1977 %User{} = b <- get_cached_by_id(b.id) do
1984 def parse_bio(bio) when is_binary(bio) and bio != "" do
1986 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1990 def parse_bio(_), do: ""
1992 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1993 # TODO: get profile URLs other than user.ap_id
1994 profile_urls = [user.ap_id]
1997 |> CommonUtils.format_input("text/plain",
1998 mentions_format: :full,
1999 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
2004 def parse_bio(_, _), do: ""
2006 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2007 Repo.transaction(fn ->
2008 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2012 def tag(nickname, tags) when is_binary(nickname),
2013 do: tag(get_by_nickname(nickname), tags)
2015 def tag(%User{} = user, tags),
2016 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2018 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2019 Repo.transaction(fn ->
2020 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2024 def untag(nickname, tags) when is_binary(nickname),
2025 do: untag(get_by_nickname(nickname), tags)
2027 def untag(%User{} = user, tags),
2028 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2030 defp update_tags(%User{} = user, new_tags) do
2031 {:ok, updated_user} =
2033 |> change(%{tags: new_tags})
2034 |> update_and_set_cache()
2039 defp normalize_tags(tags) do
2042 |> Enum.map(&String.downcase/1)
2045 defp local_nickname_regex do
2046 if Config.get([:instance, :extended_nickname_format]) do
2047 @extended_local_nickname_regex
2049 @strict_local_nickname_regex
2053 def local_nickname(nickname_or_mention) do
2056 |> String.split("@")
2060 def full_nickname(%User{} = user) do
2061 if String.contains?(user.nickname, "@") do
2064 %{host: host} = URI.parse(user.ap_id)
2065 user.nickname <> "@" <> host
2069 def full_nickname(nickname_or_mention),
2070 do: String.trim_leading(nickname_or_mention, "@")
2072 def error_user(ap_id) do
2076 nickname: "erroruser@example.com",
2077 inserted_at: NaiveDateTime.utc_now()
2081 @spec all_superusers() :: [User.t()]
2082 def all_superusers do
2083 User.Query.build(%{super_users: true, local: true, is_active: true})
2087 def muting_reblogs?(%User{} = user, %User{} = target) do
2088 UserRelationship.reblog_mute_exists?(user, target)
2091 def showing_reblogs?(%User{} = user, %User{} = target) do
2092 not muting_reblogs?(user, target)
2096 The function returns a query to get users with no activity for given interval of days.
2097 Inactive users are those who didn't read any notification, or had any activity where
2098 the user is the activity's actor, during `inactivity_threshold` days.
2099 Deactivated users will not appear in this list.
2103 iex> Pleroma.User.list_inactive_users()
2106 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2107 def list_inactive_users_query(inactivity_threshold \\ 7) do
2108 negative_inactivity_threshold = -inactivity_threshold
2109 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2110 # Subqueries are not supported in `where` clauses, join gets too complicated.
2111 has_read_notifications =
2112 from(n in Pleroma.Notification,
2113 where: n.seen == true,
2115 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2118 |> Pleroma.Repo.all()
2120 from(u in Pleroma.User,
2121 left_join: a in Pleroma.Activity,
2122 on: u.ap_id == a.actor,
2123 where: not is_nil(u.nickname),
2124 where: u.is_active == ^true,
2125 where: u.id not in ^has_read_notifications,
2128 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2129 is_nil(max(a.inserted_at))
2134 Enable or disable email notifications for user
2138 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2139 Pleroma.User{email_notifications: %{"digest" => true}}
2141 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2142 Pleroma.User{email_notifications: %{"digest" => false}}
2144 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2145 {:ok, t()} | {:error, Ecto.Changeset.t()}
2146 def switch_email_notifications(user, type, status) do
2147 User.update_email_notifications(user, %{type => status})
2151 Set `last_digest_emailed_at` value for the user to current time
2153 @spec touch_last_digest_emailed_at(t()) :: t()
2154 def touch_last_digest_emailed_at(user) do
2155 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2157 {:ok, updated_user} =
2159 |> change(%{last_digest_emailed_at: now})
2160 |> update_and_set_cache()
2165 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2166 def set_confirmation(%User{} = user, bool) do
2168 |> confirmation_changeset(set_confirmation: bool)
2169 |> update_and_set_cache()
2172 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2176 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2177 # use instance-default
2178 config = Config.get([:assets, :mascots])
2179 default_mascot = Config.get([:assets, :default_mascot])
2180 mascot = Keyword.get(config, default_mascot)
2183 "id" => "default-mascot",
2184 "url" => mascot[:url],
2185 "preview_url" => mascot[:url],
2187 "mime_type" => mascot[:mime_type]
2192 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2194 def ensure_keys_present(%User{} = user) do
2195 with {:ok, pem} <- Keys.generate_rsa_pem() do
2197 |> cast(%{keys: pem}, [:keys])
2198 |> validate_required([:keys])
2199 |> update_and_set_cache()
2203 def get_ap_ids_by_nicknames(nicknames) do
2205 where: u.nickname in ^nicknames,
2211 defp put_password_hash(
2212 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2214 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2217 defp put_password_hash(changeset), do: changeset
2219 def is_internal_user?(%User{nickname: nil}), do: true
2220 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2221 def is_internal_user?(_), do: false
2223 # A hack because user delete activities have a fake id for whatever reason
2224 # TODO: Get rid of this
2225 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2227 def get_delivered_users_by_object_id(object_id) do
2229 inner_join: delivery in assoc(u, :deliveries),
2230 where: delivery.object_id == ^object_id
2235 def change_email(user, email) do
2237 |> cast(%{email: email}, [:email])
2238 |> validate_required([:email])
2239 |> unique_constraint(:email)
2240 |> validate_format(:email, @email_regex)
2241 |> update_and_set_cache()
2244 # Internal function; public one is `deactivate/2`
2245 defp set_activation_status(user, status) do
2247 |> cast(%{is_active: status}, [:is_active])
2248 |> update_and_set_cache()
2251 def update_banner(user, banner) do
2253 |> cast(%{banner: banner}, [:banner])
2254 |> update_and_set_cache()
2257 def update_background(user, background) do
2259 |> cast(%{background: background}, [:background])
2260 |> update_and_set_cache()
2263 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2266 moderator: is_moderator
2270 def validate_fields(changeset, remote? \\ false) do
2271 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2272 limit = Config.get([:instance, limit_name], 0)
2275 |> validate_length(:fields, max: limit)
2276 |> validate_change(:fields, fn :fields, fields ->
2277 if Enum.all?(fields, &valid_field?/1) do
2285 defp valid_field?(%{"name" => name, "value" => value}) do
2286 name_limit = Config.get([:instance, :account_field_name_length], 255)
2287 value_limit = Config.get([:instance, :account_field_value_length], 255)
2289 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2290 String.length(value) <= value_limit
2293 defp valid_field?(_), do: false
2295 defp truncate_field(%{"name" => name, "value" => value}) do
2297 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2300 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2302 %{"name" => name, "value" => value}
2305 def admin_api_update(user, params) do
2312 |> update_and_set_cache()
2315 @doc "Signs user out of all applications"
2316 def global_sign_out(user) do
2317 OAuth.Authorization.delete_user_authorizations(user)
2318 OAuth.Token.delete_user_tokens(user)
2321 def mascot_update(user, url) do
2323 |> cast(%{mascot: url}, [:mascot])
2324 |> validate_required([:mascot])
2325 |> update_and_set_cache()
2328 def mastodon_settings_update(user, settings) do
2330 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2331 |> validate_required([:mastofe_settings])
2332 |> update_and_set_cache()
2335 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2336 def confirmation_changeset(user, set_confirmation: confirmed?) do
2341 confirmation_token: nil
2345 is_confirmed: false,
2346 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2350 cast(user, params, [:is_confirmed, :confirmation_token])
2353 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2354 def approval_changeset(user, set_approval: approved?) do
2355 cast(user, %{is_approved: approved?}, [:is_approved])
2358 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2359 if id not in user.pinned_activities do
2360 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2361 params = %{pinned_activities: user.pinned_activities ++ [id]}
2363 # if pinned activity was scheduled for deletion, we remove job
2364 if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
2365 Oban.cancel_job(expiration.id)
2369 |> cast(params, [:pinned_activities])
2370 |> validate_length(:pinned_activities,
2371 max: max_pinned_statuses,
2372 message: "You have already pinned the maximum number of statuses"
2377 |> update_and_set_cache()
2380 def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
2381 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2383 # if pinned activity was scheduled for deletion, we reschedule it for deletion
2384 if data["expires_at"] do
2385 # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
2387 data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
2389 Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
2391 expires_at: expires_at
2396 |> cast(params, [:pinned_activities])
2397 |> update_and_set_cache()
2400 def update_email_notifications(user, settings) do
2401 email_notifications =
2402 user.email_notifications
2403 |> Map.merge(settings)
2404 |> Map.take(["digest"])
2406 params = %{email_notifications: email_notifications}
2407 fields = [:email_notifications]
2410 |> cast(params, fields)
2411 |> validate_required(fields)
2412 |> update_and_set_cache()
2415 defp set_domain_blocks(user, domain_blocks) do
2416 params = %{domain_blocks: domain_blocks}
2419 |> cast(params, [:domain_blocks])
2420 |> validate_required([:domain_blocks])
2421 |> update_and_set_cache()
2424 def block_domain(user, domain_blocked) do
2425 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2428 def unblock_domain(user, domain_blocked) do
2429 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2432 @spec add_to_block(User.t(), User.t()) ::
2433 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2434 defp add_to_block(%User{} = user, %User{} = blocked) do
2435 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2436 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2441 @spec add_to_block(User.t(), User.t()) ::
2442 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2443 defp remove_from_block(%User{} = user, %User{} = blocked) do
2444 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2445 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2450 def set_invisible(user, invisible) do
2451 params = %{invisible: invisible}
2454 |> cast(params, [:invisible])
2455 |> validate_required([:invisible])
2456 |> update_and_set_cache()
2459 def sanitize_html(%User{} = user) do
2460 sanitize_html(user, nil)
2463 # User data that mastodon isn't filtering (treated as plaintext):
2466 def sanitize_html(%User{} = user, filter) do
2468 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2471 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2476 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2477 |> Map.put(:fields, fields)
2480 def get_host(%User{ap_id: ap_id} = _user) do
2481 URI.parse(ap_id).host
2484 def update_last_active_at(%__MODULE__{local: true} = user) do
2486 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2487 |> update_and_set_cache()
2490 def active_user_count(weeks \\ 4) do
2491 active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
2494 |> where([u], u.last_active_at >= ^active_after)
2495 |> where([u], u.local == true)
2496 |> Repo.aggregate(:count)