Update
This commit is contained in:
parent
aac9bcb6e4
commit
3f329cf59e
13 changed files with 191 additions and 119 deletions
|
@ -1,106 +1,165 @@
|
|||
defmodule NullaWeb.InboxController do
|
||||
use NullaWeb, :controller
|
||||
alias Nulla.HTTPSignature
|
||||
alias Nulla.ActivityPub
|
||||
alias Nulla.Snowflake
|
||||
alias Nulla.HTTPSignature
|
||||
alias Nulla.Utils
|
||||
alias Nulla.Models.User
|
||||
alias Nulla.Models.Actor
|
||||
alias Nulla.Models.Relation
|
||||
alias Nulla.Models.Activity
|
||||
|
||||
def inbox(
|
||||
conn,
|
||||
%{"id" => follow_id, "type" => "Follow", "actor" => actor_uri, "object" => target_uri}
|
||||
) do
|
||||
def inbox(conn, %{
|
||||
"id" => follow_id,
|
||||
"type" => "Follow",
|
||||
"actor" => actor_uri,
|
||||
"object" => target_uri
|
||||
}) do
|
||||
accept_id = Snowflake.next_id()
|
||||
|
||||
with local_actor <- Actor.get_actor(ap_id: target_uri),
|
||||
{:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri),
|
||||
:ok <- HTTPSignature.verify(conn, remote_actor_json),
|
||||
remote_actor <-
|
||||
Actor.create_actor(
|
||||
remote_actor_json
|
||||
|> Map.put("ap_id", remote_actor_json["id"])
|
||||
|> Map.delete("id")
|
||||
|> Map.put("domain", URI.parse(remote_actor_json["id"]).host)
|
||||
),
|
||||
follow_activity <-
|
||||
Activity.create_activity(%{
|
||||
ap_id: follow_id,
|
||||
type: "Follow",
|
||||
actor: remote_actor.id,
|
||||
object: target_uri
|
||||
}),
|
||||
accept_activity <-
|
||||
Activity.create_activity(%{
|
||||
id: accept_id,
|
||||
ap_id: "https://#{local_actor.domain}/activities/accept/#{accept_id}",
|
||||
type: "Accept",
|
||||
actor: local_actor.ap_id,
|
||||
object: follow_activity
|
||||
}),
|
||||
_ <-
|
||||
Relation.create_relation(%{
|
||||
followed_by: true,
|
||||
local_actor_id: local_actor.id,
|
||||
remote_actor_id: remote_actor.id
|
||||
}) do
|
||||
body = Jason.encode!(ActivityPub.activity(accept_activity))
|
||||
|
||||
digest =
|
||||
:crypto.hash(:sha256, body)
|
||||
|> Base.encode64()
|
||||
|> then(&("SHA-256=" <> &1))
|
||||
|
||||
date =
|
||||
DateTime.utc_now()
|
||||
|> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
host = URI.parse(actor_uri).host
|
||||
request_target = "post /inbox"
|
||||
|
||||
signature_string =
|
||||
"""
|
||||
(request-target): #{request_target}
|
||||
host: #{host}
|
||||
date: #{date}
|
||||
digest: #{digest}
|
||||
"""
|
||||
|
||||
user = User.get_user(id: local_actor.id)
|
||||
privateKeyPem = user.privateKey["privateKeyPem"]
|
||||
|
||||
private_key =
|
||||
:public_key.pem_decode(privateKeyPem)
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
signature =
|
||||
:public_key.sign(signature_string, :sha256, private_key)
|
||||
|> Base.encode64()
|
||||
|
||||
signature_header =
|
||||
"""
|
||||
keyId="#{local_actor.publicKey["id"]}",
|
||||
algorithm="rsa-sha256",
|
||||
headers="(request-target) host date digest content-type",
|
||||
signature="#{signature}"
|
||||
"""
|
||||
|> String.replace("\n", "")
|
||||
|> String.trim()
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_resp_header("host", host)
|
||||
|> put_resp_header("date", date)
|
||||
|> put_resp_header("signature", signature_header)
|
||||
|> put_resp_header("digest", digest)
|
||||
|> send_resp(200, body)
|
||||
{:ok, remote_actor} <- get_or_create_actor(remote_actor_json),
|
||||
{:ok, follow_activity} <-
|
||||
create_follow_activity(follow_id, remote_actor.ap_id, target_uri),
|
||||
{:ok, accept_activity} <-
|
||||
create_accept_activity(accept_id, local_actor, follow_activity),
|
||||
{:ok, _relation} <- get_or_create_relation(local_actor.id, remote_actor.id),
|
||||
:ok <- deliver_accept(accept_activity, remote_actor_json, local_actor) do
|
||||
send_resp(conn, 200, "")
|
||||
else
|
||||
error ->
|
||||
IO.inspect(error, label: "Follow error")
|
||||
json(conn, %{"error" => "Failed to process Follow"})
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_create_actor(remote_actor_json) do
|
||||
ap_id = remote_actor_json["id"]
|
||||
|
||||
case Actor.get_actor(ap_id: ap_id) do
|
||||
nil ->
|
||||
params =
|
||||
remote_actor_json
|
||||
|> Map.put("ap_id", ap_id)
|
||||
|> Map.delete("id")
|
||||
|> Map.put("domain", URI.parse(ap_id).host)
|
||||
|
||||
case Actor.create_actor(params) do
|
||||
{:ok, actor} -> {:ok, actor}
|
||||
{:error, changeset} -> {:error, {:actor_creation_failed, changeset}}
|
||||
end
|
||||
|
||||
actor ->
|
||||
{:ok, actor}
|
||||
end
|
||||
end
|
||||
|
||||
defp create_follow_activity(follow_id, actor_id, target_uri) do
|
||||
Activity.create_activity(%{
|
||||
ap_id: follow_id,
|
||||
type: "Follow",
|
||||
actor: actor_id,
|
||||
object: target_uri
|
||||
})
|
||||
end
|
||||
|
||||
defp create_accept_activity(accept_id, local_actor, follow_activity) do
|
||||
Activity.create_activity(%{
|
||||
id: accept_id,
|
||||
ap_id: "https://#{local_actor.domain}/activities/accept/#{accept_id}",
|
||||
type: "Accept",
|
||||
actor: local_actor.ap_id,
|
||||
object: Jason.encode!(follow_activity)
|
||||
})
|
||||
end
|
||||
|
||||
defp get_or_create_relation(local_actor_id, remote_actor_id) do
|
||||
case Relation.get_relation(local_actor_id: local_actor_id, remote_actor_id: remote_actor_id) do
|
||||
nil ->
|
||||
Relation.create_relation(%{
|
||||
followed_by: true,
|
||||
local_actor_id: local_actor_id,
|
||||
remote_actor_id: remote_actor_id
|
||||
})
|
||||
|
||||
relation ->
|
||||
{:ok, relation}
|
||||
end
|
||||
end
|
||||
|
||||
defp deliver_accept(accept_activity, remote_actor_json, local_actor) do
|
||||
inbox_url = remote_actor_json["inbox"]
|
||||
accept_activity = %Activity{accept_activity | object: Jason.decode!(accept_activity.object)}
|
||||
body = Jason.encode!(ActivityPub.activity(accept_activity))
|
||||
|
||||
digest =
|
||||
:crypto.hash(:sha256, body)
|
||||
|> Base.encode64()
|
||||
|> then(&("SHA-256=" <> &1))
|
||||
|
||||
date =
|
||||
DateTime.utc_now()
|
||||
|> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
uri = URI.parse(inbox_url)
|
||||
request_target = "post #{uri.path}"
|
||||
|
||||
signature_string = """
|
||||
(request-target): #{request_target}
|
||||
host: #{uri.host}
|
||||
date: #{date}
|
||||
digest: #{digest}
|
||||
"""
|
||||
|
||||
user = User.get_user(id: local_actor.id)
|
||||
|
||||
private_key =
|
||||
case :public_key.pem_decode(user.privateKeyPem) do
|
||||
[{_type, der, _rest}] ->
|
||||
:public_key.der_decode(:RSAPrivateKey, der)
|
||||
|
||||
_ ->
|
||||
raise "Invalid PEM format"
|
||||
end
|
||||
|
||||
signature =
|
||||
:public_key.sign(signature_string, :sha256, private_key)
|
||||
|> Base.encode64()
|
||||
|
||||
signature_header =
|
||||
"""
|
||||
keyId="#{local_actor.publicKey["id"]}",
|
||||
algorithm="rsa-sha256",
|
||||
headers="(request-target) host date digest content-type",
|
||||
signature="#{signature}"
|
||||
"""
|
||||
|> String.replace("\n", "")
|
||||
|> String.trim()
|
||||
|
||||
headers = [
|
||||
{"host", uri.host},
|
||||
{"date", date},
|
||||
{"digest", digest},
|
||||
{"signature", signature_header},
|
||||
{"content-type", "application/activity+json"}
|
||||
]
|
||||
|
||||
request = Finch.build(:post, inbox_url, headers, body)
|
||||
|
||||
case Finch.request(request, Nulla.Finch) do
|
||||
{:ok, %Finch.Response{status: code}} when code in 200..299 ->
|
||||
IO.puts("Accept delivered successfully")
|
||||
:ok
|
||||
|
||||
{:ok, %Finch.Response{status: code, body: resp}} ->
|
||||
IO.inspect({:error, code, resp}, label: "Failed to deliver Accept")
|
||||
{:error, {:http_error, code}}
|
||||
|
||||
{:error, reason} ->
|
||||
IO.inspect(reason, label: "Finch delivery failed")
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue