nulla/lib/nulla_web/controllers/inbox_controller.ex

100 lines
2.9 KiB
Elixir

defmodule NullaWeb.InboxController do
use NullaWeb, :controller
alias Nulla.HTTPSignature
alias Nulla.ActivityPub
alias Nulla.Utils
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
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.activity(%{
ap_id: follow_id,
type: "Follow",
actor: remote_actor.id,
object: target_uri
}),
accept_activity <-
Activity.activity(%{
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.follow_accept(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(local_actor.private_key_pem)
|> 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)
else
error ->
IO.inspect(error, label: "Follow error")
json(conn, %{"error" => "Failed to process Follow"})
end
end
end