diff --git a/README.md b/README.md index 9ca983a..fdb7cd8 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ ## Protocol Support - [x] Check that signature header (digest) matches digest of body contents -- [ ] Check the domain of the public key against the domain of the object being CRUDed +- [x] Check the domain of the public key against the domain of the object being CRUDed ## Testing diff --git a/lib/activity_pub.ex b/lib/activity_pub.ex index 53dafae..b6c3aa7 100644 --- a/lib/activity_pub.ex +++ b/lib/activity_pub.ex @@ -38,9 +38,12 @@ defmodule ActivityPub do |> Req.Request.put_header("accept", "application/json") case Req.get(request) do - {:ok, result} -> + {:ok, %{status: 200} = result} -> {:ok, result.body} + {:ok, %{status: 404}} -> + nil + error -> error end diff --git a/lib/activity_pub/headers.ex b/lib/activity_pub/headers.ex index 87a9915..c515133 100644 --- a/lib/activity_pub/headers.ex +++ b/lib/activity_pub/headers.ex @@ -77,6 +77,9 @@ defmodule ActivityPub.Headers do {:ok, public_key} -> :public_key.verify(to_verify, :sha256, signature, public_key) + nil -> + false + error -> error end diff --git a/lib/postland/actors.ex b/lib/postland/actors.ex index ed9f2cb..5f69c07 100644 --- a/lib/postland/actors.ex +++ b/lib/postland/actors.ex @@ -6,9 +6,13 @@ defmodule Postland.Actors do def actor(actor_id) do result = Cachex.fetch(:main_cache, "actor:#{actor_id}", fn _key -> - {:ok, actor} = ActivityPub.fetch_actor(actor_id) + case ActivityPub.fetch_actor(actor_id) do + {:ok, actor} -> + {:commit, actor, expire: :timer.seconds(300)} - {:commit, actor, expire: :timer.seconds(300)} + nil -> + {:commit, nil, expire: :timer.seconds(300)} + end end) case result do diff --git a/lib/postland/follows.ex b/lib/postland/follows.ex index d435243..42b99d3 100644 --- a/lib/postland/follows.ex +++ b/lib/postland/follows.ex @@ -52,14 +52,30 @@ defmodule Postland.Follows do def record_and_send_withdrawl(request) do Multi.new() |> Multi.delete(:delete, request) - |> Multi.run(:send_acceptance, fn _data, _repo -> + |> Multi.run(:send_withdrawl, fn _data, _repo -> send_withdrawl(request) end) |> Repo.transaction() end + def record_and_unfollow(follow) do + Multi.new() + |> Multi.delete(:delete, follow) + |> Multi.run(:send_delete_follow, fn _data, _repo -> + send_delete_follow(follow) + end) + |> Repo.transaction() + end + def send_withdrawl(request) do - {:ok, actor} = ActivityPub.fetch_actor(request.followee) + request.followee + |> ActivityPub.fetch_actor() + |> do_send_withdrawl(request) + end + + defp do_send_withdrawl(nil, _), do: {:ok, nil} + + defp do_send_withdrawl({:ok, actor}, request) do inbox = Map.get(actor, "inbox") body = @@ -84,7 +100,14 @@ defmodule Postland.Follows do end def send_acceptance(request) do - {:ok, actor} = ActivityPub.fetch_actor(request.follower) + request.follower + |> ActivityPub.fetch_actor() + |> do_send_acceptance(request) + end + + defp do_send_acceptance(nil, _), do: {:ok, nil} + + defp do_send_acceptance({:ok, actor}, request) do inbox = Map.get(actor, "inbox") case get_follow_activity(request.follower) do @@ -180,6 +203,34 @@ defmodule Postland.Follows do Req.post(inbox, headers: headers, body: follow_request) end + def send_delete_follow(follow) do + encoded_followee = Base.url_encode64(follow.followee, padding: false) + encoded_follower = Base.url_encode64(follow.follower, padding: false) + + {:ok, actor} = ActivityPub.fetch_actor(follow.followee) + inbox = Map.get(actor, "inbox") + + follow_request = + %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Delete", + "actor" => Postland.my_actor_id(), + "object" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}") + } + |> Jason.encode!() + + headers = + Headers.signing_headers( + "POST", + inbox, + follow_request, + Postland.my_actor_id(), + Accounts.solo_user().private_key + ) + + Req.post(inbox, headers: headers, body: follow_request) + end + def get(follower, followee) do from(f in Follow, where: f.followee == ^followee, where: f.follower == ^follower) |> Repo.one() diff --git a/lib/postland_web/components/core_components.ex b/lib/postland_web/components/core_components.ex index c66812e..bfc7053 100644 --- a/lib/postland_web/components/core_components.ex +++ b/lib/postland_web/components/core_components.ex @@ -60,6 +60,14 @@ defmodule PostlandWeb.CoreComponents do > Withdraw Request + <.button + :if={@status == :following_confirmed} + phx-click="unfollow" + phx-value-dom-id={@dom_id} + phx-value-id={@account["id"]} + > + Unfollow + @@ -177,7 +185,7 @@ defmodule PostlandWeb.CoreComponents do end def post_card(assigns) do - author = Postland.Timeline.attribution(assigns.post) || %{} + author = Postland.Timeline.attribution(assigns.post) %{host: host} = case author do @@ -201,7 +209,7 @@ defmodule PostlandWeb.CoreComponents do |> Map.put(:cw, cw) ~H""" -
+
Enum.reject(&is_nil(&1.account)) account_count = Enum.count(followers) diff --git a/lib/postland_web/live/following_live.ex b/lib/postland_web/live/following_live.ex index f730059..7decb85 100644 --- a/lib/postland_web/live/following_live.ex +++ b/lib/postland_web/live/following_live.ex @@ -33,6 +33,7 @@ defmodule PostlandWeb.FollowingLive do account: Actors.actor(follow.followee) } end) + |> Enum.reject(&is_nil(&1.account)) account_count = Enum.count(follows) @@ -60,4 +61,21 @@ defmodule PostlandWeb.FollowingLive do {:noreply, socket} end + + def handle_event("unfollow", %{"id" => id, "dom-id" => dom_id}, socket) do + request = Follows.get(Postland.my_actor_id(), id) + + socket = + case Follows.record_and_unfollow(request) do + {:ok, _} -> + socket + |> stream_delete_by_dom_id(:accounts, dom_id) + |> assign(:count, socket.assigns.count - 1) + + _ -> + put_flash(socket, :error, "An unexpected error occurred.") + end + + {:noreply, socket} + end end