1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
13 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.EctoType.ActivityPub.ObjectValidators
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
24 alias Pleroma.Notification
26 alias Pleroma.Registration
29 alias Pleroma.UserRelationship
31 alias Pleroma.Web.ActivityPub.ActivityPub
32 alias Pleroma.Web.ActivityPub.Builder
33 alias Pleroma.Web.ActivityPub.Pipeline
34 alias Pleroma.Web.ActivityPub.Utils
35 alias Pleroma.Web.CommonAPI
36 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
37 alias Pleroma.Web.OAuth
38 alias Pleroma.Web.RelMe
39 alias Pleroma.Workers.BackgroundWorker
43 @type t :: %__MODULE__{}
44 @type account_status ::
47 | :password_reset_pending
48 | :confirmation_pending
50 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
52 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
53 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
54 @url_regex ~r/^https?:\/\/[^\s]{1,256}$/
56 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
57 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
59 # AP ID user relationships (blocks, mutes etc.)
60 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
61 @user_relationships_config [
63 blocker_blocks: :blocked_users,
64 blockee_blocks: :blocker_users
67 muter_mutes: :muted_users,
68 mutee_mutes: :muter_users
71 reblog_muter_mutes: :reblog_muted_users,
72 reblog_mutee_mutes: :reblog_muter_users
75 notification_muter_mutes: :notification_muted_users,
76 notification_mutee_mutes: :notification_muter_users
78 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
79 inverse_subscription: [
80 subscribee_subscriptions: :subscriber_users,
81 subscriber_subscriptions: :subscribee_users
85 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
88 field(:bio, :string, default: "")
89 field(:raw_bio, :string)
90 field(:email, :string)
92 field(:nickname, :string)
93 field(:password_hash, :string)
94 field(:password, :string, virtual: true)
95 field(:password_confirmation, :string, virtual: true)
97 field(:public_key, :string)
98 field(:ap_id, :string)
99 field(:avatar, :map, default: %{})
100 field(:local, :boolean, default: true)
101 field(:follower_address, :string)
102 field(:following_address, :string)
103 field(:search_rank, :float, virtual: true)
104 field(:search_type, :integer, virtual: true)
105 field(:tags, {:array, :string}, default: [])
106 field(:last_refreshed_at, :naive_datetime_usec)
107 field(:last_digest_emailed_at, :naive_datetime)
108 field(:banner, :map, default: %{})
109 field(:background, :map, default: %{})
110 field(:note_count, :integer, default: 0)
111 field(:follower_count, :integer, default: 0)
112 field(:following_count, :integer, default: 0)
113 field(:is_locked, :boolean, default: false)
114 field(:confirmation_pending, :boolean, default: false)
115 field(:password_reset_pending, :boolean, default: false)
116 field(:approval_pending, :boolean, default: false)
117 field(:registration_reason, :string, default: nil)
118 field(:confirmation_token, :string, default: nil)
119 field(:default_scope, :string, default: "public")
120 field(:domain_blocks, {:array, :string}, default: [])
121 field(:deactivated, :boolean, default: false)
122 field(:no_rich_text, :boolean, default: false)
123 field(:ap_enabled, :boolean, default: false)
124 field(:is_moderator, :boolean, default: false)
125 field(:is_admin, :boolean, default: false)
126 field(:show_role, :boolean, default: true)
127 field(:mastofe_settings, :map, default: nil)
128 field(:uri, ObjectValidators.Uri, default: nil)
129 field(:hide_followers_count, :boolean, default: false)
130 field(:hide_follows_count, :boolean, default: false)
131 field(:hide_followers, :boolean, default: false)
132 field(:hide_follows, :boolean, default: false)
133 field(:hide_favorites, :boolean, default: true)
134 field(:pinned_activities, {:array, :string}, default: [])
135 field(:email_notifications, :map, default: %{"digest" => false})
136 field(:mascot, :map, default: nil)
137 field(:emoji, :map, default: %{})
138 field(:pleroma_settings_store, :map, default: %{})
139 field(:fields, {:array, :map}, default: [])
140 field(:raw_fields, {:array, :map}, default: [])
141 field(:is_discoverable, :boolean, default: false)
142 field(:invisible, :boolean, default: false)
143 field(:allow_following_move, :boolean, default: true)
144 field(:skip_thread_containment, :boolean, default: false)
145 field(:actor_type, :string, default: "Person")
146 field(:also_known_as, {:array, :string}, default: [])
147 field(:inbox, :string)
148 field(:shared_inbox, :string)
149 field(:accepts_chat_messages, :boolean, default: nil)
152 :notification_settings,
153 Pleroma.User.NotificationSetting,
157 has_many(:notifications, Notification)
158 has_many(:registrations, Registration)
159 has_many(:deliveries, Delivery)
161 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
162 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
164 for {relationship_type,
166 {outgoing_relation, outgoing_relation_target},
167 {incoming_relation, incoming_relation_source}
168 ]} <- @user_relationships_config do
169 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
170 # :notification_muter_mutes, :subscribee_subscriptions
171 has_many(outgoing_relation, UserRelationship,
172 foreign_key: :source_id,
173 where: [relationship_type: relationship_type]
176 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
177 # :notification_mutee_mutes, :subscriber_subscriptions
178 has_many(incoming_relation, UserRelationship,
179 foreign_key: :target_id,
180 where: [relationship_type: relationship_type]
183 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
184 # :notification_muted_users, :subscriber_users
185 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
187 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
188 # :notification_muter_users, :subscribee_users
189 has_many(incoming_relation_source, through: [incoming_relation, :source])
192 # `:blocks` is deprecated (replaced with `blocked_users` relation)
193 field(:blocks, {:array, :string}, default: [])
194 # `:mutes` is deprecated (replaced with `muted_users` relation)
195 field(:mutes, {:array, :string}, default: [])
196 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
197 field(:muted_reblogs, {:array, :string}, default: [])
198 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
199 field(:muted_notifications, {:array, :string}, default: [])
200 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
201 field(:subscribers, {:array, :string}, default: [])
204 :multi_factor_authentication_settings,
212 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
213 @user_relationships_config do
214 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
215 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
216 # `def subscriber_users/2`
217 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
218 target_users_query = assoc(user, unquote(outgoing_relation_target))
220 if restrict_deactivated? do
221 restrict_deactivated(target_users_query)
227 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
228 # `def notification_muted_users/2`, `def subscriber_users/2`
229 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
231 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
233 restrict_deactivated?
238 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
239 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
240 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
242 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
244 restrict_deactivated?
246 |> select([u], u.ap_id)
251 def cached_blocked_users_ap_ids(user) do
252 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
253 blocked_users_ap_ids(user)
257 def cached_muted_users_ap_ids(user) do
258 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
259 muted_users_ap_ids(user)
263 defdelegate following_count(user), to: FollowingRelationship
264 defdelegate following(user), to: FollowingRelationship
265 defdelegate following?(follower, followed), to: FollowingRelationship
266 defdelegate following_ap_ids(user), to: FollowingRelationship
267 defdelegate get_follow_requests(user), to: FollowingRelationship
268 defdelegate search(query, opts \\ []), to: User.Search
271 Dumps Flake Id to SQL-compatible format (16-byte UUID).
272 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
274 def binary_id(source_id) when is_binary(source_id) do
275 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
282 def binary_id(source_ids) when is_list(source_ids) do
283 Enum.map(source_ids, &binary_id/1)
286 def binary_id(%User{} = user), do: binary_id(user.id)
288 @doc "Returns status account"
289 @spec account_status(User.t()) :: account_status()
290 def account_status(%User{deactivated: true}), do: :deactivated
291 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
292 def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
294 def account_status(%User{local: true, confirmation_pending: true}) do
295 if Config.get([:instance, :account_activation_required]) do
296 :confirmation_pending
302 def account_status(%User{}), do: :active
304 @spec visible_for(User.t(), User.t() | nil) ::
307 | :restricted_unauthenticated
309 | :confirmation_pending
310 def visible_for(user, for_user \\ nil)
312 def visible_for(%User{invisible: true}, _), do: :invisible
314 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
316 def visible_for(%User{} = user, nil) do
317 if restrict_unauthenticated?(user) do
318 :restrict_unauthenticated
320 visible_account_status(user)
324 def visible_for(%User{} = user, for_user) do
325 if superuser?(for_user) do
328 visible_account_status(user)
332 def visible_for(_, _), do: :invisible
334 defp restrict_unauthenticated?(%User{local: true}) do
335 Config.restrict_unauthenticated_access?(:profiles, :local)
338 defp restrict_unauthenticated?(%User{local: _}) do
339 Config.restrict_unauthenticated_access?(:profiles, :remote)
342 defp visible_account_status(user) do
343 status = account_status(user)
345 if status in [:active, :password_reset_pending] do
352 @spec superuser?(User.t()) :: boolean()
353 def superuser?(%User{local: true, is_admin: true}), do: true
354 def superuser?(%User{local: true, is_moderator: true}), do: true
355 def superuser?(_), do: false
357 @spec invisible?(User.t()) :: boolean()
358 def invisible?(%User{invisible: true}), do: true
359 def invisible?(_), do: false
361 def avatar_url(user, options \\ []) do
363 %{"url" => [%{"href" => href} | _]} ->
367 unless options[:no_default] do
368 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
373 def banner_url(user, options \\ []) do
375 %{"url" => [%{"href" => href} | _]} -> href
376 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
380 # Should probably be renamed or removed
381 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
383 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
384 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
386 @spec ap_following(User.t()) :: String.t()
387 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
388 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
390 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
391 def restrict_deactivated(query) do
392 from(u in query, where: u.deactivated != ^true)
395 defp truncate_fields_param(params) do
396 if Map.has_key?(params, :fields) do
397 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
403 defp truncate_if_exists(params, key, max_length) do
404 if Map.has_key?(params, key) and is_binary(params[key]) do
405 {value, _chopped} = String.split_at(params[key], max_length)
406 Map.put(params, key, value)
412 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
414 defp fix_follower_address(%{nickname: nickname} = params),
415 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
417 defp fix_follower_address(params), do: params
419 def remote_user_changeset(struct \\ %User{local: false}, params) do
420 bio_limit = Config.get([:instance, :user_bio_length], 5000)
421 name_limit = Config.get([:instance, :user_name_length], 100)
424 case params[:name] do
425 name when is_binary(name) and byte_size(name) > 0 -> name
426 _ -> params[:nickname]
431 |> Map.put(:name, name)
432 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
433 |> truncate_if_exists(:name, name_limit)
434 |> truncate_if_exists(:bio, bio_limit)
435 |> truncate_fields_param()
436 |> fix_follower_address()
459 :hide_followers_count,
468 :accepts_chat_messages
471 |> cast(params, [:name], empty_values: [])
472 |> validate_required([:ap_id])
473 |> validate_required([:name], trim: false)
474 |> unique_constraint(:nickname)
475 |> validate_format(:nickname, @email_regex)
476 |> validate_length(:bio, max: bio_limit)
477 |> validate_length(:name, max: name_limit)
478 |> validate_fields(true)
479 |> validate_non_local()
482 defp validate_non_local(cng) do
483 local? = get_field(cng, :local)
487 |> add_error(:local, "User is local, can't update with this changeset.")
493 def update_changeset(struct, params \\ %{}) do
494 bio_limit = Config.get([:instance, :user_bio_length], 5000)
495 name_limit = Config.get([:instance, :user_name_length], 100)
515 :hide_followers_count,
518 :allow_following_move,
522 :skip_thread_containment,
525 :pleroma_settings_store,
528 :accepts_chat_messages
531 |> unique_constraint(:nickname)
532 |> validate_format(:nickname, local_nickname_regex())
533 |> validate_also_known_as()
534 |> validate_length(:bio, max: bio_limit)
535 |> validate_length(:name, min: 1, max: name_limit)
536 |> validate_inclusion(:actor_type, ["Person", "Service"])
539 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
540 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
541 |> put_change_if_present(:banner, &put_upload(&1, :banner))
542 |> put_change_if_present(:background, &put_upload(&1, :background))
543 |> put_change_if_present(
544 :pleroma_settings_store,
545 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
547 |> validate_fields(false)
550 defp put_fields(changeset) do
551 if raw_fields = get_change(changeset, :raw_fields) do
554 |> Enum.filter(fn %{"name" => n} -> n != "" end)
558 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
561 |> put_change(:raw_fields, raw_fields)
562 |> put_change(:fields, fields)
568 defp parse_fields(value) do
570 |> Formatter.linkify(mentions_format: :full)
574 defp put_emoji(changeset) do
575 emojified_fields = [:bio, :name, :raw_fields]
577 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
578 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
579 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
581 emoji = Map.merge(bio, name)
585 |> get_field(:raw_fields)
586 |> Enum.reduce(emoji, fn x, acc ->
587 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
590 put_change(changeset, :emoji, emoji)
596 defp put_change_if_present(changeset, map_field, value_function) do
597 with {:ok, value} <- fetch_change(changeset, map_field),
598 {:ok, new_value} <- value_function.(value) do
599 put_change(changeset, map_field, new_value)
605 defp put_upload(value, type) do
606 with %Plug.Upload{} <- value,
607 {:ok, object} <- ActivityPub.upload(value, type: type) do
612 def update_as_admin_changeset(struct, params) do
614 |> update_changeset(params)
615 |> cast(params, [:email])
616 |> delete_change(:also_known_as)
617 |> unique_constraint(:email)
618 |> validate_format(:email, @email_regex)
619 |> validate_inclusion(:actor_type, ["Person", "Service"])
622 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
623 def update_as_admin(user, params) do
624 params = Map.put(params, "password_confirmation", params["password"])
625 changeset = update_as_admin_changeset(user, params)
627 if params["password"] do
628 reset_password(user, changeset, params)
630 User.update_and_set_cache(changeset)
634 def password_update_changeset(struct, params) do
636 |> cast(params, [:password, :password_confirmation])
637 |> validate_required([:password, :password_confirmation])
638 |> validate_confirmation(:password)
639 |> put_password_hash()
640 |> put_change(:password_reset_pending, false)
643 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
644 def reset_password(%User{} = user, params) do
645 reset_password(user, user, params)
648 def reset_password(%User{id: user_id} = user, struct, params) do
651 |> Multi.update(:user, password_update_changeset(struct, params))
652 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
653 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
655 case Repo.transaction(multi) do
656 {:ok, %{user: user} = _} -> set_cache(user)
657 {:error, _, changeset, _} -> {:error, changeset}
661 def update_password_reset_pending(user, value) do
664 |> put_change(:password_reset_pending, value)
665 |> update_and_set_cache()
668 def force_password_reset_async(user) do
669 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
672 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
673 def force_password_reset(user), do: update_password_reset_pending(user, true)
675 # Used to auto-register LDAP accounts which won't have a password hash stored locally
676 def register_changeset_ldap(struct, params = %{password: password})
677 when is_nil(password) do
678 params = Map.put_new(params, :accepts_chat_messages, true)
681 if Map.has_key?(params, :email) do
682 Map.put_new(params, :email, params[:email])
692 :accepts_chat_messages
694 |> validate_required([:name, :nickname])
695 |> unique_constraint(:nickname)
696 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
697 |> validate_format(:nickname, local_nickname_regex())
699 |> unique_constraint(:ap_id)
700 |> put_following_and_follower_address()
703 def register_changeset(struct, params \\ %{}, opts \\ []) do
704 bio_limit = Config.get([:instance, :user_bio_length], 5000)
705 name_limit = Config.get([:instance, :user_name_length], 100)
706 reason_limit = Config.get([:instance, :registration_reason_length], 500)
707 params = Map.put_new(params, :accepts_chat_messages, true)
710 if is_nil(opts[:need_confirmation]) do
711 Config.get([:instance, :account_activation_required])
713 opts[:need_confirmation]
717 if is_nil(opts[:need_approval]) do
718 Config.get([:instance, :account_approval_required])
724 |> confirmation_changeset(need_confirmation: need_confirmation?)
725 |> approval_changeset(need_approval: need_approval?)
733 :password_confirmation,
735 :accepts_chat_messages,
738 |> validate_required([:name, :nickname, :password, :password_confirmation])
739 |> validate_confirmation(:password)
740 |> unique_constraint(:email)
741 |> validate_format(:email, @email_regex)
742 |> validate_change(:email, fn :email, email ->
744 Config.get([User, :email_blacklist])
745 |> Enum.all?(fn blacklisted_domain ->
746 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
749 if valid?, do: [], else: [email: "Invalid email"]
751 |> unique_constraint(:nickname)
752 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
753 |> validate_format(:nickname, local_nickname_regex())
754 |> validate_length(:bio, max: bio_limit)
755 |> validate_length(:name, min: 1, max: name_limit)
756 |> validate_length(:registration_reason, max: reason_limit)
757 |> maybe_validate_required_email(opts[:external])
760 |> unique_constraint(:ap_id)
761 |> put_following_and_follower_address()
764 def maybe_validate_required_email(changeset, true), do: changeset
766 def maybe_validate_required_email(changeset, _) do
767 if Config.get([:instance, :account_activation_required]) do
768 validate_required(changeset, [:email])
774 defp put_ap_id(changeset) do
775 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
776 put_change(changeset, :ap_id, ap_id)
779 defp put_following_and_follower_address(changeset) do
780 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
783 |> put_change(:follower_address, followers)
786 defp autofollow_users(user) do
787 candidates = Config.get([:instance, :autofollowed_nicknames])
790 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
793 follow_all(user, autofollowed_users)
796 defp autofollowing_users(user) do
797 candidates = Config.get([:instance, :autofollowing_nicknames])
799 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
801 |> Enum.each(&follow(&1, user, :follow_accept))
806 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
807 def register(%Ecto.Changeset{} = changeset) do
808 with {:ok, user} <- Repo.insert(changeset) do
809 post_register_action(user)
813 def post_register_action(%User{confirmation_pending: true} = user) do
814 with {:ok, _} <- try_send_confirmation_email(user) do
819 def post_register_action(%User{approval_pending: true} = user) do
820 with {:ok, _} <- send_user_approval_email(user),
821 {:ok, _} <- send_admin_approval_emails(user) do
826 def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
827 with {:ok, user} <- autofollow_users(user),
828 {:ok, _} <- autofollowing_users(user),
829 {:ok, user} <- set_cache(user),
830 {:ok, _} <- send_welcome_email(user),
831 {:ok, _} <- send_welcome_message(user),
832 {:ok, _} <- send_welcome_chat_message(user) do
837 defp send_user_approval_email(user) do
839 |> Pleroma.Emails.UserEmail.approval_pending_email()
840 |> Pleroma.Emails.Mailer.deliver_async()
845 defp send_admin_approval_emails(user) do
847 |> Enum.filter(fn user -> not is_nil(user.email) end)
848 |> Enum.each(fn superuser ->
850 |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
851 |> Pleroma.Emails.Mailer.deliver_async()
857 def send_welcome_message(user) do
858 if User.WelcomeMessage.enabled?() do
859 User.WelcomeMessage.post_message(user)
866 def send_welcome_chat_message(user) do
867 if User.WelcomeChatMessage.enabled?() do
868 User.WelcomeChatMessage.post_message(user)
875 def send_welcome_email(%User{email: email} = user) when is_binary(email) do
876 if User.WelcomeEmail.enabled?() do
877 User.WelcomeEmail.send_email(user)
884 def send_welcome_email(_), do: {:ok, :noop}
886 @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
887 def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
888 when is_binary(email) do
889 if Config.get([:instance, :account_activation_required]) do
890 send_confirmation_email(user)
897 def try_send_confirmation_email(_), do: {:ok, :noop}
899 @spec send_confirmation_email(Uset.t()) :: User.t()
900 def send_confirmation_email(%User{} = user) do
902 |> Pleroma.Emails.UserEmail.account_confirmation_email()
903 |> Pleroma.Emails.Mailer.deliver_async()
908 def needs_update?(%User{local: true}), do: false
910 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
912 def needs_update?(%User{local: false} = user) do
913 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
916 def needs_update?(_), do: true
918 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
920 # "Locked" (self-locked) users demand explicit authorization of follow requests
921 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
922 follow(follower, followed, :follow_pending)
925 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
926 follow(follower, followed)
929 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
930 if not ap_enabled?(followed) do
931 follow(follower, followed)
933 {:ok, follower, followed}
937 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
938 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
939 def follow_all(follower, followeds) do
941 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
942 |> Enum.each(&follow(follower, &1, :follow_accept))
947 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
948 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
951 followed.deactivated ->
952 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
954 deny_follow_blocked and blocks?(followed, follower) ->
955 {:error, "Could not follow user: #{followed.nickname} blocked you."}
958 FollowingRelationship.follow(follower, followed, state)
962 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
963 {:error, "Not subscribed!"}
966 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
967 def unfollow(%User{} = follower, %User{} = followed) do
968 case do_unfollow(follower, followed) do
969 {:ok, follower, followed} ->
970 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
977 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
978 defp do_unfollow(%User{} = follower, %User{} = followed) do
979 case get_follow_state(follower, followed) do
980 state when state in [:follow_pending, :follow_accept] ->
981 FollowingRelationship.unfollow(follower, followed)
984 {:error, "Not subscribed!"}
988 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
989 def get_follow_state(%User{} = follower, %User{} = following) do
990 following_relationship = FollowingRelationship.get(follower, following)
991 get_follow_state(follower, following, following_relationship)
994 def get_follow_state(
997 following_relationship
999 case {following_relationship, following.local} do
1001 case Utils.fetch_latest_follow(follower, following) do
1002 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
1003 FollowingRelationship.state_to_enum(state)
1009 {%{state: state}, _} ->
1017 def locked?(%User{} = user) do
1018 user.is_locked || false
1021 def get_by_id(id) do
1022 Repo.get_by(User, id: id)
1025 def get_by_ap_id(ap_id) do
1026 Repo.get_by(User, ap_id: ap_id)
1029 def get_all_by_ap_id(ap_ids) do
1030 from(u in __MODULE__,
1031 where: u.ap_id in ^ap_ids
1036 def get_all_by_ids(ids) do
1037 from(u in __MODULE__, where: u.id in ^ids)
1041 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1042 # of the ap_id and the domain and tries to get that user
1043 def get_by_guessed_nickname(ap_id) do
1044 domain = URI.parse(ap_id).host
1045 name = List.last(String.split(ap_id, "/"))
1046 nickname = "#{name}@#{domain}"
1048 get_cached_by_nickname(nickname)
1051 def set_cache({:ok, user}), do: set_cache(user)
1052 def set_cache({:error, err}), do: {:error, err}
1054 def set_cache(%User{} = user) do
1055 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1056 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1057 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1061 def update_and_set_cache(struct, params) do
1063 |> update_changeset(params)
1064 |> update_and_set_cache()
1067 def update_and_set_cache(changeset) do
1068 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1073 def get_user_friends_ap_ids(user) do
1074 from(u in User.get_friends_query(user), select: u.ap_id)
1078 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1079 def get_cached_user_friends_ap_ids(user) do
1080 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1081 get_user_friends_ap_ids(user)
1085 def invalidate_cache(user) do
1086 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1087 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1088 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1089 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1090 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1093 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1094 def get_cached_by_ap_id(ap_id) do
1095 key = "ap_id:#{ap_id}"
1097 with {:ok, nil} <- @cachex.get(:user_cache, key),
1098 user when not is_nil(user) <- get_by_ap_id(ap_id),
1099 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1107 def get_cached_by_id(id) do
1111 @cachex.fetch!(:user_cache, key, fn _ ->
1112 user = get_by_id(id)
1115 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1116 {:commit, user.ap_id}
1122 get_cached_by_ap_id(ap_id)
1125 def get_cached_by_nickname(nickname) do
1126 key = "nickname:#{nickname}"
1128 @cachex.fetch!(:user_cache, key, fn _ ->
1129 case get_or_fetch_by_nickname(nickname) do
1130 {:ok, user} -> {:commit, user}
1131 {:error, _error} -> {:ignore, nil}
1136 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1137 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1140 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1141 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1143 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1144 get_cached_by_nickname(nickname_or_id)
1146 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1147 get_cached_by_nickname(nickname_or_id)
1154 @spec get_by_nickname(String.t()) :: User.t() | nil
1155 def get_by_nickname(nickname) do
1156 Repo.get_by(User, nickname: nickname) ||
1157 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1158 Repo.get_by(User, nickname: local_nickname(nickname))
1162 def get_by_email(email), do: Repo.get_by(User, email: email)
1164 def get_by_nickname_or_email(nickname_or_email) do
1165 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1168 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1170 def get_or_fetch_by_nickname(nickname) do
1171 with %User{} = user <- get_by_nickname(nickname) do
1175 with [_nick, _domain] <- String.split(nickname, "@"),
1176 {:ok, user} <- fetch_by_nickname(nickname) do
1179 _e -> {:error, "not found " <> nickname}
1184 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1185 def get_followers_query(%User{} = user, nil) do
1186 User.Query.build(%{followers: user, deactivated: false})
1189 def get_followers_query(%User{} = user, page) do
1191 |> get_followers_query(nil)
1192 |> User.Query.paginate(page, 20)
1195 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1196 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1198 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1199 def get_followers(%User{} = user, page \\ nil) do
1201 |> get_followers_query(page)
1205 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1206 def get_external_followers(%User{} = user, page \\ nil) do
1208 |> get_followers_query(page)
1209 |> User.Query.build(%{external: true})
1213 def get_followers_ids(%User{} = user, page \\ nil) do
1215 |> get_followers_query(page)
1216 |> select([u], u.id)
1220 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1221 def get_friends_query(%User{} = user, nil) do
1222 User.Query.build(%{friends: user, deactivated: false})
1225 def get_friends_query(%User{} = user, page) do
1227 |> get_friends_query(nil)
1228 |> User.Query.paginate(page, 20)
1231 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1232 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1234 def get_friends(%User{} = user, page \\ nil) do
1236 |> get_friends_query(page)
1240 def get_friends_ap_ids(%User{} = user) do
1242 |> get_friends_query(nil)
1243 |> select([u], u.ap_id)
1247 def get_friends_ids(%User{} = user, page \\ nil) do
1249 |> get_friends_query(page)
1250 |> select([u], u.id)
1254 def increase_note_count(%User{} = user) do
1256 |> where(id: ^user.id)
1257 |> update([u], inc: [note_count: 1])
1259 |> Repo.update_all([])
1261 {1, [user]} -> set_cache(user)
1266 def decrease_note_count(%User{} = user) do
1268 |> where(id: ^user.id)
1271 note_count: fragment("greatest(0, note_count - 1)")
1275 |> Repo.update_all([])
1277 {1, [user]} -> set_cache(user)
1282 def update_note_count(%User{} = user, note_count \\ nil) do
1287 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1293 |> cast(%{note_count: note_count}, [:note_count])
1294 |> update_and_set_cache()
1297 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1298 def maybe_fetch_follow_information(user) do
1299 with {:ok, user} <- fetch_follow_information(user) do
1303 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1309 def fetch_follow_information(user) do
1310 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1312 |> follow_information_changeset(info)
1313 |> update_and_set_cache()
1317 defp follow_information_changeset(user, params) do
1324 :hide_followers_count,
1329 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1330 def update_follower_count(%User{} = user) do
1331 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1332 follower_count = FollowingRelationship.follower_count(user)
1335 |> follow_information_changeset(%{follower_count: follower_count})
1336 |> update_and_set_cache
1338 {:ok, maybe_fetch_follow_information(user)}
1342 @spec update_following_count(User.t()) :: {:ok, User.t()}
1343 def update_following_count(%User{local: false} = user) do
1344 if Config.get([:instance, :external_user_synchronization]) do
1345 {:ok, maybe_fetch_follow_information(user)}
1351 def update_following_count(%User{local: true} = user) do
1352 following_count = FollowingRelationship.following_count(user)
1355 |> follow_information_changeset(%{following_count: following_count})
1356 |> update_and_set_cache()
1359 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1360 def get_users_from_set(ap_ids, opts \\ []) do
1361 local_only = Keyword.get(opts, :local_only, true)
1362 criteria = %{ap_id: ap_ids, deactivated: false}
1363 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1365 User.Query.build(criteria)
1369 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1370 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1373 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1379 @spec mute(User.t(), User.t(), map()) ::
1380 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1381 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1382 notifications? = Map.get(params, :notifications, true)
1383 expires_in = Map.get(params, :expires_in, 0)
1385 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1386 {:ok, user_notification_mute} <-
1387 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1389 if expires_in > 0 do
1390 Pleroma.Workers.MuteExpireWorker.enqueue(
1392 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1393 schedule_in: expires_in
1397 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1399 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1403 def unmute(%User{} = muter, %User{} = mutee) do
1404 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1405 {:ok, user_notification_mute} <-
1406 UserRelationship.delete_notification_mute(muter, mutee) do
1407 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1408 {:ok, [user_mute, user_notification_mute]}
1412 def unmute(muter_id, mutee_id) do
1413 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1414 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1415 unmute(muter, mutee)
1417 {who, result} = error ->
1419 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1426 def subscribe(%User{} = subscriber, %User{} = target) do
1427 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1429 if blocks?(target, subscriber) and deny_follow_blocked do
1430 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1432 # Note: the relationship is inverse: subscriber acts as relationship target
1433 UserRelationship.create_inverse_subscription(target, subscriber)
1437 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1438 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1439 subscribe(subscriber, subscribee)
1443 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1444 # Note: the relationship is inverse: subscriber acts as relationship target
1445 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1448 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1449 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1450 unsubscribe(unsubscriber, user)
1454 def block(%User{} = blocker, %User{} = blocked) do
1455 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1457 if following?(blocker, blocked) do
1458 {:ok, blocker, _} = unfollow(blocker, blocked)
1464 # clear any requested follows as well
1466 case CommonAPI.reject_follow_request(blocked, blocker) do
1467 {:ok, %User{} = updated_blocked} -> updated_blocked
1471 unsubscribe(blocked, blocker)
1473 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1474 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1476 {:ok, blocker} = update_follower_count(blocker)
1477 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1478 add_to_block(blocker, blocked)
1481 # helper to handle the block given only an actor's AP id
1482 def block(%User{} = blocker, %{ap_id: ap_id}) do
1483 block(blocker, get_cached_by_ap_id(ap_id))
1486 def unblock(%User{} = blocker, %User{} = blocked) do
1487 remove_from_block(blocker, blocked)
1490 # helper to handle the block given only an actor's AP id
1491 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1492 unblock(blocker, get_cached_by_ap_id(ap_id))
1495 def mutes?(nil, _), do: false
1496 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1498 def mutes_user?(%User{} = user, %User{} = target) do
1499 UserRelationship.mute_exists?(user, target)
1502 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1503 def muted_notifications?(nil, _), do: false
1505 def muted_notifications?(%User{} = user, %User{} = target),
1506 do: UserRelationship.notification_mute_exists?(user, target)
1508 def blocks?(nil, _), do: false
1510 def blocks?(%User{} = user, %User{} = target) do
1511 blocks_user?(user, target) ||
1512 (blocks_domain?(user, target) and not User.following?(user, target))
1515 def blocks_user?(%User{} = user, %User{} = target) do
1516 UserRelationship.block_exists?(user, target)
1519 def blocks_user?(_, _), do: false
1521 def blocks_domain?(%User{} = user, %User{} = target) do
1522 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1523 %{host: host} = URI.parse(target.ap_id)
1524 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1527 def blocks_domain?(_, _), do: false
1529 def subscribed_to?(%User{} = user, %User{} = target) do
1530 # Note: the relationship is inverse: subscriber acts as relationship target
1531 UserRelationship.inverse_subscription_exists?(target, user)
1534 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1535 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1536 subscribed_to?(user, target)
1541 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1542 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1544 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1545 def outgoing_relationships_ap_ids(_user, []), do: %{}
1547 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1549 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1550 when is_list(relationship_types) do
1553 |> assoc(:outgoing_relationships)
1554 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1555 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1556 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1557 |> group_by([user_rel, u], user_rel.relationship_type)
1559 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1564 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1568 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1570 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1572 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1574 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1575 when is_list(relationship_types) do
1577 |> assoc(:incoming_relationships)
1578 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1579 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1580 |> maybe_filter_on_ap_id(ap_ids)
1581 |> select([user_rel, u], u.ap_id)
1586 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1587 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1590 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1592 def deactivate_async(user, status \\ true) do
1593 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1596 def deactivate(user, status \\ true)
1598 def deactivate(users, status) when is_list(users) do
1599 Repo.transaction(fn ->
1600 for user <- users, do: deactivate(user, status)
1604 def deactivate(%User{} = user, status) do
1605 with {:ok, user} <- set_activation_status(user, status) do
1608 |> Enum.filter(& &1.local)
1609 |> Enum.each(&set_cache(update_following_count(&1)))
1611 # Only update local user counts, remote will be update during the next pull.
1614 |> Enum.filter(& &1.local)
1615 |> Enum.each(&do_unfollow(user, &1))
1621 def approve(users) when is_list(users) do
1622 Repo.transaction(fn ->
1623 Enum.map(users, fn user ->
1624 with {:ok, user} <- approve(user), do: user
1629 def approve(%User{approval_pending: true} = user) do
1630 with chg <- change(user, approval_pending: false),
1631 {:ok, user} <- update_and_set_cache(chg) do
1632 post_register_action(user)
1637 def approve(%User{} = user), do: {:ok, user}
1639 def confirm(users) when is_list(users) do
1640 Repo.transaction(fn ->
1641 Enum.map(users, fn user ->
1642 with {:ok, user} <- confirm(user), do: user
1647 def confirm(%User{confirmation_pending: true} = user) do
1648 with chg <- confirmation_changeset(user, need_confirmation: false),
1649 {:ok, user} <- update_and_set_cache(chg) do
1650 post_register_action(user)
1655 def confirm(%User{} = user), do: {:ok, user}
1657 def update_notification_settings(%User{} = user, settings) do
1659 |> cast(%{notification_settings: settings}, [])
1660 |> cast_embed(:notification_settings)
1661 |> validate_required([:notification_settings])
1662 |> update_and_set_cache()
1665 @spec purge_user_changeset(User.t()) :: Changeset.t()
1666 def purge_user_changeset(user) do
1667 # "Right to be forgotten"
1668 # https://gdpr.eu/right-to-be-forgotten/
1679 last_refreshed_at: nil,
1680 last_digest_emailed_at: nil,
1687 confirmation_pending: false,
1688 password_reset_pending: false,
1689 approval_pending: false,
1690 registration_reason: nil,
1691 confirmation_token: nil,
1695 is_moderator: false,
1697 mastofe_settings: nil,
1700 pleroma_settings_store: %{},
1703 is_discoverable: false,
1708 def delete(users) when is_list(users) do
1709 for user <- users, do: delete(user)
1712 def delete(%User{} = user) do
1713 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1716 defp delete_and_invalidate_cache(%User{} = user) do
1717 invalidate_cache(user)
1721 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1723 defp delete_or_deactivate(%User{local: true} = user) do
1724 status = account_status(user)
1727 :confirmation_pending ->
1728 delete_and_invalidate_cache(user)
1730 :approval_pending ->
1731 delete_and_invalidate_cache(user)
1735 |> purge_user_changeset()
1736 |> update_and_set_cache()
1740 def perform(:force_password_reset, user), do: force_password_reset(user)
1742 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1743 def perform(:delete, %User{} = user) do
1744 # Remove all relationships
1747 |> Enum.each(fn follower ->
1748 ActivityPub.unfollow(follower, user)
1749 unfollow(follower, user)
1754 |> Enum.each(fn followed ->
1755 ActivityPub.unfollow(user, followed)
1756 unfollow(user, followed)
1759 delete_user_activities(user)
1760 delete_notifications_from_user_activities(user)
1762 delete_outgoing_pending_follow_requests(user)
1764 delete_or_deactivate(user)
1767 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1769 @spec external_users_query() :: Ecto.Query.t()
1770 def external_users_query do
1778 @spec external_users(keyword()) :: [User.t()]
1779 def external_users(opts \\ []) do
1781 external_users_query()
1782 |> select([u], struct(u, [:id, :ap_id]))
1786 do: where(query, [u], u.id > ^opts[:max_id]),
1791 do: limit(query, ^opts[:limit]),
1797 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1799 |> join(:inner, [n], activity in assoc(n, :activity))
1800 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1801 |> Repo.delete_all()
1804 def delete_user_activities(%User{ap_id: ap_id} = user) do
1806 |> Activity.Queries.by_actor()
1807 |> Repo.chunk_stream(50, :batches)
1808 |> Stream.each(fn activities ->
1809 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1814 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1815 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1816 {:ok, delete_data, _} <- Builder.delete(user, object) do
1817 Pipeline.common_pipeline(delete_data, local: user.local)
1819 {:find_object, nil} ->
1820 # We have the create activity, but not the object, it was probably pruned.
1821 # Insert a tombstone and try again
1822 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1823 {:ok, _tombstone} <- Object.create(tombstone_data) do
1824 delete_activity(activity, user)
1828 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1829 Logger.error("Error: #{inspect(e)}")
1833 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1834 when type in ["Like", "Announce"] do
1835 {:ok, undo, _} = Builder.undo(user, activity)
1836 Pipeline.common_pipeline(undo, local: user.local)
1839 defp delete_activity(_activity, _user), do: "Doing nothing"
1841 defp delete_outgoing_pending_follow_requests(user) do
1843 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1844 |> Repo.delete_all()
1847 def html_filter_policy(%User{no_rich_text: true}) do
1848 Pleroma.HTML.Scrubber.TwitterText
1851 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1853 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1855 def get_or_fetch_by_ap_id(ap_id) do
1856 cached_user = get_cached_by_ap_id(ap_id)
1858 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1860 case {cached_user, maybe_fetched_user} do
1861 {_, {:ok, %User{} = user}} ->
1864 {%User{} = user, _} ->
1868 {:error, :not_found}
1873 Creates an internal service actor by URI if missing.
1874 Optionally takes nickname for addressing.
1876 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1877 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1879 case get_cached_by_ap_id(uri) do
1881 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1882 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1886 %User{invisible: false} = user ->
1896 @spec set_invisible(User.t()) :: {:ok, User.t()}
1897 defp set_invisible(user) do
1899 |> change(%{invisible: true})
1900 |> update_and_set_cache()
1903 @spec create_service_actor(String.t(), String.t()) ::
1904 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1905 defp create_service_actor(uri, nickname) do
1911 follower_address: uri <> "/followers"
1914 |> unique_constraint(:nickname)
1919 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1922 |> :public_key.pem_decode()
1924 |> :public_key.pem_entry_decode()
1929 def public_key(_), do: {:error, "key not found"}
1931 def get_public_key_for_ap_id(ap_id) do
1932 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1933 {:ok, public_key} <- public_key(user) do
1940 def ap_enabled?(%User{local: true}), do: true
1941 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1942 def ap_enabled?(_), do: false
1944 @doc "Gets or fetch a user by uri or nickname."
1945 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1946 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1947 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1949 # wait a period of time and return newest version of the User structs
1950 # this is because we have synchronous follow APIs and need to simulate them
1951 # with an async handshake
1952 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1953 with %User{} = a <- get_cached_by_id(a.id),
1954 %User{} = b <- get_cached_by_id(b.id) do
1961 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1962 with :ok <- :timer.sleep(timeout),
1963 %User{} = a <- get_cached_by_id(a.id),
1964 %User{} = b <- get_cached_by_id(b.id) do
1971 def parse_bio(bio) when is_binary(bio) and bio != "" do
1973 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1977 def parse_bio(_), do: ""
1979 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1980 # TODO: get profile URLs other than user.ap_id
1981 profile_urls = [user.ap_id]
1984 |> CommonUtils.format_input("text/plain",
1985 mentions_format: :full,
1986 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1991 def parse_bio(_, _), do: ""
1993 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1994 Repo.transaction(fn ->
1995 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1999 def tag(nickname, tags) when is_binary(nickname),
2000 do: tag(get_by_nickname(nickname), tags)
2002 def tag(%User{} = user, tags),
2003 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2005 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2006 Repo.transaction(fn ->
2007 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2011 def untag(nickname, tags) when is_binary(nickname),
2012 do: untag(get_by_nickname(nickname), tags)
2014 def untag(%User{} = user, tags),
2015 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2017 defp update_tags(%User{} = user, new_tags) do
2018 {:ok, updated_user} =
2020 |> change(%{tags: new_tags})
2021 |> update_and_set_cache()
2026 defp normalize_tags(tags) do
2029 |> Enum.map(&String.downcase/1)
2032 defp local_nickname_regex do
2033 if Config.get([:instance, :extended_nickname_format]) do
2034 @extended_local_nickname_regex
2036 @strict_local_nickname_regex
2040 def local_nickname(nickname_or_mention) do
2043 |> String.split("@")
2047 def full_nickname(nickname_or_mention),
2048 do: String.trim_leading(nickname_or_mention, "@")
2050 def error_user(ap_id) do
2054 nickname: "erroruser@example.com",
2055 inserted_at: NaiveDateTime.utc_now()
2059 @spec all_superusers() :: [User.t()]
2060 def all_superusers do
2061 User.Query.build(%{super_users: true, local: true, deactivated: false})
2065 def muting_reblogs?(%User{} = user, %User{} = target) do
2066 UserRelationship.reblog_mute_exists?(user, target)
2069 def showing_reblogs?(%User{} = user, %User{} = target) do
2070 not muting_reblogs?(user, target)
2074 The function returns a query to get users with no activity for given interval of days.
2075 Inactive users are those who didn't read any notification, or had any activity where
2076 the user is the activity's actor, during `inactivity_threshold` days.
2077 Deactivated users will not appear in this list.
2081 iex> Pleroma.User.list_inactive_users()
2084 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2085 def list_inactive_users_query(inactivity_threshold \\ 7) do
2086 negative_inactivity_threshold = -inactivity_threshold
2087 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2088 # Subqueries are not supported in `where` clauses, join gets too complicated.
2089 has_read_notifications =
2090 from(n in Pleroma.Notification,
2091 where: n.seen == true,
2093 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2096 |> Pleroma.Repo.all()
2098 from(u in Pleroma.User,
2099 left_join: a in Pleroma.Activity,
2100 on: u.ap_id == a.actor,
2101 where: not is_nil(u.nickname),
2102 where: u.deactivated != ^true,
2103 where: u.id not in ^has_read_notifications,
2106 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2107 is_nil(max(a.inserted_at))
2112 Enable or disable email notifications for user
2116 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2117 Pleroma.User{email_notifications: %{"digest" => true}}
2119 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2120 Pleroma.User{email_notifications: %{"digest" => false}}
2122 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2123 {:ok, t()} | {:error, Ecto.Changeset.t()}
2124 def switch_email_notifications(user, type, status) do
2125 User.update_email_notifications(user, %{type => status})
2129 Set `last_digest_emailed_at` value for the user to current time
2131 @spec touch_last_digest_emailed_at(t()) :: t()
2132 def touch_last_digest_emailed_at(user) do
2133 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2135 {:ok, updated_user} =
2137 |> change(%{last_digest_emailed_at: now})
2138 |> update_and_set_cache()
2143 @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2144 def need_confirmation(%User{} = user, bool) do
2146 |> confirmation_changeset(need_confirmation: bool)
2147 |> update_and_set_cache()
2150 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2154 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2155 # use instance-default
2156 config = Config.get([:assets, :mascots])
2157 default_mascot = Config.get([:assets, :default_mascot])
2158 mascot = Keyword.get(config, default_mascot)
2161 "id" => "default-mascot",
2162 "url" => mascot[:url],
2163 "preview_url" => mascot[:url],
2165 "mime_type" => mascot[:mime_type]
2170 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2172 def ensure_keys_present(%User{} = user) do
2173 with {:ok, pem} <- Keys.generate_rsa_pem() do
2175 |> cast(%{keys: pem}, [:keys])
2176 |> validate_required([:keys])
2177 |> update_and_set_cache()
2181 def get_ap_ids_by_nicknames(nicknames) do
2183 where: u.nickname in ^nicknames,
2189 defp put_password_hash(
2190 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2192 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2195 defp put_password_hash(changeset), do: changeset
2197 def is_internal_user?(%User{nickname: nil}), do: true
2198 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2199 def is_internal_user?(_), do: false
2201 # A hack because user delete activities have a fake id for whatever reason
2202 # TODO: Get rid of this
2203 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2205 def get_delivered_users_by_object_id(object_id) do
2207 inner_join: delivery in assoc(u, :deliveries),
2208 where: delivery.object_id == ^object_id
2213 def change_email(user, email) do
2215 |> cast(%{email: email}, [:email])
2216 |> validate_required([:email])
2217 |> unique_constraint(:email)
2218 |> validate_format(:email, @email_regex)
2219 |> update_and_set_cache()
2222 # Internal function; public one is `deactivate/2`
2223 defp set_activation_status(user, deactivated) do
2225 |> cast(%{deactivated: deactivated}, [:deactivated])
2226 |> update_and_set_cache()
2229 def update_banner(user, banner) do
2231 |> cast(%{banner: banner}, [:banner])
2232 |> update_and_set_cache()
2235 def update_background(user, background) do
2237 |> cast(%{background: background}, [:background])
2238 |> update_and_set_cache()
2241 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2244 moderator: is_moderator
2248 def validate_fields(changeset, remote? \\ false) do
2249 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2250 limit = Config.get([:instance, limit_name], 0)
2253 |> validate_length(:fields, max: limit)
2254 |> validate_change(:fields, fn :fields, fields ->
2255 if Enum.all?(fields, &valid_field?/1) do
2263 defp valid_field?(%{"name" => name, "value" => value}) do
2264 name_limit = Config.get([:instance, :account_field_name_length], 255)
2265 value_limit = Config.get([:instance, :account_field_value_length], 255)
2267 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2268 String.length(value) <= value_limit
2271 defp valid_field?(_), do: false
2273 defp truncate_field(%{"name" => name, "value" => value}) do
2275 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2278 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2280 %{"name" => name, "value" => value}
2283 def admin_api_update(user, params) do
2290 |> update_and_set_cache()
2293 @doc "Signs user out of all applications"
2294 def global_sign_out(user) do
2295 OAuth.Authorization.delete_user_authorizations(user)
2296 OAuth.Token.delete_user_tokens(user)
2299 def mascot_update(user, url) do
2301 |> cast(%{mascot: url}, [:mascot])
2302 |> validate_required([:mascot])
2303 |> update_and_set_cache()
2306 def mastodon_settings_update(user, settings) do
2308 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2309 |> validate_required([:mastofe_settings])
2310 |> update_and_set_cache()
2313 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2314 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2316 if need_confirmation? do
2318 confirmation_pending: true,
2319 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2323 confirmation_pending: false,
2324 confirmation_token: nil
2328 cast(user, params, [:confirmation_pending, :confirmation_token])
2331 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2332 def approval_changeset(user, need_approval: need_approval?) do
2333 params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
2334 cast(user, params, [:approval_pending])
2337 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2338 if id not in user.pinned_activities do
2339 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2340 params = %{pinned_activities: user.pinned_activities ++ [id]}
2342 # if pinned activity was scheduled for deletion, we remove job
2343 if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
2344 Oban.cancel_job(expiration.id)
2348 |> cast(params, [:pinned_activities])
2349 |> validate_length(:pinned_activities,
2350 max: max_pinned_statuses,
2351 message: "You have already pinned the maximum number of statuses"
2356 |> update_and_set_cache()
2359 def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
2360 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2362 # if pinned activity was scheduled for deletion, we reschedule it for deletion
2363 if data["expires_at"] do
2364 # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
2366 data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
2368 Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
2370 expires_at: expires_at
2375 |> cast(params, [:pinned_activities])
2376 |> update_and_set_cache()
2379 def update_email_notifications(user, settings) do
2380 email_notifications =
2381 user.email_notifications
2382 |> Map.merge(settings)
2383 |> Map.take(["digest"])
2385 params = %{email_notifications: email_notifications}
2386 fields = [:email_notifications]
2389 |> cast(params, fields)
2390 |> validate_required(fields)
2391 |> update_and_set_cache()
2394 defp set_domain_blocks(user, domain_blocks) do
2395 params = %{domain_blocks: domain_blocks}
2398 |> cast(params, [:domain_blocks])
2399 |> validate_required([:domain_blocks])
2400 |> update_and_set_cache()
2403 def block_domain(user, domain_blocked) do
2404 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2407 def unblock_domain(user, domain_blocked) do
2408 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2411 @spec add_to_block(User.t(), User.t()) ::
2412 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2413 defp add_to_block(%User{} = user, %User{} = blocked) do
2414 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2415 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2420 @spec add_to_block(User.t(), User.t()) ::
2421 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2422 defp remove_from_block(%User{} = user, %User{} = blocked) do
2423 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2424 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2429 def set_invisible(user, invisible) do
2430 params = %{invisible: invisible}
2433 |> cast(params, [:invisible])
2434 |> validate_required([:invisible])
2435 |> update_and_set_cache()
2438 def sanitize_html(%User{} = user) do
2439 sanitize_html(user, nil)
2442 # User data that mastodon isn't filtering (treated as plaintext):
2445 def sanitize_html(%User{} = user, filter) do
2447 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2450 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2455 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2456 |> Map.put(:fields, fields)
2459 defp validate_also_known_as(changeset) do
2460 validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as ->
2461 if Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do
2464 [also_known_as: "Invalid ap_id format. Must be a URL."]
2469 def get_host(%User{ap_id: ap_id} = _user) do
2470 URI.parse(ap_id).host