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