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
|
defmodule Vonbraun.ActivityPubReq do
|
||||||
require Logger
|
require Logger
|
||||||
alias Vonbraun.HTTPSignature
|
alias Vonbraun.HTTPSignature
|
||||||
|
alias Vonbraun.ActivityPub.Object
|
||||||
@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
|
|
||||||
|
|
||||||
def get(url = %URI{:path => path, :query => query, :host => host}) do
|
def get(url = %URI{:path => path, :query => query, :host => host}) do
|
||||||
headers = %{
|
headers = %{
|
||||||
|
@ -27,7 +16,7 @@ defmodule Vonbraun.ActivityPubReq do
|
||||||
path
|
path
|
||||||
end
|
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)
|
Req.get(url, headers: headers)
|
||||||
end
|
end
|
||||||
|
@ -46,7 +35,7 @@ defmodule Vonbraun.ActivityPubReq do
|
||||||
path
|
path
|
||||||
end
|
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}`")
|
Logger.debug("POST payload is: `#{body}`")
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Vonbraun.Application do
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
Vonbraun.KeyAgent,
|
Vonbraun.KeyAgent,
|
||||||
|
Vonbraun.ActivityPub.Handler,
|
||||||
Vonbraun.Repo,
|
Vonbraun.Repo,
|
||||||
{Bandit, scheme: :http, plug: Vonbraun.MyRouter, port: 4012}
|
{Bandit, scheme: :http, plug: Vonbraun.MyRouter, port: 4012}
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Vonbraun.InboxRouter do
|
||||||
alias Vonbraun.HTTPSignature
|
alias Vonbraun.HTTPSignature
|
||||||
alias Vonbraun.ActivityPubReq
|
alias Vonbraun.ActivityPubReq
|
||||||
alias Vonbraun.Ecto.Schema.Actor
|
alias Vonbraun.Ecto.Schema.Actor
|
||||||
|
alias Vonbraun.ActivityPub.Object
|
||||||
use Plug.Router
|
use Plug.Router
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -83,43 +84,28 @@ defmodule Vonbraun.InboxRouter do
|
||||||
{:error, :notfound}
|
{:error, :notfound}
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: make sure I'm in the to list.
|
|
||||||
defp handle_activity(
|
defp handle_activity(
|
||||||
%{"type" => "Follow", "actor" => actor_id, "object" => follow_target},
|
%{"type" => "Follow", "actor" => actor_id, "object" => follow_target},
|
||||||
actor = %{}
|
actor = %{}
|
||||||
)
|
)
|
||||||
when is_binary(follow_target) do
|
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}}}
|
{: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
|
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 =
|
activity_type =
|
||||||
case follows_me_state do
|
case follows_me_state do
|
||||||
"accepted" ->
|
"accepted" ->
|
||||||
"Accept"
|
:accept
|
||||||
|
|
||||||
"rejected" ->
|
"rejected" ->
|
||||||
"Reject"
|
:reject
|
||||||
|
|
||||||
"pending" ->
|
"pending" ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if activity_type do
|
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("Replying to follow request with: #{activity_type}")
|
||||||
Logger.debug("And payload: `#{payload}`")
|
Logger.debug("And payload: `#{payload}`")
|
||||||
|
@ -172,7 +158,7 @@ defmodule Vonbraun.InboxRouter do
|
||||||
with {:asked, {:ok, %Actor{:blocked => nil, :following_state => "accepted"}}} <-
|
with {:asked, {:ok, %Actor{:blocked => nil, :following_state => "accepted"}}} <-
|
||||||
{:asked, Actor.mark_pending_follow(actor_id, "accepted", force: true)},
|
{:asked, Actor.mark_pending_follow(actor_id, "accepted", force: true)},
|
||||||
{:actor, {:ok, follow_actor_id}} <- {:actor, extract_follow_object_actor(object)},
|
{: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}")
|
Logger.info("Now following: #{actor_id}")
|
||||||
{:ok, :following}
|
{:ok, :following}
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in New Issue