untested
This commit is contained in:
parent
1470dcebb7
commit
249ae3619b
|
@ -0,0 +1,29 @@
|
|||
defmodule Vonbraun.ActivityPub.Handler do
|
||||
use Agent
|
||||
require Logger
|
||||
|
||||
def start_link(_opts) do
|
||||
Agent.start_link(fn -> load_handlers() end, name: __MODULE__)
|
||||
end
|
||||
|
||||
defp load_handlers() do
|
||||
[
|
||||
Vonbraun.ActivityPub.Handler.Follow,
|
||||
Vonbraun.ActivityPub.Handler.Accept,
|
||||
Vonbraun.ActivityPub.Handler.Reject,
|
||||
Vonbraun.ActivityPub.Handler.Undo
|
||||
]
|
||||
|> Enum.reduce(Map.new(), fn mod, map ->
|
||||
type = apply(mod, :type, [])
|
||||
Map.put(map, type, &mod.handle/1)
|
||||
end)
|
||||
end
|
||||
|
||||
@spec handle(%{type: String.t()}) :: :ok | {:ok, atom()} | {:error, any()}
|
||||
def handle(activity = %{"type" => type}) when is_binary(type) do
|
||||
Agent.get(__MODULE__, fn map ->
|
||||
func = Map.get(map, type, fn _ -> {:error, :type} end)
|
||||
apply(func, [activity])
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Vonbraun.ActivityPub.Handler.Accept do
|
||||
@behaviour Vonbraun.ActivityPub.HandlerBehaviour
|
||||
|
||||
def type, do: "Accept"
|
||||
|
||||
# Lots of kinds of things can be accepted.
|
||||
def handle(_activity = %{"type" => "Accept"}) do
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Vonbraun.ActivityPub.Handler.Follow do
|
||||
@behaviour Vonbraun.ActivityPub.HandlerBehaviour
|
||||
|
||||
def type, do: "Follow"
|
||||
|
||||
def handle(_activity = %{"type" => "Follow"}) do
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Vonbraun.ActivityPub.Handler.Reject do
|
||||
@behaviour Vonbraun.ActivityPub.HandlerBehaviour
|
||||
|
||||
def type, do: "Reject"
|
||||
|
||||
# Lots of kinds of things can be accepted.
|
||||
def handle(_activity = %{"type" => "Reject"}) do
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Vonbraun.ActivityPub.Handler.Undo do
|
||||
@behaviour Vonbraun.ActivityPub.HandlerBehaviour
|
||||
|
||||
def type, do: "Undo"
|
||||
|
||||
# Lots of different kinds of things can be undone.
|
||||
def handle(_activity = %{"type" => "Undo"}) do
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
defmodule Vonbraun.ActivityPub.HandlerBehaviour do
|
||||
@callback type() :: String.t()
|
||||
@callback handle(activity :: map()) :: :ok | {:ok, atom()} | {:error, any()}
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
defmodule Vonbraun.ActivityPub.Object do
|
||||
@context "https://www.w3.org/ns/activitystreams"
|
||||
|
||||
@spec my_id() :: String.t()
|
||||
def my_id() do
|
||||
domain = Application.fetch_env!(:vonbraun, :domain)
|
||||
nickname = Application.fetch_env!(:vonbraun, :nickname)
|
||||
"https://#{domain}/users/#{nickname}"
|
||||
end
|
||||
|
||||
@spec my_key_id() :: String.t()
|
||||
def my_key_id() do
|
||||
"#{my_id()}#main-key"
|
||||
end
|
||||
|
||||
def activity(type, id, object, options \\ [])
|
||||
when is_binary(type) and is_binary(id) and (is_binary(object) or is_map(object)) do
|
||||
to =
|
||||
case Keyword.get(options, :to, []) do
|
||||
to when is_binary(to) -> [to]
|
||||
to when is_list(to) -> to
|
||||
end
|
||||
|
||||
cc =
|
||||
case Keyword.get(options, :cc, []) do
|
||||
cc when is_binary(cc) -> [cc]
|
||||
cc when is_list(cc) -> cc
|
||||
end
|
||||
|
||||
copy_recipients? = Keyword.get(options, :copy_recipients, false)
|
||||
|
||||
object =
|
||||
if is_map(object) && copy_recipients? do
|
||||
Map.merge(object, %{"to" => to, "cc" => cc})
|
||||
else
|
||||
object
|
||||
end
|
||||
|
||||
%{
|
||||
"@context" => @context,
|
||||
"id" => id,
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"bcc" => [],
|
||||
"bto" => [],
|
||||
"type" => type,
|
||||
"actor" => my_id(),
|
||||
"object" => object
|
||||
}
|
||||
end
|
||||
|
||||
def my_follow_activity_id(to_follow_id) when is_binary(to_follow_id),
|
||||
do:
|
||||
"https://#{Application.fetch_env!(:vonbraun, :domain)}/id/follow:#{URI.encode(to_follow_id)}"
|
||||
|
||||
def follow_activity(to_follow_id) when is_binary(to_follow_id),
|
||||
do: activity("Follow", my_follow_activity_id(to_follow_id), to_follow_id, to: to_follow_id)
|
||||
|
||||
def accept_follow_activity(followee_id, type \\ :accept)
|
||||
when is_binary(followee_id) and type in [:accept, :reject] do
|
||||
activity_type =
|
||||
if type == :accept do
|
||||
"Accept"
|
||||
else
|
||||
"Reject"
|
||||
end
|
||||
|
||||
object = %{
|
||||
"type" => "Follow",
|
||||
"actor" => followee_id
|
||||
}
|
||||
|
||||
accept_activity_id =
|
||||
"https://#{Application.fetch_env!(:vonbraun, :domain)}/id/follow-reply:#{URI.encode(followee_id)}"
|
||||
|
||||
activity(activity_type, accept_activity_id, object)
|
||||
end
|
||||
end
|
|
@ -1,18 +1,7 @@
|
|||
defmodule Vonbraun.ActivityPubReq do
|
||||
require Logger
|
||||
alias Vonbraun.HTTPSignature
|
||||
|
||||
@spec actor_id() :: String.t()
|
||||
def actor_id() do
|
||||
domain = Application.fetch_env!(:vonbraun, :domain)
|
||||
nickname = Application.fetch_env!(:vonbraun, :nickname)
|
||||
"https://#{domain}/users/#{nickname}"
|
||||
end
|
||||
|
||||
@spec key_id() :: String.t()
|
||||
def key_id() do
|
||||
"#{actor_id()}#main-key"
|
||||
end
|
||||
alias Vonbraun.ActivityPub.Object
|
||||
|
||||
def get(url = %URI{:path => path, :query => query, :host => host}) do
|
||||
headers = %{
|
||||
|
@ -27,7 +16,7 @@ defmodule Vonbraun.ActivityPubReq do
|
|||
path
|
||||
end
|
||||
|
||||
headers = HTTPSignature.add_get_signature(headers, key_id(), target)
|
||||
headers = HTTPSignature.add_get_signature(headers, Object.my_key_id(), target)
|
||||
|
||||
Req.get(url, headers: headers)
|
||||
end
|
||||
|
@ -46,7 +35,7 @@ defmodule Vonbraun.ActivityPubReq do
|
|||
path
|
||||
end
|
||||
|
||||
headers = HTTPSignature.add_post_signature(headers, key_id(), target, body)
|
||||
headers = HTTPSignature.add_post_signature(headers, Object.my_key_id(), target, body)
|
||||
|
||||
Logger.debug("POST payload is: `#{body}`")
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Vonbraun.Application do
|
|||
def start(_type, _args) do
|
||||
children = [
|
||||
Vonbraun.KeyAgent,
|
||||
Vonbraun.ActivityPub.Handler,
|
||||
Vonbraun.Repo,
|
||||
{Bandit, scheme: :http, plug: Vonbraun.MyRouter, port: 4012}
|
||||
]
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Vonbraun.InboxRouter do
|
|||
alias Vonbraun.HTTPSignature
|
||||
alias Vonbraun.ActivityPubReq
|
||||
alias Vonbraun.Ecto.Schema.Actor
|
||||
alias Vonbraun.ActivityPub.Object
|
||||
use Plug.Router
|
||||
require Logger
|
||||
|
||||
|
@ -83,43 +84,28 @@ defmodule Vonbraun.InboxRouter do
|
|||
{:error, :notfound}
|
||||
end
|
||||
|
||||
# TODO: make sure I'm in the to list.
|
||||
defp handle_activity(
|
||||
%{"type" => "Follow", "actor" => actor_id, "object" => follow_target},
|
||||
actor = %{}
|
||||
)
|
||||
when is_binary(follow_target) do
|
||||
with {:valid_target, true} <- {:valid_target, ActivityPubReq.actor_id() == follow_target},
|
||||
with {:valid_target, true} <- {:valid_target, Object.my_id() == follow_target},
|
||||
{:add, {:ok, %Actor{:blocked => nil, :follows_me_state => follows_me_state}}}
|
||||
when not is_nil(follows_me_state) <- {:add, Actor.maybe_add_follower(actor_id)} do
|
||||
domain = Application.fetch_env!(:vonbraun, :domain)
|
||||
activity_id = "https://#{domain}/id/follow-reply:#{URI.encode(actor_id)}"
|
||||
|
||||
payload = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => activity_id,
|
||||
"to" => [actor_id],
|
||||
"actor" => ActivityPubReq.actor_id(),
|
||||
"object" => %{
|
||||
"type" => "Follow",
|
||||
"actor" => actor_id
|
||||
}
|
||||
}
|
||||
|
||||
activity_type =
|
||||
case follows_me_state do
|
||||
"accepted" ->
|
||||
"Accept"
|
||||
:accept
|
||||
|
||||
"rejected" ->
|
||||
"Reject"
|
||||
:reject
|
||||
|
||||
"pending" ->
|
||||
nil
|
||||
end
|
||||
|
||||
if activity_type do
|
||||
payload = Map.put(payload, "type", activity_type) |> Jason.encode!()
|
||||
payload = Object.accept_follow_activity(actor_id, activity_type) |> Jason.encode!()
|
||||
|
||||
Logger.debug("Replying to follow request with: #{activity_type}")
|
||||
Logger.debug("And payload: `#{payload}`")
|
||||
|
@ -172,7 +158,7 @@ defmodule Vonbraun.InboxRouter do
|
|||
with {:asked, {:ok, %Actor{:blocked => nil, :following_state => "accepted"}}} <-
|
||||
{:asked, Actor.mark_pending_follow(actor_id, "accepted", force: true)},
|
||||
{:actor, {:ok, follow_actor_id}} <- {:actor, extract_follow_object_actor(object)},
|
||||
{:match, true} <- {:match, follow_actor_id == ActivityPubReq.actor_id()} do
|
||||
{:match, true} <- {:match, follow_actor_id == Object.my_id()} do
|
||||
Logger.info("Now following: #{actor_id}")
|
||||
{:ok, :following}
|
||||
else
|
||||
|
|
Loading…
Reference in New Issue