implement Move activities (#45)
[akkoma] / lib / pleroma / web / twitter_api / controllers / util_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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.Config
11 alias Pleroma.Emoji
12 alias Pleroma.Healthcheck
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.CommonAPI
16 alias Pleroma.Web.Plugs.OAuthScopesPlug
17 alias Pleroma.Web.WebFinger
18
19 plug(Pleroma.Web.ApiSpec.CastAndValidate when action != :remote_subscribe)
20 plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
21
22 plug(
23 OAuthScopesPlug,
24 %{scopes: ["write:accounts"]}
25 when action in [
26 :change_email,
27 :change_password,
28 :delete_account,
29 :update_notificaton_settings,
30 :disable_account,
31 :move_account,
32 :add_alias,
33 :delete_alias
34 ]
35 )
36
37 plug(
38 OAuthScopesPlug,
39 %{scopes: ["read:accounts"]}
40 when action in [
41 :list_aliases
42 ]
43 )
44
45 defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
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_interaction(%{body_params: %{ap_id: ap_id, profile: profile}} = conn, _params) do
78 with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
79 conn
80 |> json(%{url: String.replace(template, "{uri}", ap_id)})
81 else
82 _e -> json(conn, %{error: "Couldn't find user"})
83 end
84 end
85
86 def frontend_configurations(conn, _params) do
87 render(conn, "frontend_configurations.json")
88 end
89
90 def emoji(conn, _params) do
91 emoji =
92 Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
93 Map.put(acc, code, %{image_url: file, tags: tags})
94 end)
95
96 json(conn, emoji)
97 end
98
99 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
100 with {:ok, _} <- User.update_notification_settings(user, params) do
101 json(conn, %{status: "success"})
102 end
103 end
104
105 def change_password(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
106 case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
107 {:ok, user} ->
108 with {:ok, _user} <-
109 User.reset_password(user, %{
110 password: body_params.new_password,
111 password_confirmation: body_params.new_password_confirmation
112 }) do
113 json(conn, %{status: "success"})
114 else
115 {:error, changeset} ->
116 {_, {error, _}} = Enum.at(changeset.errors, 0)
117 json(conn, %{error: "New password #{error}."})
118
119 _ ->
120 json(conn, %{error: "Unable to change password."})
121 end
122
123 {:error, msg} ->
124 json(conn, %{error: msg})
125 end
126 end
127
128 def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
129 case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
130 {:ok, user} ->
131 with {:ok, _user} <- User.change_email(user, body_params.email) do
132 json(conn, %{status: "success"})
133 else
134 {:error, changeset} ->
135 {_, {error, _}} = Enum.at(changeset.errors, 0)
136 json(conn, %{error: "Email #{error}."})
137
138 _ ->
139 json(conn, %{error: "Unable to change email."})
140 end
141
142 {:error, msg} ->
143 json(conn, %{error: msg})
144 end
145 end
146
147 def delete_account(%{assigns: %{user: user}, body_params: body_params} = conn, params) do
148 # This endpoint can accept a query param or JSON body for backwards-compatibility.
149 # Submitting a JSON body is recommended, so passwords don't end up in server logs.
150 password = body_params[:password] || params[:password] || ""
151
152 case CommonAPI.Utils.confirm_current_password(user, password) do
153 {:ok, user} ->
154 User.delete(user)
155 json(conn, %{status: "success"})
156
157 {:error, msg} ->
158 json(conn, %{error: msg})
159 end
160 end
161
162 def disable_account(%{assigns: %{user: user}} = conn, params) do
163 case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
164 {:ok, user} ->
165 User.set_activation_async(user, false)
166 json(conn, %{status: "success"})
167
168 {:error, msg} ->
169 json(conn, %{error: msg})
170 end
171 end
172
173 def move_account(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
174 case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
175 {:ok, user} ->
176 with {:ok, target_user} <- find_or_fetch_user_by_nickname(body_params.target_account),
177 {:ok, _user} <- ActivityPub.move(user, target_user) do
178 json(conn, %{status: "success"})
179 else
180 {:not_found, _} ->
181 conn
182 |> put_status(404)
183 |> json(%{error: "Target account not found."})
184
185 {:error, error} ->
186 json(conn, %{error: error})
187 end
188
189 {:error, msg} ->
190 json(conn, %{error: msg})
191 end
192 end
193
194 def add_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
195 with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
196 {:ok, _user} <- user |> User.add_alias(alias_user) do
197 json(conn, %{status: "success"})
198 else
199 {:not_found, _} ->
200 conn
201 |> put_status(404)
202 |> json(%{error: "Target account does not exist."})
203
204 {:error, error} ->
205 json(conn, %{error: error})
206 end
207 end
208
209 def delete_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
210 with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
211 {:ok, _user} <- user |> User.delete_alias(alias_user) do
212 json(conn, %{status: "success"})
213 else
214 {:error, :no_such_alias} ->
215 conn
216 |> put_status(404)
217 |> json(%{error: "Account has no such alias."})
218
219 {:error, error} ->
220 json(conn, %{error: error})
221 end
222 end
223
224 def list_aliases(%{assigns: %{user: user}} = conn, %{}) do
225 alias_nicks =
226 user
227 |> User.alias_users()
228 |> Enum.map(&User.full_nickname/1)
229
230 json(conn, %{aliases: alias_nicks})
231 end
232
233 defp find_user_by_nickname(nickname) do
234 user = User.get_cached_by_nickname(nickname)
235
236 if user == nil do
237 {:not_found, nil}
238 else
239 {:ok, user}
240 end
241 end
242
243 defp find_or_fetch_user_by_nickname(nickname) do
244 user = User.get_by_nickname(nickname)
245
246 if user != nil and user.local do
247 {:ok, user}
248 else
249 with {:ok, user} <- User.fetch_by_nickname(nickname) do
250 {:ok, user}
251 else
252 _ ->
253 {:not_found, nil}
254 end
255 end
256 end
257
258 def captcha(conn, _params) do
259 json(conn, Pleroma.Captcha.new())
260 end
261
262 def healthcheck(conn, _params) do
263 with true <- Config.get([:instance, :healthcheck]),
264 %{healthy: true} = info <- Healthcheck.system_info() do
265 json(conn, info)
266 else
267 %{healthy: false} = info ->
268 service_unavailable(conn, info)
269
270 _ ->
271 service_unavailable(conn, %{})
272 end
273 end
274
275 defp service_unavailable(conn, info) do
276 conn
277 |> put_status(:service_unavailable)
278 |> json(info)
279 end
280 end