From 90a243845ed04e7da00259c31dffa48ef93d322b Mon Sep 17 00:00:00 2001 From: Ro Date: Thu, 26 Dec 2024 21:03:37 -0600 Subject: [PATCH] feat: Reject requests --- lib/activity_pub.ex | 59 +++++++++++------ lib/postland/activities.ex | 2 +- lib/postland/follows.ex | 64 +++++++++++++++---- .../components/core_components.ex | 12 +++- lib/postland_web/live/dev_live.ex | 28 ++++++++ lib/postland_web/live/followers_live.ex | 27 +++++++- lib/postland_web/live/user_login_live.ex | 26 ++++---- lib/postland_web/router.ex | 5 ++ 8 files changed, 175 insertions(+), 48 deletions(-) create mode 100644 lib/postland_web/live/dev_live.ex diff --git a/lib/activity_pub.ex b/lib/activity_pub.ex index b6c3aa7..a0817e8 100644 --- a/lib/activity_pub.ex +++ b/lib/activity_pub.ex @@ -18,34 +18,53 @@ defmodule ActivityPub do end def post_activity(sender_id, private_key, inbox, activity) do - body = Jason.encode!(activity) + if String.contains?(inbox, "example.com") do + {:ok, %{status: 200, body: "ok"}} + else + body = Jason.encode!(activity) - headers = - Headers.signing_headers( - "POST", - inbox, - body, - sender_id, - private_key - ) + headers = + Headers.signing_headers( + "POST", + inbox, + body, + sender_id, + private_key + ) - Req.post(inbox, headers: headers, body: body) + Req.post(inbox, headers: headers, body: body) + end end def fetch_actor(actor_id) do - request = - Req.new(url: actor_id) - |> Req.Request.put_header("accept", "application/json") + if String.contains?(actor_id, "example.com") do + {:ok, + %{ + "@context" => [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ], + "id" => actor_id, + "type" => "Person", + "preferredUsername" => "example", + "name" => "Example User", + "inbox" => "#{actor_id}/inbox" + }} + else + request = + Req.new(url: actor_id) + |> Req.Request.put_header("accept", "application/json") - case Req.get(request) do - {:ok, %{status: 200} = result} -> - {:ok, result.body} + case Req.get(request) do + {:ok, %{status: 200} = result} -> + {:ok, result.body} - {:ok, %{status: 404}} -> - nil + {:ok, %{status: 404}} -> + nil - error -> - error + error -> + error + end end end end diff --git a/lib/postland/activities.ex b/lib/postland/activities.ex index a3f2687..6c57ac4 100644 --- a/lib/postland/activities.ex +++ b/lib/postland/activities.ex @@ -187,7 +187,7 @@ defmodule Postland.Activities do def record_activity(params) do params |> Activity.changeset() - |> Repo.insert() + |> Repo.insert(on_conflict: :replace_all, conflict_target: :id) end # TODO: add effects for CRUD notes diff --git a/lib/postland/follows.ex b/lib/postland/follows.ex index 42b99d3..01d712f 100644 --- a/lib/postland/follows.ex +++ b/lib/postland/follows.ex @@ -67,6 +67,15 @@ defmodule Postland.Follows do |> Repo.transaction() end + def record_and_reject(follow) do + Multi.new() + |> Multi.delete(:delete, follow) + |> Multi.run(:send_reject_follow, fn _data, _repo -> + send_reject(follow) + end) + |> Repo.transaction() + end + def send_withdrawl(request) do request.followee |> ActivityPub.fetch_actor() @@ -115,7 +124,7 @@ defmodule Postland.Follows do {:error, "could not find Follow activity to Accept"} follow_activity -> - body = + activity = %{ "@context" => "https://www.w3.org/ns/activitystreams", "type" => "Accept", @@ -127,18 +136,51 @@ defmodule Postland.Follows do "type" => "Follow" } } - |> Jason.encode!() - headers = - Headers.signing_headers( - "POST", - inbox, - body, - Postland.my_actor_id(), - Accounts.solo_user().private_key - ) + ActivityPub.post_activity( + Postland.my_actor_id(), + Accounts.solo_user().private_key, + inbox, + activity + ) + end + end - Req.post(inbox, headers: headers, body: body) + def send_reject(request) do + request.follower + |> ActivityPub.fetch_actor() + |> do_send_reject(request) + end + + defp do_send_reject(nil, _), do: {:ok, nil} + + defp do_send_reject({:ok, actor}, request) do + inbox = Map.get(actor, "inbox") + + case get_follow_activity(request.follower) do + nil -> + {:error, "could not find Follow activity to Reject"} + + follow_activity -> + activity = + %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Reject", + "actor" => Postland.my_actor_id(), + "object" => %{ + "actor" => request.follower, + "id" => Map.get(follow_activity.data, "id"), + "object" => Postland.my_actor_id(), + "type" => "Follow" + } + } + + ActivityPub.post_activity( + Postland.my_actor_id(), + Accounts.solo_user().private_key, + inbox, + activity + ) end end diff --git a/lib/postland_web/components/core_components.ex b/lib/postland_web/components/core_components.ex index 52cef1f..068d442 100644 --- a/lib/postland_web/components/core_components.ex +++ b/lib/postland_web/components/core_components.ex @@ -52,13 +52,21 @@ defmodule PostlandWeb.CoreComponents do

+ <.button + :if={@status == :follower_pending} + phx-click="reject" + phx-value-dom-id={@dom_id} + phx-value-id={@account["id"]} + > + Reject + <.button :if={@status == :follower_pending} phx-click="confirm" phx-value-dom-id={@dom_id} phx-value-id={@account["id"]} > - Accept Request + Accept <.button :if={@status == :following_pending} @@ -484,7 +492,7 @@ defmodule PostlandWeb.CoreComponents do def simple_form(assigns) do ~H""" <.form :let={f} for={@for} as={@as} {@rest}> -
+
<%= render_slot(@inner_block, f) %>
<%= render_slot(action, f) %> diff --git a/lib/postland_web/live/dev_live.ex b/lib/postland_web/live/dev_live.ex new file mode 100644 index 0000000..e134225 --- /dev/null +++ b/lib/postland_web/live/dev_live.ex @@ -0,0 +1,28 @@ +defmodule PostlandWeb.DevLive do + use PostlandWeb, :live_view + use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router + + alias Postland.Activities + + def render(assigns) do + ~H""" + <.button phx-click="make_follower_request">Make follower request + """ + end + + def handle_event("make_follower_request", _, socket) do + mock_actor = "https://example.com/actors/test" + encoded_followee = Base.url_encode64(Postland.my_actor_id(), padding: false) + encoded_follower = Base.url_encode64(mock_actor, padding: false) + + Activities.process_activity(%{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}"), + "type" => "Follow", + "actor" => mock_actor, + "object" => Postland.my_actor_id() + }) + + {:noreply, socket} + end +end diff --git a/lib/postland_web/live/followers_live.ex b/lib/postland_web/live/followers_live.ex index 939a28d..a632c8c 100644 --- a/lib/postland_web/live/followers_live.ex +++ b/lib/postland_web/live/followers_live.ex @@ -43,7 +43,7 @@ defmodule PostlandWeb.FollowersLive do {:ok, socket |> stream(:accounts, followers) |> assign(count: account_count)} end - def handle_event("confirm", %{"id" => id, "dom-id" => dom_id}, socket) do + def handle_event("confirm", %{"id" => id}, socket) do request = Follows.get(id, Postland.my_actor_id()) socket = @@ -53,7 +53,7 @@ defmodule PostlandWeb.FollowersLive do acct = %{ id: request.follower, - confirmed: !!request.onfirmed_at, + confirmed: !!request.confirmed_at, account: Actors.actor(request.follower) } @@ -66,4 +66,27 @@ defmodule PostlandWeb.FollowersLive do {:noreply, socket} end + + def handle_event("reject", %{"id" => id}, socket) do + request = Follows.get(id, Postland.my_actor_id()) + + socket = + case Follows.record_and_reject(request) do + {:ok, _} -> + acct = %{ + id: request.follower, + confirmed: true, + account: Actors.actor(request.follower) + } + + socket + |> stream_delete(:accounts, acct) + |> assign(count: socket.assigns.count - 1) + + _ -> + put_flash(socket, :error, "An unexpected error occurred.") + end + + {:noreply, socket} + end end diff --git a/lib/postland_web/live/user_login_live.ex b/lib/postland_web/live/user_login_live.ex index d2791c9..fbe9304 100644 --- a/lib/postland_web/live/user_login_live.ex +++ b/lib/postland_web/live/user_login_live.ex @@ -8,19 +8,21 @@ defmodule PostlandWeb.UserLoginLive do Log in to account - <.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore"> - <.input field={@form[:username]} type="text" label="Username" required /> - <.input field={@form[:password]} type="password" label="Password" required /> +
+ <.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore"> + <.input field={@form[:username]} type="text" label="Username" required /> + <.input field={@form[:password]} type="password" label="Password" required /> - <:actions> - <.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" /> - - <:actions> - <.button phx-disable-with="Logging in..." class="w-full"> - Log in - - - + <:actions> + <.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" /> + + <:actions> + <.button phx-disable-with="Logging in..." class="w-full"> + Log in + + + +
""" end diff --git a/lib/postland_web/router.ex b/lib/postland_web/router.ex index 2dc9bb4..634328d 100644 --- a/lib/postland_web/router.ex +++ b/lib/postland_web/router.ex @@ -58,6 +58,11 @@ defmodule PostlandWeb.Router do pipe_through :browser live_dashboard "/dashboard", metrics: PostlandWeb.Telemetry + + live_session :mount_current_user_dev, + on_mount: [{PostlandWeb.UserAuth, :mount_current_user}] do + live "/mocks", PostlandWeb.DevLive + end end end