Merge branch 'feat/doc-improvements' into 'develop'
[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}}
108 when type in ["Article", "Event", "Note", "Video", "Page", "Question"] ->
109 true
110
111 _ ->
112 false
113 end
114 end
115
116 def do_remote_follow(conn, %{
117 "authorization" => %{"name" => username, "password" => password, "id" => id}
118 }) do
119 with %User{} = followee <- User.get_cached_by_id(id),
120 {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
121 {_, true, _} <- {
122 :auth,
123 AuthenticationPlug.checkpw(password, user.password_hash),
124 followee
125 },
126 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
127 conn
128 |> render("followed.html", %{error: false})
129 else
130 # Was already following user
131 {:error, "Could not follow user:" <> _rest} ->
132 render(conn, "followed.html", %{error: "Error following account"})
133
134 {:auth, _, followee} ->
135 conn
136 |> render("follow_login.html", %{
137 error: "Wrong username or password",
138 id: id,
139 name: followee.nickname,
140 avatar: User.avatar_url(followee)
141 })
142
143 e ->
144 Logger.debug("Remote follow failed with error #{inspect(e)}")
145 render(conn, "followed.html", %{error: "Something went wrong."})
146 end
147 end
148
149 def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
150 with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
151 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
152 conn
153 |> render("followed.html", %{error: false})
154 else
155 # Was already following user
156 {:error, "Could not follow user:" <> _rest} ->
157 render(conn, "followed.html", %{error: "Error following account"})
158
159 {:fetch_user, error} ->
160 Logger.debug("Remote follow failed with error #{inspect(error)}")
161 render(conn, "followed.html", %{error: "Could not find user"})
162
163 e ->
164 Logger.debug("Remote follow failed with error #{inspect(e)}")
165 render(conn, "followed.html", %{error: "Something went wrong."})
166 end
167 end
168
169 def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
170 with {:ok, _} <- Notification.read_one(user, notification_id) do
171 json(conn, %{status: "success"})
172 else
173 {:error, message} ->
174 conn
175 |> put_resp_content_type("application/json")
176 |> send_resp(403, Jason.encode!(%{"error" => message}))
177 end
178 end
179
180 def config(%{assigns: %{format: "xml"}} = conn, _params) do
181 instance = Pleroma.Config.get(:instance)
182
183 response = """
184 <config>
185 <site>
186 <name>#{Keyword.get(instance, :name)}</name>
187 <site>#{Web.base_url()}</site>
188 <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
189 <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
190 </site>
191 </config>
192 """
193
194 conn
195 |> put_resp_content_type("application/xml")
196 |> send_resp(200, response)
197 end
198
199 def config(conn, _params) do
200 instance = Pleroma.Config.get(:instance)
201
202 vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
203
204 uploadlimit = %{
205 uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
206 avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
207 backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
208 bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
209 }
210
211 data = %{
212 name: Keyword.get(instance, :name),
213 description: Keyword.get(instance, :description),
214 server: Web.base_url(),
215 textlimit: to_string(Keyword.get(instance, :limit)),
216 uploadlimit: uploadlimit,
217 closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
218 private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
219 vapidPublicKey: vapid_public_key,
220 accountActivationRequired:
221 bool_to_val(Keyword.get(instance, :account_activation_required, false)),
222 invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
223 safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
224 }
225
226 managed_config = Keyword.get(instance, :managed_config)
227
228 data =
229 if managed_config do
230 pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
231 Map.put(data, "pleromafe", pleroma_fe)
232 else
233 data
234 end
235
236 json(conn, %{site: data})
237 end
238
239 defp bool_to_val(true), do: "1"
240 defp bool_to_val(_), do: "0"
241 defp bool_to_val(true, val, _), do: val
242 defp bool_to_val(_, _, val), do: val
243
244 def frontend_configurations(conn, _params) do
245 config =
246 Pleroma.Config.get(:frontend_configurations, %{})
247 |> Enum.into(%{})
248
249 json(conn, config)
250 end
251
252 def version(%{assigns: %{format: "xml"}} = conn, _params) do
253 version = Pleroma.Application.named_version()
254
255 conn
256 |> put_resp_content_type("application/xml")
257 |> send_resp(200, "<version>#{version}</version>")
258 end
259
260 def version(conn, _params) do
261 json(conn, Pleroma.Application.named_version())
262 end
263
264 def emoji(conn, _params) do
265 emoji =
266 Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
267 Map.put(acc, code, %{image_url: file, tags: tags})
268 end)
269
270 json(conn, emoji)
271 end
272
273 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
274 with {:ok, _} <- User.update_notification_settings(user, params) do
275 json(conn, %{status: "success"})
276 end
277 end
278
279 def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
280 follow_import(conn, %{"list" => File.read!(listfile.path)})
281 end
282
283 def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
284 with lines <- String.split(list, "\n"),
285 followed_identifiers <-
286 Enum.map(lines, fn line ->
287 String.split(line, ",") |> List.first()
288 end)
289 |> List.delete("Account address") do
290 User.follow_import(follower, followed_identifiers)
291 json(conn, "job started")
292 end
293 end
294
295 def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
296 blocks_import(conn, %{"list" => File.read!(listfile.path)})
297 end
298
299 def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
300 with blocked_identifiers <- String.split(list) do
301 User.blocks_import(blocker, blocked_identifiers)
302 json(conn, "job started")
303 end
304 end
305
306 def change_password(%{assigns: %{user: user}} = conn, params) do
307 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
308 {:ok, user} ->
309 with {:ok, _user} <-
310 User.reset_password(user, %{
311 password: params["new_password"],
312 password_confirmation: params["new_password_confirmation"]
313 }) do
314 json(conn, %{status: "success"})
315 else
316 {:error, changeset} ->
317 {_, {error, _}} = Enum.at(changeset.errors, 0)
318 json(conn, %{error: "New password #{error}."})
319
320 _ ->
321 json(conn, %{error: "Unable to change password."})
322 end
323
324 {:error, msg} ->
325 json(conn, %{error: msg})
326 end
327 end
328
329 def change_email(%{assigns: %{user: user}} = conn, params) do
330 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
331 {:ok, user} ->
332 with {:ok, _user} <- User.change_email(user, params["email"]) do
333 json(conn, %{status: "success"})
334 else
335 {:error, changeset} ->
336 {_, {error, _}} = Enum.at(changeset.errors, 0)
337 json(conn, %{error: "Email #{error}."})
338
339 _ ->
340 json(conn, %{error: "Unable to change email."})
341 end
342
343 {:error, msg} ->
344 json(conn, %{error: msg})
345 end
346 end
347
348 def delete_account(%{assigns: %{user: user}} = conn, params) do
349 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
350 {:ok, user} ->
351 User.delete(user)
352 json(conn, %{status: "success"})
353
354 {:error, msg} ->
355 json(conn, %{error: msg})
356 end
357 end
358
359 def disable_account(%{assigns: %{user: user}} = conn, params) do
360 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
361 {:ok, user} ->
362 User.deactivate_async(user)
363 json(conn, %{status: "success"})
364
365 {:error, msg} ->
366 json(conn, %{error: msg})
367 end
368 end
369
370 def captcha(conn, _params) do
371 json(conn, Pleroma.Captcha.new())
372 end
373
374 def healthcheck(conn, _params) do
375 with true <- Config.get([:instance, :healthcheck]),
376 %{healthy: true} = info <- Healthcheck.system_info() do
377 json(conn, info)
378 else
379 %{healthy: false} = info ->
380 service_unavailable(conn, info)
381
382 _ ->
383 service_unavailable(conn, %{})
384 end
385 end
386
387 defp service_unavailable(conn, info) do
388 conn
389 |> put_status(:service_unavailable)
390 |> json(info)
391 end
392 end