giant massive dep upgrade and dialyxir-found error emporium (#371)
[akkoma] / lib / pleroma / web / twitter_api / controllers / util_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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(
154 %Plug.Conn{body_params: %{ap_id: ap_id, profile: profile}} = conn,
155 _params
156 ) do
157 with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
158 conn
159 |> json(%{url: String.replace(template, "{uri}", ap_id)})
160 else
161 _e -> json(conn, %{error: "Couldn't find user"})
162 end
163 end
164
165 defp get_ap_id(activity) do
166 object = Pleroma.Object.normalize(activity, fetch: false)
167
168 case object do
169 %{data: %{"id" => ap_id}} -> {:ok, ap_id}
170 _ -> {:no_ap_id, nil}
171 end
172 end
173
174 def frontend_configurations(conn, _params) do
175 render(conn, "frontend_configurations.json")
176 end
177
178 def emoji(conn, _params) do
179 emoji =
180 Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
181 Map.put(acc, code, %{image_url: file, tags: tags})
182 end)
183
184 json(conn, emoji)
185 end
186
187 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
188 with {:ok, _} <- User.update_notification_settings(user, params) do
189 json(conn, %{status: "success"})
190 end
191 end
192
193 def change_password(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
194 case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
195 {:ok, user} ->
196 with {:ok, _user} <-
197 User.reset_password(user, %{
198 password: body_params.new_password,
199 password_confirmation: body_params.new_password_confirmation
200 }) do
201 json(conn, %{status: "success"})
202 else
203 {:error, changeset} ->
204 {_, {error, _}} = Enum.at(changeset.errors, 0)
205 json(conn, %{error: "New password #{error}."})
206
207 _ ->
208 json(conn, %{error: "Unable to change password."})
209 end
210
211 {:error, msg} ->
212 json(conn, %{error: msg})
213 end
214 end
215
216 def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
217 case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
218 {:ok, user} ->
219 with {:ok, _user} <- User.change_email(user, body_params.email) do
220 json(conn, %{status: "success"})
221 else
222 {:error, changeset} ->
223 {_, {error, _}} = Enum.at(changeset.errors, 0)
224 json(conn, %{error: "Email #{error}."})
225
226 _ ->
227 json(conn, %{error: "Unable to change email."})
228 end
229
230 {:error, msg} ->
231 json(conn, %{error: msg})
232 end
233 end
234
235 def delete_account(%{assigns: %{user: user}, body_params: body_params} = conn, params) do
236 # This endpoint can accept a query param or JSON body for backwards-compatibility.
237 # Submitting a JSON body is recommended, so passwords don't end up in server logs.
238 password = body_params[:password] || params[:password] || ""
239
240 case CommonAPI.Utils.confirm_current_password(user, password) do
241 {:ok, user} ->
242 User.delete(user)
243 json(conn, %{status: "success"})
244
245 {:error, msg} ->
246 json(conn, %{error: msg})
247 end
248 end
249
250 def disable_account(%{assigns: %{user: user}} = conn, params) do
251 case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
252 {:ok, user} ->
253 User.set_activation_async(user, false)
254 json(conn, %{status: "success"})
255
256 {:error, msg} ->
257 json(conn, %{error: msg})
258 end
259 end
260
261 def move_account(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
262 case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
263 {:ok, user} ->
264 with {:ok, target_user} <- find_or_fetch_user_by_nickname(body_params.target_account),
265 {:ok, _user} <- ActivityPub.move(user, target_user) do
266 json(conn, %{status: "success"})
267 else
268 {:not_found, _} ->
269 conn
270 |> put_status(404)
271 |> json(%{error: "Target account not found."})
272
273 {:error, error} ->
274 json(conn, %{error: error})
275 end
276
277 {:error, msg} ->
278 json(conn, %{error: msg})
279 end
280 end
281
282 def add_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
283 with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
284 {:ok, _user} <- user |> User.add_alias(alias_user) do
285 json(conn, %{status: "success"})
286 else
287 {:not_found, _} ->
288 conn
289 |> put_status(404)
290 |> json(%{error: "Target account does not exist."})
291
292 {:error, error} ->
293 json(conn, %{error: error})
294 end
295 end
296
297 def delete_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
298 with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
299 {:ok, _user} <- user |> User.delete_alias(alias_user) do
300 json(conn, %{status: "success"})
301 else
302 {:error, :no_such_alias} ->
303 conn
304 |> put_status(404)
305 |> json(%{error: "Account has no such alias."})
306
307 {:error, error} ->
308 json(conn, %{error: error})
309 end
310 end
311
312 def list_aliases(%{assigns: %{user: user}} = conn, %{}) do
313 alias_nicks =
314 user
315 |> User.alias_users()
316 |> Enum.map(&User.full_nickname/1)
317
318 json(conn, %{aliases: alias_nicks})
319 end
320
321 defp find_user_by_nickname(nickname) do
322 user = User.get_cached_by_nickname(nickname)
323
324 if user == nil do
325 {:not_found, nil}
326 else
327 {:ok, user}
328 end
329 end
330
331 defp find_or_fetch_user_by_nickname(nickname) do
332 user = User.get_by_nickname(nickname)
333
334 if user != nil and user.local do
335 {:ok, user}
336 else
337 with {:ok, user} <- User.fetch_by_nickname(nickname) do
338 {:ok, user}
339 else
340 _ ->
341 {:not_found, nil}
342 end
343 end
344 end
345
346 def captcha(conn, _params) do
347 json(conn, Pleroma.Captcha.new())
348 end
349
350 def healthcheck(conn, _params) do
351 with true <- Config.get([:instance, :healthcheck]),
352 %{healthy: true} = info <- Healthcheck.system_info() do
353 json(conn, info)
354 else
355 %{healthy: false} = info ->
356 service_unavailable(conn, info)
357
358 _ ->
359 service_unavailable(conn, %{})
360 end
361 end
362
363 defp service_unavailable(conn, info) do
364 conn
365 |> put_status(:service_unavailable)
366 |> json(info)
367 end
368 end