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