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