First
[anni] / lib / pleroma / web / twitter_api / controllers / util_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.TwitterAPI.UtilController do
6   use Pleroma.Web, :controller
7
8   require Logger
9
10   alias Pleroma.Activity
11   alias Pleroma.Config
12   alias Pleroma.Emoji
13   alias Pleroma.Healthcheck
14   alias Pleroma.User
15   alias Pleroma.Web.ActivityPub.ActivityPub
16   alias Pleroma.Web.CommonAPI
17   alias Pleroma.Web.Plugs.OAuthScopesPlug
18   alias Pleroma.Web.WebFinger
19
20   plug(
21     Pleroma.Web.ApiSpec.CastAndValidate
22     when action != :remote_subscribe and action != :show_subscribe_form
23   )
24
25   plug(
26     Pleroma.Web.Plugs.FederatingPlug
27     when action == :remote_subscribe
28     when action == :show_subscribe_form
29   )
30
31   plug(
32     OAuthScopesPlug,
33     %{scopes: ["write:accounts"]}
34     when action in [
35            :change_email,
36            :change_password,
37            :delete_account,
38            :update_notificaton_settings,
39            :disable_account,
40            :move_account,
41            :add_alias,
42            :delete_alias
43          ]
44   )
45
46   plug(
47     OAuthScopesPlug,
48     %{scopes: ["read:accounts"]}
49     when action in [
50            :list_aliases
51          ]
52   )
53
54   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
55
56   def show_subscribe_form(conn, %{"nickname" => nick}) do
57     with %User{} = user <- User.get_cached_by_nickname(nick),
58          avatar = User.avatar_url(user) do
59       conn
60       |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
61     else
62       _e ->
63         render(conn, "subscribe.html", %{
64           nickname: nick,
65           avatar: nil,
66           error:
67             Pleroma.Web.Gettext.dpgettext(
68               "static_pages",
69               "remote follow error message - user not found",
70               "Could not find user"
71             )
72         })
73     end
74   end
75
76   def show_subscribe_form(conn, %{"status_id" => id}) do
77     with %Activity{} = activity <- Activity.get_by_id(id),
78          {:ok, ap_id} <- get_ap_id(activity),
79          %User{} = user <- User.get_cached_by_ap_id(activity.actor),
80          avatar = User.avatar_url(user) do
81       conn
82       |> render("status_interact.html", %{
83         status_link: ap_id,
84         status_id: id,
85         nickname: user.nickname,
86         avatar: avatar,
87         error: false
88       })
89     else
90       _e ->
91         render(conn, "status_interact.html", %{
92           status_id: id,
93           avatar: nil,
94           error:
95             Pleroma.Web.Gettext.dpgettext(
96               "static_pages",
97               "status interact error message - status not found",
98               "Could not find status"
99             )
100         })
101     end
102   end
103
104   def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
105     show_subscribe_form(conn, %{"nickname" => nick})
106   end
107
108   def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do
109     show_subscribe_form(conn, %{"status_id" => id})
110   end
111
112   def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
113     with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
114          %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
115       conn
116       |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
117     else
118       _e ->
119         render(conn, "subscribe.html", %{
120           nickname: nick,
121           avatar: nil,
122           error:
123             Pleroma.Web.Gettext.dpgettext(
124               "static_pages",
125               "remote follow error message - unknown error",
126               "Something went wrong."
127             )
128         })
129     end
130   end
131
132   def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do
133     with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
134          %Activity{} = activity <- Activity.get_by_id(id),
135          {:ok, ap_id} <- get_ap_id(activity) do
136       conn
137       |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
138     else
139       _e ->
140         render(conn, "status_interact.html", %{
141           status_id: id,
142           avatar: nil,
143           error:
144             Pleroma.Web.Gettext.dpgettext(
145               "static_pages",
146               "status interact error message - unknown error",
147               "Something went wrong."
148             )
149         })
150     end
151   end
152
153   def remote_interaction(%{body_params: %{ap_id: ap_id, profile: profile}} = conn, _params) do
154     with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
155       conn
156       |> json(%{url: String.replace(template, "{uri}", ap_id)})
157     else
158       _e -> json(conn, %{error: "Couldn't find user"})
159     end
160   end
161
162   defp get_ap_id(activity) do
163     object = Pleroma.Object.normalize(activity, fetch: false)
164
165     case object do
166       %{data: %{"id" => ap_id}} -> {:ok, ap_id}
167       _ -> {:no_ap_id, nil}
168     end
169   end
170
171   def frontend_configurations(conn, _params) do
172     render(conn, "frontend_configurations.json")
173   end
174
175   def emoji(conn, _params) do
176     emoji =
177       Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
178         Map.put(acc, code, %{image_url: file, tags: tags})
179       end)
180
181     json(conn, emoji)
182   end
183
184   def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
185     with {:ok, _} <- User.update_notification_settings(user, params) do
186       json(conn, %{status: "success"})
187     end
188   end
189
190   def change_password(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
191     case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
192       {:ok, user} ->
193         with {:ok, _user} <-
194                User.reset_password(user, %{
195                  password: body_params.new_password,
196                  password_confirmation: body_params.new_password_confirmation
197                }) do
198           json(conn, %{status: "success"})
199         else
200           {:error, changeset} ->
201             {_, {error, _}} = Enum.at(changeset.errors, 0)
202             json(conn, %{error: "New password #{error}."})
203
204           _ ->
205             json(conn, %{error: "Unable to change password."})
206         end
207
208       {:error, msg} ->
209         json(conn, %{error: msg})
210     end
211   end
212
213   def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
214     case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
215       {:ok, user} ->
216         with {:ok, _user} <- User.change_email(user, body_params.email) do
217           json(conn, %{status: "success"})
218         else
219           {:error, changeset} ->
220             {_, {error, _}} = Enum.at(changeset.errors, 0)
221             json(conn, %{error: "Email #{error}."})
222
223           _ ->
224             json(conn, %{error: "Unable to change email."})
225         end
226
227       {:error, msg} ->
228         json(conn, %{error: msg})
229     end
230   end
231
232   def delete_account(%{assigns: %{user: user}, body_params: body_params} = conn, params) do
233     # This endpoint can accept a query param or JSON body for backwards-compatibility.
234     # Submitting a JSON body is recommended, so passwords don't end up in server logs.
235     password = body_params[:password] || params[:password] || ""
236
237     case CommonAPI.Utils.confirm_current_password(user, password) do
238       {:ok, user} ->
239         User.delete(user)
240         json(conn, %{status: "success"})
241
242       {:error, msg} ->
243         json(conn, %{error: msg})
244     end
245   end
246
247   def disable_account(%{assigns: %{user: user}} = conn, params) do
248     case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
249       {:ok, user} ->
250         User.set_activation_async(user, false)
251         json(conn, %{status: "success"})
252
253       {:error, msg} ->
254         json(conn, %{error: msg})
255     end
256   end
257
258   def move_account(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
259     case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
260       {:ok, user} ->
261         with {:ok, target_user} <- find_or_fetch_user_by_nickname(body_params.target_account),
262              {:ok, _user} <- ActivityPub.move(user, target_user) do
263           json(conn, %{status: "success"})
264         else
265           {:not_found, _} ->
266             conn
267             |> put_status(404)
268             |> json(%{error: "Target account not found."})
269
270           {:error, error} ->
271             json(conn, %{error: error})
272         end
273
274       {:error, msg} ->
275         json(conn, %{error: msg})
276     end
277   end
278
279   def add_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
280     with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
281          {:ok, _user} <- user |> User.add_alias(alias_user) do
282       json(conn, %{status: "success"})
283     else
284       {:not_found, _} ->
285         conn
286         |> put_status(404)
287         |> json(%{error: "Target account does not exist."})
288
289       {:error, error} ->
290         json(conn, %{error: error})
291     end
292   end
293
294   def delete_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
295     with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
296          {:ok, _user} <- user |> User.delete_alias(alias_user) do
297       json(conn, %{status: "success"})
298     else
299       {:error, :no_such_alias} ->
300         conn
301         |> put_status(404)
302         |> json(%{error: "Account has no such alias."})
303
304       {:error, error} ->
305         json(conn, %{error: error})
306     end
307   end
308
309   def list_aliases(%{assigns: %{user: user}} = conn, %{}) do
310     alias_nicks =
311       user
312       |> User.alias_users()
313       |> Enum.map(&User.full_nickname/1)
314
315     json(conn, %{aliases: alias_nicks})
316   end
317
318   defp find_user_by_nickname(nickname) do
319     user = User.get_cached_by_nickname(nickname)
320
321     if user == nil do
322       {:not_found, nil}
323     else
324       {:ok, user}
325     end
326   end
327
328   defp find_or_fetch_user_by_nickname(nickname) do
329     user = User.get_by_nickname(nickname)
330
331     if user != nil and user.local do
332       {:ok, user}
333     else
334       with {:ok, user} <- User.fetch_by_nickname(nickname) do
335         {:ok, user}
336       else
337         _ ->
338           {:not_found, nil}
339       end
340     end
341   end
342
343   def captcha(conn, _params) do
344     json(conn, Pleroma.Captcha.new())
345   end
346
347   def healthcheck(conn, _params) do
348     with true <- Config.get([:instance, :healthcheck]),
349          %{healthy: true} = info <- Healthcheck.system_info() do
350       json(conn, info)
351     else
352       %{healthy: false} = info ->
353         service_unavailable(conn, info)
354
355       _ ->
356         service_unavailable(conn, %{})
357     end
358   end
359
360   defp service_unavailable(conn, info) do
361     conn
362     |> put_status(:service_unavailable)
363     |> json(info)
364   end
365 end