a0c3e5c52907d50e237e9912d27433088e769f1c
[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(%{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