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