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