[#1260] Merge remote-tracking branch 'remotes/upstream/develop' into 1260-rate-limite...
[akkoma] / lib / pleroma / web / twitter_api / controllers / util_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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.Notification
15 alias Pleroma.Plugs.AuthenticationPlug
16 alias Pleroma.User
17 alias Pleroma.Web
18 alias Pleroma.Web.CommonAPI
19 alias Pleroma.Web.WebFinger
20
21 plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
22
23 def help_test(conn, _params) do
24 json(conn, "ok")
25 end
26
27 def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
28 with %User{} = user <- User.get_cached_by_nickname(nick),
29 avatar = User.avatar_url(user) do
30 conn
31 |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
32 else
33 _e ->
34 render(conn, "subscribe.html", %{
35 nickname: nick,
36 avatar: nil,
37 error: "Could not find user"
38 })
39 end
40 end
41
42 def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
43 with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
44 %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
45 conn
46 |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
47 else
48 _e ->
49 render(conn, "subscribe.html", %{
50 nickname: nick,
51 avatar: nil,
52 error: "Something went wrong."
53 })
54 end
55 end
56
57 def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
58 if is_status?(acct) do
59 {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
60 %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
61 redirect(conn, to: "/notice/#{activity_id}")
62 else
63 with {:ok, followee} <- User.get_or_fetch(acct) do
64 conn
65 |> render(follow_template(user), %{
66 error: false,
67 acct: acct,
68 avatar: User.avatar_url(followee),
69 name: followee.nickname,
70 id: followee.id
71 })
72 else
73 {:error, _reason} ->
74 render(conn, follow_template(user), %{error: :error})
75 end
76 end
77 end
78
79 defp follow_template(%User{} = _user), do: "follow.html"
80 defp follow_template(_), do: "follow_login.html"
81
82 defp is_status?(acct) do
83 case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
84 {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
85 true
86
87 _ ->
88 false
89 end
90 end
91
92 def do_remote_follow(conn, %{
93 "authorization" => %{"name" => username, "password" => password, "id" => id}
94 }) do
95 with %User{} = followee <- User.get_cached_by_id(id),
96 {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
97 {_, true, _} <- {
98 :auth,
99 AuthenticationPlug.checkpw(password, user.password_hash),
100 followee
101 },
102 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
103 conn
104 |> render("followed.html", %{error: false})
105 else
106 # Was already following user
107 {:error, "Could not follow user:" <> _rest} ->
108 render(conn, "followed.html", %{error: "Error following account"})
109
110 {:auth, _, followee} ->
111 conn
112 |> render("follow_login.html", %{
113 error: "Wrong username or password",
114 id: id,
115 name: followee.nickname,
116 avatar: User.avatar_url(followee)
117 })
118
119 e ->
120 Logger.debug("Remote follow failed with error #{inspect(e)}")
121 render(conn, "followed.html", %{error: "Something went wrong."})
122 end
123 end
124
125 def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
126 with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
127 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
128 conn
129 |> render("followed.html", %{error: false})
130 else
131 # Was already following user
132 {:error, "Could not follow user:" <> _rest} ->
133 render(conn, "followed.html", %{error: "Error following account"})
134
135 {:fetch_user, error} ->
136 Logger.debug("Remote follow failed with error #{inspect(error)}")
137 render(conn, "followed.html", %{error: "Could not find user"})
138
139 e ->
140 Logger.debug("Remote follow failed with error #{inspect(e)}")
141 render(conn, "followed.html", %{error: "Something went wrong."})
142 end
143 end
144
145 def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
146 with {:ok, _} <- Notification.read_one(user, notification_id) do
147 json(conn, %{status: "success"})
148 else
149 {:error, message} ->
150 conn
151 |> put_resp_content_type("application/json")
152 |> send_resp(403, Jason.encode!(%{"error" => message}))
153 end
154 end
155
156 def config(%{assigns: %{format: "xml"}} = conn, _params) do
157 instance = Pleroma.Config.get(:instance)
158
159 response = """
160 <config>
161 <site>
162 <name>#{Keyword.get(instance, :name)}</name>
163 <site>#{Web.base_url()}</site>
164 <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
165 <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
166 </site>
167 </config>
168 """
169
170 conn
171 |> put_resp_content_type("application/xml")
172 |> send_resp(200, response)
173 end
174
175 def config(conn, _params) do
176 instance = Pleroma.Config.get(:instance)
177
178 vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
179
180 uploadlimit = %{
181 uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
182 avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
183 backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
184 bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
185 }
186
187 data = %{
188 name: Keyword.get(instance, :name),
189 description: Keyword.get(instance, :description),
190 server: Web.base_url(),
191 textlimit: to_string(Keyword.get(instance, :limit)),
192 uploadlimit: uploadlimit,
193 closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
194 private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
195 vapidPublicKey: vapid_public_key,
196 accountActivationRequired:
197 bool_to_val(Keyword.get(instance, :account_activation_required, false)),
198 invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
199 safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
200 }
201
202 managed_config = Keyword.get(instance, :managed_config)
203
204 data =
205 if managed_config do
206 pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
207 Map.put(data, "pleromafe", pleroma_fe)
208 else
209 data
210 end
211
212 json(conn, %{site: data})
213 end
214
215 defp bool_to_val(true), do: "1"
216 defp bool_to_val(_), do: "0"
217 defp bool_to_val(true, val, _), do: val
218 defp bool_to_val(_, _, val), do: val
219
220 def frontend_configurations(conn, _params) do
221 config =
222 Pleroma.Config.get(:frontend_configurations, %{})
223 |> Enum.into(%{})
224
225 json(conn, config)
226 end
227
228 def version(%{assigns: %{format: "xml"}} = conn, _params) do
229 version = Pleroma.Application.named_version()
230
231 conn
232 |> put_resp_content_type("application/xml")
233 |> send_resp(200, "<version>#{version}</version>")
234 end
235
236 def version(conn, _params) do
237 json(conn, Pleroma.Application.named_version())
238 end
239
240 def emoji(conn, _params) do
241 emoji =
242 Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
243 Map.put(acc, code, %{image_url: file, tags: tags})
244 end)
245
246 json(conn, emoji)
247 end
248
249 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
250 with {:ok, _} <- User.update_notification_settings(user, params) do
251 json(conn, %{status: "success"})
252 end
253 end
254
255 def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
256 follow_import(conn, %{"list" => File.read!(listfile.path)})
257 end
258
259 def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
260 with lines <- String.split(list, "\n"),
261 followed_identifiers <-
262 Enum.map(lines, fn line ->
263 String.split(line, ",") |> List.first()
264 end)
265 |> List.delete("Account address") do
266 User.follow_import(follower, followed_identifiers)
267 json(conn, "job started")
268 end
269 end
270
271 def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
272 blocks_import(conn, %{"list" => File.read!(listfile.path)})
273 end
274
275 def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
276 with blocked_identifiers <- String.split(list) do
277 User.blocks_import(blocker, blocked_identifiers)
278 json(conn, "job started")
279 end
280 end
281
282 def change_password(%{assigns: %{user: user}} = conn, params) do
283 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
284 {:ok, user} ->
285 with {:ok, _user} <-
286 User.reset_password(user, %{
287 password: params["new_password"],
288 password_confirmation: params["new_password_confirmation"]
289 }) do
290 json(conn, %{status: "success"})
291 else
292 {:error, changeset} ->
293 {_, {error, _}} = Enum.at(changeset.errors, 0)
294 json(conn, %{error: "New password #{error}."})
295
296 _ ->
297 json(conn, %{error: "Unable to change password."})
298 end
299
300 {:error, msg} ->
301 json(conn, %{error: msg})
302 end
303 end
304
305 def change_email(%{assigns: %{user: user}} = conn, params) do
306 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
307 {:ok, user} ->
308 with {:ok, _user} <- User.change_email(user, params["email"]) do
309 json(conn, %{status: "success"})
310 else
311 {:error, changeset} ->
312 {_, {error, _}} = Enum.at(changeset.errors, 0)
313 json(conn, %{error: "Email #{error}."})
314
315 _ ->
316 json(conn, %{error: "Unable to change email."})
317 end
318
319 {:error, msg} ->
320 json(conn, %{error: msg})
321 end
322 end
323
324 def delete_account(%{assigns: %{user: user}} = conn, params) do
325 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
326 {:ok, user} ->
327 User.delete(user)
328 json(conn, %{status: "success"})
329
330 {:error, msg} ->
331 json(conn, %{error: msg})
332 end
333 end
334
335 def disable_account(%{assigns: %{user: user}} = conn, params) do
336 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
337 {:ok, user} ->
338 User.deactivate_async(user)
339 json(conn, %{status: "success"})
340
341 {:error, msg} ->
342 json(conn, %{error: msg})
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